Search code examples
flutterdartstatefulwidget

Flutter, how do I correctly declare and use variables in a stateful widget?


I have a StatefulWidget that contains a ListView() with a ListView.builder(). The ListView.builder() builds several CheckboxListTile()'s using two Lists, one for the labels and one for the booleans. However it is not working as intended. I tried to create a minimal example of the problem, it looks like this:

import 'package:flutter/material.dart';

class TestClass extends StatefulWidget {
  @override
  _TestClassState createState() => _TestClassState();
}

class _TestClassState extends State<TestClass> {
  @override
  Widget build(BuildContext context) {
    bool firstBoolean = false;
    bool secondBoolean = false;
    List labels = [
    "First Checkbox",
    "Second Checkbox",
    ];  
    List<bool> booleans = [
      firstBoolean,
      secondBoolean,
    ];    
    return ListView(
      children: [
        Container(
          height: MediaQuery.of(context).size.height * 0.5,
          child: ListView.builder(
              physics: AlwaysScrollableScrollPhysics(),
              shrinkWrap: true,
              itemCount: 2,
              itemBuilder: (context, index) {
                return CheckboxListTile(
                  title: Text("${labels[index]}"),
                  value: booleans[index],
                  onChanged: (bool newValue) {
                    setState(() {
                      secondBoolean = newValue;
                      print(
                          "firstBoolean, secondBoolean: [$firstBoolean, $secondBoolean]");
                    });
                  },
                );
              }),
            ),
          ],
        );
      }
    }

This does not change the checkboxes and also not the assigned booleans. If I replace this line:

value: booleans[index]

with

value: firstBoolean

And move the bool firstBoolean = false; above the @override in the _TestCassState It 'works' but since all the checkboxes now have the same boolean variable assigned to them it changes all of them at the same time. But I dont understand why this 'works' and the code above does not. Also if I try to create all the booleans I need above @override and then try to create a list containing all of them, I get this error:

The instance member 'firstBoolean' can't be accessed in an initializer. Try replacing the reference to the instance member with a different expression

If I move to List with the labels above the @override it doesnt give me this error and assigns the labels correctly

I feel like I am not understanding something fundamental about StatefulWidgets. So I would really aprecciate if somebody could give me an explanation of how to solve this correctly and why this is not working as intended


Solution

  • You don't even need the individual bool variables.

    You can basically have your labels inside your TestClass since they are not part of the state since they never change.

    Next, you can have just the List<bool> booleans in your State class since all your changing stuff is just these booleans itself.

    So your structure will be like this now.

    class TestClass extends StatefulWidget {
      final List labels = [
        "First Checkbox",
        "Second Checkbox",
      ];
      
      @override
      _TestClassState createState() => _TestClassState();
    }
    

    This is good practice to put them in the StatefulWidget class. You can then use them inside your State class like this - widget.labels[index].

    So, your State class will be,

    class _TestClassState extends State<TestClass> {
      List<bool> booleans = [false,false];
    
      @override
      Widget build(BuildContext context) {
        return ListView(
          children: [
            Container(
              height: MediaQuery.of(context).size.height * 0.5,
              child: ListView.builder(
                physics: AlwaysScrollableScrollPhysics(),
                shrinkWrap: true,
                itemCount: 2,
                itemBuilder: (context, index) {
                  return CheckboxListTile(
                    title: Text("${widget.labels[index]}"),
                    value: booleans[index],
                    onChanged: (bool newValue) {
                      setState(() {
                        // There was a logic error here in your code, so changed it to work correctly
                        booleans[index] = newValue;
                    });
                  },
                );
              }),
            ),
          ],
        );
      }
    }
    

    enter image description here