【问题标题】:Dismissible and FutureBuilder do not work togetherDismissible 和 FutureBuilder 不能一起工作
【发布时间】:2020-03-06 08:46:26
【问题描述】:

这是一个复杂的问题,但我会尽力解释它。

我的项目使用 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
      ],
    );
  }

待定列表

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 再次构建,但在 FutureBuilder 再次构建时,dbProvider.getAllTasks() 调用尚未完成。因此,FutureBuilder 传递旧的快照,这会导致 ListView 使用刚刚关闭的 Task 再次构建。这会导致被解散的 ListTile 在被解散后立即出现,这看起来令人毛骨悚然且错误。

我不知道如何解决这个问题。任何帮助将不胜感激。

【问题讨论】:

    标签: flutter dart future sqflite dismissible


    【解决方案1】:

    我遇到了完全相同的问题,我使用的是与Futures 配合使用的 sqflite,所以我最终将FutureBuilderDismissible 一起用于我的ListView。被解雇的列表项将删除,然后重新出现一个框架,然后再次消失。我遇到了这个问题:

    https://groups.google.com/forum/#!topic/flutter-dev/pC48MMVKJGc

    这建议从快照数据本身中删除列表项:

    return FutureBuilder<List<FolderModel>>(
          future: Provider.of<FoldersProvider>(context).getFolders(),
          builder: (context, snapshot) {
            if (!snapshot.hasData) {
              return Center(
                child: CircularProgressIndicator(),
              );
            }
            return ListView.builder(
              itemCount: snapshot.data.length,
              itemBuilder: (context, i) {
                final folder = snapshot.data[i];
                return Dismissible(
                  onDismissed: (direction) {
                    snapshot.data.removeAt(i); // to avoid weird future builder issue with dismissible
                    Provider.of<FoldersProvider>(context, listen: false).deleteFolder(folder.id);
                  },
                  background: Card(
                    margin: EdgeInsets.symmetric(vertical: 8),
                    elevation: 1,
                    child: Container(
                      alignment: AlignmentDirectional.centerStart,
                      color: Theme.of(context).accentColor,
                      child: Padding(
                        padding: EdgeInsets.fromLTRB(15.0, 0.0, 0.0, 0.0),
                        child: Icon(
                          Icons.delete,
                          color: Colors.white,
                        ),
                      ),
                    ),
                  ),
                  key: UniqueKey(),
                  direction: DismissDirection.startToEnd,
                  child: Card(
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(15),
                    ),
                    margin: EdgeInsets.symmetric(vertical: 5, horizontal: 10),
                    elevation: 1,
                    child: ListTile(
                      title: Text(folder.folderName),
                      leading: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: <Widget>[
                          Icon(
                            Icons.folder,
                            color: Theme.of(context).accentColor,
                          ),
                        ],
                      ),
                      subtitle: folder.numberOfLists != 1
                          ? Text('${folder.numberOfLists} items')
                          : Text('${folder.numberOfLists} item'),
                      onTap: () {},                  
                    ),
                  ),
                );
              },
            );
          },
        );
    

    低头看,它奏效了!对代码的最小更改:)

    【讨论】:

      【解决方案2】:

      通过不使用 FutureBuilder 并在查询完成后调用 setState 找到了解决方法。

      该状态现在包含一个 List&lt;Task&gt;,而不是 Future&lt;List&lt;Task&gt;&gt;,它被声明为 null。

      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 只是根据状态中存储的 List 进行构建。

      @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 更好,以防在小部件再次构建之前必须完成 Future。

      【讨论】:

      • 这很容易,我正在使用地图,只是做了 snapshot.data.remove(e)
      猜你喜欢
      • 2020-07-11
      • 1970-01-01
      • 2014-01-07
      • 2019-08-17
      • 2016-11-23
      • 2019-02-18
      • 2015-05-16
      • 2017-10-14
      • 2012-06-01
      相关资源
      最近更新 更多