'Why is my Checkbox not updating when I pass a callback from a stateful widget to stateless widget?

Im trying to use Lift the State for my Checkbox and I know that by changing my TaskTile to a stateful widget ill be able to solve my problem but I want to know why my Checkbox isnt updating when I pass the setstate as a callback from a stateful widget.Im sorry if this question is dumb im just getting started with Flutter so any help is appreciated.

This is my stateless widget which builds the checkbox in a tile

import 'package:flutter/material.dart';

class TaskTile extends StatelessWidget {
  final String title;
  final bool isChecked;
  final void Function(bool?) checkboxCallback;

  TaskTile({required this.title,required this.isChecked,required this.checkboxCallback});

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(title,style: TextStyle(decoration: isChecked?TextDecoration.lineThrough:null),),
      trailing: Checkbox(
        value: isChecked,
        onChanged: checkboxCallback,
      ),
    );
  }
}

And this is the stateful widget where I am giving the callback

import 'package:flutter/material.dart';
import 'task_tile.dart';
import 'task.dart';

class TaskList extends StatefulWidget {
  @override
  _TaskListState createState() => _TaskListState();
}

class _TaskListState extends State<TaskList> {
  @override
  Widget build(BuildContext context) {
    bool isChecked = false;

    List<Task> tasks = [
      Task(title: 'Buy Milk', isDone: isChecked),
      Task(title: 'Buy Bread', isDone: isChecked),
      Task(title: 'Buy Eggs', isDone: isChecked),
    ];

    return ListView.builder(itemBuilder: (context, index) {
      return TaskTile(title: tasks[index].title,isChecked: tasks[index].isDone,checkboxCallback: (checkboxState){
        setState(() {
          tasks[index].toggleIsDone();
        });
      },);
    },itemCount: tasks.length,);
  }
}

Also this is the Task Class where I am giving the toggle function to change the state

class Task{
  final String title;
  bool isDone;

  Task({required this.title,required this.isDone});

  void toggleIsDone(){
    isDone = !isDone;
  }


}


Solution 1:[1]

You are not calling the function. Instead of

trailing: Checkbox(
        value: isChecked,
        onChanged: checkboxCallback,
      ),

Can you try

trailing: Checkbox(
        value: isChecked,
        onChanged: (){checkboxCallback();},
      ),

Solution 2:[2]

You need to make TaskTile statefull. TaskList can be StatelessWidget.

Here is the answer:


import 'package:flutter/material.dart';

class TaskList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    bool isChecked = false;

    List<Task> tasks = [
      Task(title: 'Buy Milk', isDone: isChecked),
      Task(title: 'Buy Bread', isDone: isChecked),
      Task(title: 'Buy Eggs', isDone: isChecked),
    ];

    return ListView.builder(
      itemBuilder: (context, index) {
        return TaskTile(
          title: tasks[index].title,
          isChecked: tasks[index].isDone,
          checkboxCallback: (value) {
            tasks[index].toggleIsDone();
          },
        );
      },
      itemCount: tasks.length,
    );
  }
}

class TaskTile extends StatefulWidget {
  final String title;
  final bool isChecked;
  final Function checkboxCallback;

  TaskTile(
      {required this.title,
      required this.isChecked,
      required this.checkboxCallback,
      Key? key})
      : super(key: key);

  @override
  _TaskTileState createState() => _TaskTileState();
}

class _TaskTileState extends State<TaskTile> {
  late bool _isChecked;

  @override
  void initState() {
    super.initState();
    _isChecked = widget.isChecked;
  }

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(
        widget.title,
        style: TextStyle(
            decoration: _isChecked ? TextDecoration.lineThrough : null),
      ),
      trailing: Checkbox(
        value: _isChecked,
        onChanged: (value) {
          widget.checkboxCallback(value);
          setState(() {
            _isChecked = value!;
          });
        },
      ),
    );
  }
}

class Task {
  final String title;
  bool isDone;

  Task({required this.title, required this.isDone});

  void toggleIsDone() {
    isDone = !isDone;
  }
}

Solution 3:[3]

For any dynamic change your widget class need to be stateful, if you are using vscode than you can easily change from stateless to statful . First hover to your statless widget and than you will get a bulb like indicator , press it than you will get an option of changing the state.

Solution 4:[4]

According to my understanding, You are trying to change the state (isChanged) of the Task object in a list. This list inturn is used in listview builder. Since flutter work with immutable data and you are sending the reference of the list to the listview builder, the changes of the element in the list wont be reflected in the UI, eventhough you used setState function. Instead try creating a new list with the same name with the updated element. This will definitely work.

I got the idea from this solution - https://stackoverflow.com/a/61847327/11844877

PS I am a newbie to flutter and don't mind if my answer wasn't helpful.

Solution 5:[5]

This is the solution from the link below:


import 'package:flutter/material.dart';
class TaskList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    bool isChecked = false;
    List<Task> tasks = [
      Task(title: 'Buy Milk', isDone: isChecked),
      Task(title: 'Buy Bread', isDone: isChecked),
      Task(title: 'Buy Eggs', isDone: isChecked),
    ];
    return ListView.builder(
      itemBuilder: (context, index) {
        return TaskTile(
          title: tasks[index].title,
          isChecked: tasks[index].isDone,
          checkboxCallback: (value) {
            tasks[index].toggleIsDone();
          },
        );
      },
      itemCount: tasks.length,
    );
  }
}
class TaskTile extends StatefulWidget {
  final String title;
  final bool isChecked;
  final Function checkboxCallback;
  TaskTile(
      {required this.title,
      required this.isChecked,
      required this.checkboxCallback,
      Key? key})
      : super(key: key);
  @override
  _TaskTileState createState() => _TaskTileState();
}
class _TaskTileState extends State<TaskTile> {
  late bool _isChecked;
  @override
  void initState() {
    super.initState();
    _isChecked = widget.isChecked;
  }
  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(
        widget.title,
        style: TextStyle(
            decoration: _isChecked ? TextDecoration.lineThrough : null),
      ),
      trailing: Checkbox(
        value: _isChecked,
        onChanged: (value) {
          widget.checkboxCallback(value);
          setState(() {
            _isChecked = value!;
          });
        },
      ),
    );
  }
}
class Task {
  final String title;
  bool isDone;
  Task({required this.title, required this.isDone});
  void toggleIsDone() {
    isDone = !isDone;
  }
}

https://codeutility.org/why-is-my-checkbox-not-updating-when-i-pass-a-callback-from-a-stateful-widget-to-stateless-widget/

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Rahul Mishra
Solution 2 Yeasin Sheikh
Solution 3 Manishyadav
Solution 4
Solution 5 naji Iz-Aldeen