Dismissible и FutureBuilder не работают вместе - PullRequest
1 голос
/ 10 ноября 2019

Это довольно сложная проблема, но я сделаю все возможное, чтобы объяснить ее.

В моем проекте используется база данных sqflite. Эта конкретная страница возвращает список виджетов Dismissible в соответствии с данными в базе данных. Вот как я читаю данные:

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
}

Виджет TaskList возвращает страницу с FutureBuilder, которая создает ListView.builder с данными из базы данных. ListView создает Dismissible виджеты. Отклонение виджетов Dismissible обновляет строку в базе данных и снова считывает данные, чтобы обновить список.

метод построения для 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
      ],
    );
  }

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
          ),
        );
      },
    );
  }

Вот проблема (может быть неправильное толкование, я все еще новичок):

Увольнение виджета, по сути, удаляет его из списка. После того как пользователь отклоняет задачу, она «визуально» удаляется из списка и вызывается метод update(), который вызывает setState(). Вызов setState() вызывает повторное построение FutureBuilder, но вызов dbProvider.getAllTasks() не завершается к тому времени, когда FutureBuilder строит снова. Следовательно, FutureBuilder передает старый снимок, в результате чего ListView снова строится с только что отклоненной задачей. Это приводит к тому, что отклоненный ListTile появляется на мгновение после отклонения, что выглядит жутко и неправильно.

Я понятия не имею, как это исправить. Любая помощь будет оценена.

1 Ответ

0 голосов
/ 11 ноября 2019

Нашел обходной путь для этого, не используя FutureBuilder и вызывая setState после завершения запроса.

Вместо Future<List<Task>> состояние теперь содержит List<Task>, который объявлен как ноль.

class TaskListState extends State<TaskList> {
  DBProvider dbProvider = new DBProvider();
  DateTime now = DateTime.now();
  List<Task> todayTasks;
  //build
}

Функция update() была изменена следующим образом

void update() async {
    Future<List<Task>> futureTasks = dbProvider.getByDate(now); //first make the query
    futureTasks.then((data){
      List<Task> tasks = new List<Task>();
      for(int i = 0; i < data.length; i++) {
        print(data[i].name);
        tasks.add(data[i]);
      }
      setState(() {
        todayTasks = tasks; //then setState and rebuild the widget
      });
    });
  }

Таким образом, виджет не перестраивается до завершения будущего, что и было проблемой, с которой я столкнулся.

Я полностью удалил FutureBuilder, Listview.builder просто строит в соответствии со списком, хранящимся в состоянии.

@override
  Widget build(BuildContext context) {
    if(todayTasks == null) {
      update();
      return Center(
        child: CircularProgressIndicator(),
      );
    } //make a query if the list has not yet been initialized
    return ListView(
      physics: const AlwaysScrollableScrollPhysics(),
      children: <Widget>[
        //other widgets
        pendingList(Task.filterByDone(false, todayTasks)),
      ],
    );
  }

Этот подход полностью решил мою проблему, и я думаю, что это лучше, чем использование FutureBuilder в случае будущего должен быть завершен, прежде чем виджет снова будет построен.

...