I'm new to flutter and have a memory leak in my program. I was wondering if anyone jumped out to anyone about my code as to why it might be causing a memory leak. I was hoping I made some sort of newbie mistake that someone could call out. I commented out my listviewbuilder row widget and replaced it with simple text, and that seemed to lessen the memory leak. So it seems like it might be row related, but I don't understand how.
I am not scrolling or anything like that I just fire up the memory profiler. It shows that there is an ever-increasing size in the filtered node, but does not say why.
I went back through my changesets and found some code that seemed to be causing the issues. Specifically the memory leak went away when I rmeoveLoadingAnimationWidget:
AnimatedOpacity(
opacity: isUpdating ? 1.0 : 0.0,
duration: isUpdating
? const Duration(milliseconds: 0)
: const Duration(milliseconds:500),
child: Row(
children: [
LoadingAnimationWidget.fourRotatingDots(
color: Colors.black12,
size: 20,
),
SizedBox(
width: 3,
),
Text(
"Saving...",
style: TextStyle(color: Colors.grey.shade600),
),
],
),
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:save_extra_money/main.dart';
import 'package:save_extra_money/shared/loading/LoadingIndicator.dart';
import 'package:save_extra_money/transactions/TransactionUpdaterProvider.dart';
import 'package:save_extra_money/transactions/TransactionsLoadingStates.dart';
import 'package:save_extra_money/transactions/transactionGridRow.dart';
import 'package:http/http.dart' as http;
import '../Shared/ColorContainer.dart';
import '../model/Transaction.dart';
import '../model/webCallInputsAndOutputs/RetrieveTransactionsResults.dart';
import '../shared/globals/GlobalUi.dart';
import 'TransactionsViewModel.dart';
class Transactions extends ConsumerStatefulWidget {
Transactions({Key? key}) : super(key: key);
@override
TransactionsState createState() => TransactionsState();
}
class TransactionsState extends ConsumerState<Transactions> {
ScrollController? controller;
@override
void initState() {
super.initState();
controller = ScrollController()..addListener(_scrollListener);
}
@override
void dispose() {
if (controller != null) {
controller!.removeListener(_scrollListener);
}
super.dispose();
}
void _scrollListener() {
if (controller == null) {
return;
}
// print(controller.position.extentAfter);
if (controller!.position.extentAfter < 300) {
ref.read(transactionsViewModel.notifier).addMorePagedData();
}
}
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
var transactionUpdater = ref.watch(transactionUpdaterProvider);
var viewModel = ref.watch(transactionsViewModel);
viewModel.loadInitialData();
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(viewModel.getTitle()),
backgroundColor: GlobalUI.appBar,
centerTitle: true,
),
body: ColorContainer(
size: size,
child: ClipRRect(
borderRadius: BorderRadius.circular(5.0),
child:
getInitialLayout(viewModel, transactionUpdater),
),
),
);
}
Widget getInitialLayout(TransactionsViewModel viewModel,
TransactionUpdaterProvider transactionUpdater){
if(viewModel.state == TransactionsLoadingStates.InitialLoad){
return LoadingIndicator(text: 'Transactions...');
}
else if(viewModel.state == TransactionsLoadingStates.InitialError) {
return Text('Error: ${viewModel.errorText}');
}
else{
return ListView.builder(
controller: controller,
itemCount: viewModel.totalCount,
itemBuilder: (BuildContext context, int index) {
if(index==viewModel.transactions.length){
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(5),
bottomRight: Radius.circular(5)
),
color: GlobalUI.rowBackground,
),
child: LoadingAnimationWidget.fourRotatingDots(
color: Colors.black12,
size: 60,
),
);
}
if(index>viewModel.transactions.length){
return SizedBox(height: 0,width: 0,);
}
var transaction = viewModel.transactions[index];
return Text(transaction.id);
/*
return TransactionGridRow(
transaction: transaction,
isUpdating: transactionUpdater.isUpdating(transaction.id),
);
*/
});
}
}
}
CodePudding user response:
You should not make api calls in your widget build method
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:save_extra_money/main.dart';
import 'package:save_extra_money/shared/loading/LoadingIndicator.dart';
import 'package:save_extra_money/transactions/TransactionUpdaterProvider.dart';
import 'package:save_extra_money/transactions/TransactionsLoadingStates.dart';
import 'package:save_extra_money/transactions/transactionGridRow.dart';
import 'package:http/http.dart' as http;
import '../Shared/ColorContainer.dart';
import '../model/Transaction.dart';
import '../model/webCallInputsAndOutputs/RetrieveTransactionsResults.dart';
import '../shared/globals/GlobalUi.dart';
import 'TransactionsViewModel.dart';
class Transactions extends ConsumerStatefulWidget {
Transactions({Key? key}) : super(key: key);
@override
TransactionsState createState() => TransactionsState();
}
class TransactionsState extends ConsumerState<Transactions> {
ScrollController? controller;
@override
void initState() {
super.initState();
controller = ScrollController()..addListener(_scrollListener);
viewModel.loadInitialData(); // You can call your async methods in initstate
}
@override
void dispose() {
if (controller != null) {
controller!.removeListener(_scrollListener);
}
super.dispose();
}
void _scrollListener() {
if (controller == null) {
return;
}
// print(controller.position.extentAfter);
if (controller!.position.extentAfter < 300) {
ref.read(transactionsViewModel.notifier).addMorePagedData();
}
}
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
var transactionUpdater = ref.watch(transactionUpdaterProvider);
var viewModel = ref.watch(transactionsViewModel);
viewModel.loadInitialData(); // Here is the method you should not call here
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(viewModel.getTitle()),
backgroundColor: GlobalUI.appBar,
centerTitle: true,
),
body: ColorContainer(
size: size,
child: ClipRRect(
borderRadius: BorderRadius.circular(5.0),
child:
getInitialLayout(viewModel, transactionUpdater),
),
),
);
}
Widget getInitialLayout(TransactionsViewModel viewModel,
TransactionUpdaterProvider transactionUpdater){
if(viewModel.state == TransactionsLoadingStates.InitialLoad){
return LoadingIndicator(text: 'Transactions...');
}
else if(viewModel.state == TransactionsLoadingStates.InitialError) {
return Text('Error: ${viewModel.errorText}');
}
else{
return ListView.builder(
controller: controller,
itemCount: viewModel.totalCount,
itemBuilder: (BuildContext context, int index) {
if(index==viewModel.transactions.length){
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(5),
bottomRight: Radius.circular(5)
),
color: GlobalUI.rowBackground,
),
child: LoadingAnimationWidget.fourRotatingDots(
color: Colors.black12,
size: 60,
),
);
}
if(index>viewModel.transactions.length){
return SizedBox(height: 0,width: 0,);
}
var transaction = viewModel.transactions[index];
return Text(transaction.id);
/*
return TransactionGridRow(
transaction: transaction,
isUpdating: transactionUpdater.isUpdating(transaction.id),
);
*/
});
}
}
}
CodePudding user response:
- In addition to batuhand, Avoid this:
if(index>viewModel.transactions.length){
return SizedBox(height: 0,width: 0,);
}
Each time index is greater than length it will built a useless SizedBox.
Why do you use "viewModel.totalCount" and "viewModel.transactions.length"? It should be the same.
And if you compare index and length add a minus to length
if(index==viewModel.transactions.length-1)