It is kind of a complex problem but I'll do my best to explain it.
My project utilizes a sqflite database. This particular page returns a list of Dismissible widgets according to the data in the database. This is how I read the data:
class TaskListState extends State<TaskList> {
DBProvider dbProvider = new DBProvider();
Future<List<Task>> allTasks;
@override
void initState() {
allTasks = dbProvider.getAllTasks();
super.initState();
}
void update(){
setState(() {
allTasks = dbProvider.getAllTasks();
});
}
//build
}
The TaskList widget returns a page with a FutureBuilder, which builds a ListView.builder with the data from the database. The ListView builds Dismissible widgets. Dismissing the Dismissible widgets updates a row in the database and reads the data again to update the list.
build method for TaskListState
@override
Widget build(BuildContext context) {
return ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: <Widget>[
//other widgets such as a title for the list
),
FutureBuilder(
future: allTasks,
builder: (context, snapshot){
if(snapshot.hasError){
return Text("Data has error.");
} else if (!snapshot.hasData){
return Center(
child: CircularProgressIndicator(),
);
} else {
return pendingList(Task.filterByDone(false, Task.filterByDate(Datetime.now, snapshot.data))); //filters the data to match current day
}
},
),
//other widgets
],
);
}
The pendingList
Widget pendingList(List<Task> tasks){
//some code to return a Text widget if "tasks" is empty
return ListView.separated(
separatorBuilder: (context, index){
return Divider(height: 2.0);
},
itemCount: tasks.length,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, index){
return Dismissible(
//dismissible backgrounds, other non-required parameters
key: Key(UniqueKey().toString()),
onDismissed: (direction) async {
Task toRemove = tasks[index]; //save the dismissed task for the upcoming operations
int removeIndex = tasks.indexWhere((task) => task.id == toRemove.id);
tasks.removeAt(removeIndex); //remove the dismissed task
if(direction == DismissDirection.endToStart) {
rateTask(toRemove).then((value) => update()); //rateTask is a function that updates the task, it is removed from the list
}
if(direction == DismissDirection.startToEnd) {
dbProvider.update(/*code to update selected task*/).then((value) => update());
}
},
child: ListTile(
//ListTile details
),
);
},
);
}
Here is the problem (might be a wrong interpretation I'm still kind of new):
Dismissing a widget essentially removes it from the list. After the user dismisses a task, the task is "visually" removed from the list and the update()
method is called, which calls setState()
. Calling setState()
causes the FutureBuilder to build again, but the dbProvider.getAllTasks()
call is not completed by the time the FutureBuilder builds again. Therefore, the FutureBuilder passes the old snapshot, which causes the ListView to build again with the Task that just was dismissed. This causes the dismissed ListTile to appear momentarily after being dismissed, which looks creepy and wrong.
I have no idea how to fix this. Any help would be appreciated.