1

I have a Flutter app where setState() function is called inside loadExpenses() function right after a database query method. initially the count of the expense list (expCount) is set to 0. Later on, based on the selection of fund, the expense list is updated and hence the expCount is updated so that ListView.builder() can properly update the listview. However, I can observe that after selecting fund from fund dropDownMenu the expenseList is updated properly in the loadExpenses() function. But this.expCount = _expenses.length; is not updating the expCount value and that's why the ListView Builder cannot create the list.

this.dbHelper
    .getExpensesByFundId(this._selectedFund.id)
    .then((List<Expense> expenses) {
  setState(() {
    this._expenses = expenses;
    this.expCount = _expenses.length;
});

However, if I take that line outside the setState() function, I can see the expCount variable is updating just find. Can you tell me what is the problem here?

Here is my code:

    import 'package:flutter/material.dart';
    import 'package:ksk_tavel_exp/Models/expense.dart';
    import 'package:ksk_tavel_exp/Models/fund.dart';
    import 'package:ksk_tavel_exp/utils/dbHelper.dart';
    
    class ExpenseList extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return ExpenseListState();
      }
    }
    
    class ExpenseListState extends State<ExpenseList> {
      List<Fund> _funds;
      List<Expense> _expenses;
      Fund _selectedFund;
      int expCount = 0;
    
      DatabaseHelper dbHelper = DatabaseHelper();
    
      loadFunds() {
        if (this._selectedFund == null) {
          this.dbHelper.getFunds().then((List<Fund> funds) {
            setState(() {
              this._funds = funds;
              this._selectedFund = this._funds[0];
              this.loadExpenses();
            });
          });
        }
      }
    
      loadExpenses() {
        this
            .dbHelper
            .getExpensesByFundId(this._selectedFund.id)
            .then((List<Expense> expenses) {
          setState(() {
            this._expenses = expenses;
            this.expCount = _expenses.length;

         });
        });
   
      }
    
      Widget expenseBuilder(BuildContext context, int index) {
        return ListTile(
          leading: Text(this._expenses[index].date),
          title: Text(this._expenses[index].expense),
          trailing: Text('${this._expenses[index].amount} BDT'),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        double screenHeight = MediaQuery.of(context).size.height;
        this.loadFunds();
    //    this.loadExpenses();
    
        return Scaffold(
          appBar: AppBar(
            title: Text('KSK Expense App'),
            elevation: 1.0,
          ),
          body: Column(
            children: <Widget>[
              this._funds != null && this._selectedFund != null
                  ? getFunds(screenHeight)
                  : Container(),
              this._expenses != null ? this.getExpenses(screenHeight) : Container()
            ],
          ),
        );
      }
    
      Container getFunds(double screenHeight) {
        return Container(
          height: screenHeight*0.05,
          child: ListTile(
            leading: Text('Fund'),
            title: DropdownButton<Fund>(
              items: _funds.map((Fund item) {
                return DropdownMenuItem<Fund>(
                  value: item,
                  child: Text(item == null ? '' : item.fundName),
                );
              }).toList(),
              onChanged: (Fund selectedFund) {
                this._selectedFund = selectedFund;
                this.loadExpenses();
                this.expCount = _expenses.length;
              },
            ),
          ),
        );
      }
    
      Widget getExpenses(double screenHeight) {
        return Container(
            height: screenHeight*0.80,
          child: ListView.builder(
            itemBuilder: this.expenseBuilder,
            itemCount: this.expCount,
          ),
        );
      }
    
    }
Omatt
  • 8,564
  • 2
  • 42
  • 144
javaland235
  • 743
  • 3
  • 11
  • 21
  • try setstate in expense only. i.e. remove setstate in loadfund – Kenneth Li Feb 04 '19 at 07:18
  • it's not working. Only when I am taking that line out of the setState() in loadExpense(), the expCount variable is being updated. Like this: `loadExpenses() { this .dbHelper .getExpensesByFundId(this._selectedFund.id) .then((List expenses) { setState(() { this._expenses = expenses; }); }); this.expCount = _expenses.length;` – javaland235 Feb 04 '19 at 07:50
  • this.expCount = _expenses.length; this line should not be inside getfund! Since the db functions are async, But you're assuming it runs in sync. – Kenneth Li Feb 04 '19 at 08:36
  • can you print _expense.length inside setstate of loadexpense to see its value? is it correct? – Kenneth Li Feb 04 '19 at 08:40
  • I know db functions are async. Actually expCount requires _selected_fund to be initiated first which is being initiated inside the this.dbHelper.getFunds().then() function. You can see that this.loadExpenses() is called at the end of dbHelper.getFunds().then() function which means loadExpense() is called after loadFund() db call is done. And I separated the loadExpense() function because funds are only loaded in the beginning. After that only Expense List need to be reloaded. – javaland235 Feb 04 '19 at 09:06
  • Ans to your second question, when I print _expense.length inside setState of loadExpense(), I see 0 all the time. For some reason it only updates if take it out of .getExpensesByFundId(this._selectedFund.id).then() function. – javaland235 Feb 04 '19 at 09:06
  • I have found out that setState() is not the problem. When I am moving the `this.expCount = _expenses.length;` outside the setState() but still within the then() function of db query it still doesn't work. Like this: `loadExpenses() { this.dbHelper .getExpensesByFundId(this._selectedFund.id) .then((List expenses) { setState(() { this._expenses = expenses; }); this.expCount = _expenses.length; print(_expenses.length); }); }` – javaland235 Feb 04 '19 at 09:18
  • It only works if it is outside the .then() function of db query. Like this `loadExpenses() { this .dbHelper .getExpensesByFundId(this._selectedFund.id) .then((List expenses) { setState(() { this._expenses = expenses; }); }); this.expCount = _expenses.length; print(_expenses.length); }` – javaland235 Feb 04 '19 at 09:21
  • why not move the 2 sentences before the setstate, and then run an EMPTY setstate? – Kenneth Li Feb 04 '19 at 09:31
  • anyway, in async operations, I did not feel save to use setstate as State Management, I would rather uses Redux, I can post an answer with examples if your want to know Redux. – Kenneth Li Feb 04 '19 at 09:34
  • I don't think setstate() is the problem anymore. It's the then() function after dB query is the problem. I'm not clear why it's happening. then() is like onNext() function in Rxjs which loads after a certain time consuming action is completed. So anything written within then() function is async safe. – javaland235 Feb 04 '19 at 10:03
  • I'm familiar with redux but not too comfortable though. So thanks for your help – javaland235 Feb 04 '19 at 10:04
  • Are you sure that is the problem? Can you try wrapping the `getExpenses` container in Expanded? I think there is overflow issue due to column and listView. – Rahul May 22 '22 at 17:48

1 Answers1

0

There's no need to include this.expCount = _expenses.length; inside setState(), it can be used on ListView.builder() directly since the Widgets will be rebuilt after setState() was called.

ListView.builder(
  itemBuilder: this.expenseBuilder,
  itemCount: _expenses.length,
),

I also find it odd to see the need of updating expCount after calling loadExpenses() inside DropdownButton.onChanged()

Omatt
  • 8,564
  • 2
  • 42
  • 144