使用 setState 更新变量的 Flutter 回调函数在引用时不更新

问题描述 投票:0回答:2

背景:我有一个 Todo 应用程序,我正在尝试制作一个可以在本地 (sqflite) 或远程保存任务到 firebase 的地方。下面是添加 Todo 页面的快照。

问题

我最近添加了一个切换按钮作为一个单独的小部件,并获取选定的按钮(个人/远程)并通过回调函数返回一个布尔值到我的 AddTodoScreen。

但是,当我单击远程并添加任务时,它说任务是保存在本地而不是远程,并且 isRemote 的布尔值是 false。

但是当我检查我的回调函数时,它能够将我的变量设置为 true。只是当我引用变量时,尽管使用了 setState,它还没有更新。

代码:

回调函数

getSaveLocation(bool isRemote) {
  setState(() {
    remoteTask = isRemote;
    print("SET STATE CALLED VALUE OF ISREMOTE: $isRemote");
  });
}

添加待办事项的逻辑

    ToggleButton(callbackFunction: getSaveLocation,),
    FloatingActionButton.extended(onPressed: () 
    {
        //creates ToDo item
        var todo = Todo(
            id: idGenerator(),
            title: controllerTask.value.text,
            description: controllerDescription.value.text,
            isRemote: remoteTask, //THIS SHOULD CHANGE FROM THE CALLBACK
        );
    
        print("the value of is remote");
        print(todo.isRemote);
    
        //get Todos bloc add new item
        context.read<TodosBloc>().add(AddTodo(todo: todo));
    
        Navigator.pop(context);
    },
    label: const Text('add task',
    style: TextStyle(color: Colors.black),
  ),

完整代码:addTodoScreen

class _AddTodoScreen extends State<AddTodoScreen> {

  final controllerTask = TextEditingController();
  String taskTitle = '';

  final controllerDescription = TextEditingController();
  String description ='';

  bool remoteTask = false; //THIS VARIABLE CHANGES TO TRUE NOT RECOGNIZED

  @override
  void initState() {
    super.initState();

    controllerTask.addListener(() => setState(() {}));
    controllerDescription.addListener(() => setState(() {}));
  }

  @override
  Widget build(BuildContext context) {

    int idGenerator() {
      final now = DateTime.now();
      return now.microsecondsSinceEpoch;
    }

    getSaveLocation(bool isRemote) {
      setState(() {
        remoteTask = isRemote;
        print("SET STATE CALLED VALUE OF ISREMOTE: $isRemote");
      });
    }

    return Scaffold(
      appBar: AppBar(
        title: const Text('BloC Pattern: Add a To Do'),
      ),
      body: BlocListener<TodosBloc, TodosState>(
        listener: (context, state) {
          // TODO: implement listener
          if(state is TodosLoaded) {
            ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('added'),
                )
            );
          }
        },
        child: Card(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Column(
              children: [
                Expanded(
                  child: Center(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [

                        _inputField('Title', controllerTask),
                        _inputField('Description', controllerDescription),

                        //CALLBACK FN CHANGES 'remoteTask' value based on toggle button
                        ToggleButton(callbackFunction: getSaveLocation,),

                        FloatingActionButton.extended(
                          onPressed: () {
                            //creates ToDo item should take the updated remoteTask value

                            var todo = Todo(
                              id: idGenerator(),
                              title: controllerTask.value.text,
                              description: controllerDescription.value.text,
                              isRemote: remoteTask, //WHEN REFERENCED ALWAYS RETURNS FALSE
                            );

                            print("the value of is remote");
                            print(todo.isRemote);

                            //get Todos bloc add new item
                            context.read<TodosBloc>().add(AddTodo(todo: todo));

                            Navigator.pop(context);
                          },
                          label: const Text('add task',
                            style: TextStyle(color: Colors.black),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ]
            )
          ),
        ),
      ),
    );
  }
}

代码:切换按钮小部件

class ToggleButton extends StatefulWidget {


  final Function callbackFunction;
  const ToggleButton({Key? key, required this.callbackFunction}) : super(key: key);

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

class ToggleButtonState extends State<ToggleButton> {

  List<bool> isSelected = [true, false];

  @override
  Widget build(BuildContext context) => Container(
    color: Colors.green.withOpacity(.5),
    child: ToggleButtons(
      fillColor: Colors.lightBlue.shade900,
      selectedColor: Colors.white,
      isSelected: isSelected,
      renderBorder: false,
      children: const [
        Padding(
            padding: EdgeInsets.symmetric(horizontal: 12),
            child: Text('Personal',)
        ),
        Padding(
            padding: EdgeInsets.symmetric(horizontal: 12),
            child: Text('Remote',)
        ),
      ],
      onPressed: (int newIndex){
        setState(() {
          for(int idx = 0; idx < isSelected.length; idx++ ){
            if(idx == newIndex){
              isSelected[idx] = true;
            }
            else{
              isSelected[idx] = false;
            }
          }
          //check the first toggle button value
          bool isRemote = isSelected[1];
          print("Sending this value $isRemote to callback function");
          widget.callbackFunction(isSelected[1]);
        });
      },
    ),
  );
}

我相当确定问题不是来自切换按钮,因为我得到的打印输出表明状态发生了变化。

也愿意接受建议,即将逻辑移入我的 BloC 或在没有回调的情况下处理更新。对 MVVM/Bloc 架构模式来说还很陌生,所以也许我应该在其他地方或使用新的 Bloc 来处理这个逻辑?

编辑

Todo 集团代码

class TodosBloc extends Bloc<TodoEvent, TodosState> {

  TodoRepository todosRepository;

  TodosBloc(this.todosRepository) : super(TodosLoading()) {
    on<LoadTodos>(_onLoadTodos);
    on<AddTodo>(_onAddTodo);
    on<DeleteTodo>(_onDeleteTodo);
    on<UpdateTodo>(_onUpdateTodo);
    on<EditTodo>(_onEditTodo);
  }

  Future<void> _onLoadTodos(LoadTodos event, Emitter<TodosState> emit,) async {

    List<Todo?> allTodos = await todosRepository.getTodos() as List<Todo?>;

    emit(TodosLoaded(todos: allTodos));
  }

  Future<void> _onAddTodo(
      AddTodo event,
      Emitter<TodosState> emit,
      ) async {

    final state = this.state;
    if (state is TodosLoaded) {

      await todosRepository.add(event.todo);

      emit(TodosLoaded(todos: List.from(state.todos)..add(event.todo),),
      );
    }
  }
}

待办事件

abstract class TodoEvent extends Equatable {
  const TodoEvent();

  @override
  List<Object> get props => [];
}

class AddTodo extends TodoEvent {
  final Todo todo;

  const AddTodo({
    required this.todo,
  });

  @override
  List<Object> get props => [todo];
}

TodoState

@immutable
abstract class TodosState {}

class TodosLoading extends TodosState {}

class TodosLoaded extends TodosState {
  final List<Todo?> todos;

  TodosLoaded({
    this.todos = const <Todo?>[],
  });

  @override
  List<Object> get props => [todos];
}

class TodosError extends TodosState {}

Todo 存储库

class TodoRepository<Todo> extends IRepository {

  //final ITodoRepository<Todo> hiveLocalStorage;
  final DatabaseHelper localSqlLiteRepository;
  final RemoteDataSource firebaseRepository;

  TodoRepository({
    required this.localSqlLiteRepository,
    required this.firebaseRepository,
  });

  @override
  Future add(todo) async {

    print("ADDING TODO");
    var isRemote = todo.isRemote;
    print("VALUE OF ISREMOTE: $isRemote"); //VALUE IS ALWAYS FALSE
    // When code reaches this point the value of todo doesn't use the 
    //updated value



    if(todo.isRemote){
      try{
        await firebaseRepository.add(todo);
      }
      on Exception catch (e){
        print(e);
      }
    }
    else {
      await localSqlLiteRepository.add(todo);
    }

    return;
  }
}
flutter user-interface mvvm setstate
2个回答
0
投票

你的 ToggleButton 的回调函数看起来像下面的代码, 我修改了它,基本上如果你在回调函数中传递任何值,你必须在那里定义它的数据类型才能正常工作

class ToggleButton extends StatefulWidget {
    
    
      final Function(bool) callbackFunction;
      const ToggleButton({Key? key, required this.callbackFunction}) : super(key: key);
    
      @override
      ToggleButtonState createState() => ToggleButtonState();
    }
    
    class ToggleButtonState extends State<ToggleButton> {
    
      List<bool> isSelected = [true, false];
    
      @override
      Widget build(BuildContext context) => Container(
        color: Colors.green.withOpacity(.5),
        child: ToggleButtons(
          fillColor: Colors.lightBlue.shade900,
          selectedColor: Colors.white,
          isSelected: isSelected,
          renderBorder: false,
          children: const [
            Padding(
                padding: EdgeInsets.symmetric(horizontal: 12),
                child: Text('Personal',)
            ),
            Padding(
                padding: EdgeInsets.symmetric(horizontal: 12),
                child: Text('Remote',)
            ),
          ],
          onPressed: (int newIndex){
            setState(() {
              for(int idx = 0; idx < isSelected.length; idx++ ){
                if(idx == newIndex){
                  isSelected[idx] = true;
                }
                else{
                  isSelected[idx] = false;
                }
              }
              //check the first toggle button value
              bool isRemote = isSelected[1];
              print("Sending this value $isRemote to callback function");
              widget.callbackFunction(isSelected[1]);
            });
          },
        ),
      );
    }

-1
投票

要解决此问题,请将 Todo 对象的创建移动到 FloatingActionButton 的 onPressed 函数中。这将确保在创建 Todo 时使用 remoteTask 的最新值。

代码:

FloatingActionButton.extended(
  onPressed: () {
    var todo = Todo(
      id: idGenerator(),
      title: controllerTask.value.text,
      description: controllerDescription.value.text,
      isRemote: remoteTask,
    );

    context.read<TodosBloc>().add(AddTodo(todo: todo));

    Navigator.pop(context);
  },
  label: const Text(
    'add task',
    style: TextStyle(color: Colors.black),
  ),
)
© www.soinside.com 2019 - 2024. All rights reserved.