在FutureBuilder中设置提供者的价值

时间:2019-05-29 11:02:46

标签: flutter dart

我有一个小部件,可向返回地图的api发出请求。我想做的是每次加载窗口小部件并将列表保存到appState.myList时都不会发出相同的请求,但是当我在appState.myList = snapshot.data;中执行此FutureBuilder时,出现以下错误:

flutter: ══╡ EXCEPTION CAUGHT BY FOUNDATION LIBRARY ╞════════════════════════════════════════════════════════ flutter: The following assertion was thrown while dispatching notifications for MySchedule: flutter: setState() or markNeedsBuild() called during build. flutter: This ChangeNotifierProvider<MySchedule> widget cannot be marked as needing to build because the flutter: framework is already in the process of building widgets. A widget can be marked as needing to be flutter: built during the build phase only if one of its ancestors is currently building. ...

sun.dart文件

class Sun extends StatelessWidget {
  Widget build(BuildContext context) {
    final appState = Provider.of<MySchedule>(context);
    var db = PostDB();

    Widget listBuild(appState) {
      final list = appState.myList;
      return ListView.builder(
        itemCount: list.length,
        itemBuilder: (context, index) {
          return ListTile(title: Text(list[index].title));
        },
      );
    }

    Widget futureBuild(appState) {
      return FutureBuilder(
        future: db.getPosts(),
        builder: (BuildContext context, AsyncSnapshot snapshot) {
          if (snapshot.hasData) {
            // appState.myList = snapshot.data;
            return ListView.builder(
              itemCount: snapshot.data.length,
              itemBuilder: (context, index) {
                return ListTile(title: Text(snapshot.data[index].title));
              },
            );
          } else if (snapshot.hasError) {
            return Text("${snapshot.error}");
          }
          return Center(
            child: CircularProgressIndicator(),
          );
        },
      );
    }

    return Scaffold(
        body: appState.myList != null
            ? listBuild(appState)
            : futureBuild(appState));
  }
}

postService.dart文件

class PostDB {
  var isLoading = false;

  Future<List<Postmodel>> getPosts() async {
    isLoading = true;
    final response =
        await http.get("https://jsonplaceholder.typicode.com/posts");

    if (response.statusCode == 200) {
      isLoading = false;
      return (json.decode(response.body) as List)
          .map((data) => Postmodel.fromJson(data))
          .toList();
    } else {
      throw Exception('Failed to load posts');
    }
  }
}

我知道myList会调用notifyListeners(),这就是导致错误的原因。希望我说对了。如果是这样,如何设置appState.myList并在应用中使用而不会出现上述错误?

import 'package:flutter/foundation.dart';
import 'package:myflutter/models/post-model.dart';

class MySchedule with ChangeNotifier {
  List<Postmodel> _myList;

  List<Postmodel> get myList => _myList;

  set myList(List<Postmodel> newValue) {
    _myList = newValue;
    notifyListeners();
  }
}

4 个答案:

答案 0 :(得分:0)

使用提供程序时,我遇到了与您类似的问题。我的解决方案是在获取数据时添加WidgetsBinding.instance.addPostFrameCallback()

答案 1 :(得分:0)

只需从代码中删除notifyListeners();。我遇到此错误,这就是我要解决的问题。

答案 2 :(得分:-1)

如果您不依赖使用MySchedule(从您提供的内容中看不到原因),则可以简单地使用async软件包中的AsyncMemoizer(根据标准库),它将仅在将来运行一次,而不是在每次小部件重建时运行。

StatelessWidget中,您可以将备忘录存储为final变量,然后使用runOnce

import 'package:async/async.dart';
...
final AsyncMemoizer<List<Postmodel>> memoizer = AsyncMemoizer();

当您无法调用build时,您也会摆脱条件setState的功能(当您在正在监听的由notifyListeners函数访问的)。无论如何,它也不是必需的,而且很可能是不良的设计。此外,正如Rousselet所指出的,build会丢失状态,因此无论如何在再次创建StatelessWidget时仍要重新加载数据。我最初只是想分享memoizer的基本要点,但是,没有理由对此进行调整。因此,在这种情况下,您应该使用AsyncMemoizer

