背景:我有一个 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;
}
}
你的 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]);
});
},
),
);
}
要解决此问题,请将 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),
),
)