StatefulWidget

当您想在多个窗口小部件中使用列表时,建议您在class _SunState extends State<Sun> { final AsyncMemoizer<List<Postmodel>> memoizer; final PostDB db; @override void initState() { memoizer = AsyncMemoizer(); db = PostDB(); super.initState(); } Future<List<Postmodel>> getPosts() => db.getPosts(); @override Widget build(BuildContext context) { return Scaffold( body: FutureBuilder( future: memoizer.runOnce(getPosts), builder: (BuildContext context, AsyncSnapshot<List<Postmodel>> snapshot) { if (snapshot.hasData) { return ListView.builder( itemCount: snapshot.data.length, itemBuilder: (BuildContext context, int index) { return ListTile(title: Text(snapshot.data[index].title)); }); } if (snapshot.hasError) return Text('${snapshot.error}'); return Center(child: const CircularProgressIndicator()); }, )); } } 类中使用Completer,并在提供程序中存储该类的实例。

您可能希望调整以下代码的确切实现,但是,这应该使您了解如何实现所需的代码。

PostDB

class PostDB { Completer<List<Postmodel>> _completer; /// Return `null` if [getPosts] has not yet been called. bool get isLoading => _completer == null ? null : !_completer.isCompleted; /// This will allow you to call [getPosts] multiple times /// by supplying [reload], which will reassign [_completer]. Future<List<Postmodel>> getPosts([bool reload = false]) { if (_completer == null || reload) { _completer = Completer(); _completer.complete(_getPosts()); } return _completer.future; } Future<List<Postmodel>> _getPosts() async { final response = await http.get("https://jsonplaceholder.typicode.com/posts"); if (response.statusCode == 200) { return (json.decode(response.body) as List).map((data) => Postmodel.fromJson(data)).toList(); } else { throw Exception('Failed to load posts'); } } } class MySchedule { PostDB db; } class Sun extends StatelessWidget { @override Widget build(BuildContext context) { final appState = Provider.of<MySchedule>(context); return Scaffold( body: FutureBuilder( future: appState.db.getPosts(), builder: (BuildContext context, AsyncSnapshot<List<Postmodel>> snapshot) { if (snapshot.hasData) { return ListView.builder( itemCount: snapshot.data.length, itemBuilder: (BuildContext context, int index) { return ListTile(title: Text(snapshot.data[index].title)); }); } if (snapshot.hasError) return Text('${snapshot.error}'); return Center(child: const CircularProgressIndicator()); }, )); } } 允许您返回Completer,即使请求已经完成,也可以使函数调用与第一次和后续调用相同(只是数据仅加载一次)。

已对该代码进行了调整,以更好地满足您的需求,但是,看起来好像我永远不会在自己的应用程序中包含这些内容。您现在可能已经有足够的信息,并且 起作用了,这就是为什么这应该足够的原因。任何其他更改都必须由您自己完成,因为只有您知道您的应用程序。

答案 3 :(得分:-1)

出现该异常是因为您正在从其后代同步修改窗口小部件。

这很糟糕,因为它可能导致小部件树不一致。一些小部件。可能会使用突变前的值构建小部件,而其他人可能正在使用已突变的值。

解决方案是消除不一致之处。使用ChangeNotifierProvider,通常有两种情况:

  • ChangeNotifier上执行的变异总是在与创建ChangeNotifier的变异相同的 build 中完成。

在这种情况下,您可以直接从ChangeNotifier的构造函数进行调用:

class MyNotifier with ChangeNotifier {
  MyNotifier() {
    // TODO: start some request
  }
}
  • 执行的更改可以“懒惰地”发生(通常在更改页面之后)。

在这种情况下,您应该将突变包装在addPostFrameCallbackFuture.microtask中:

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  MyNotifier notifier;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final notifier = Provider.of<MyNotifier>(context);

    if (this.notifier != notifier) {
      this.notifier = notifier;
      Future.microtask(() => notifier.doSomeHttpCall());
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}