通过API调用使用FutureBuilder的无限循环

时间:2019-06-03 09:48:34

标签: flutter

我正在尝试使用API​​的结果填充ListView。从共享首选项中检索值之后,必须进行API调用。但是在执行时,我的API调用函数会运行无限循环,并且UI不会呈现。我通过调试语句跟踪了此行为。

“未来”构建器构建UI时应显示的圆形指示器也未显示。

我该如何解决?

我的代码:



class _MyHomePageState extends State<MyHomePage>{
  @override MyHomePage get widget => super.widget;

  String userID = "";
  String authID = "";


  //Retrieving values from Shared Preferences

  Future<List<String>> loadData() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();

    List<String> l= new List<String>();

    if(prefs.getString("ID") == null){
      l.add("null");
    }
    else{
      l.add(prefs.getString("ID"));
    }

    if(prefs.getString("authID") == null){
      l.add("null");
    }
    else{
      l.add(prefs.getString("authID"));
    }

    return l;
  }

//Setting values retrieved from Shared Pref
  setData() async{
    await loadData().then((value) {
      setState(() {
        userID = value[0];
        print('the user ID is' + userID);
        authID = value[1];
        print('the authID is' + authID);
      });
     // getAllTasks(userID, authID);
    });
    print("Set data execution completed ");
  }


  //FUNCTION to use values from Shared Pref and make API Call 
  Future<List<Task>> getAllTasks() async{

     await setData();
    //Waiting for Set Data to complete

    print('Ive have retrived the values ' + userID + authID );

    List<Task> taskList;

    await getTasks(userID, authID, "for_me").then((value){

      final json = value;
      if(json!="Error"){

        Tasks tasks = tasksFromJson(json); //of Class Tasks
        taskList = tasks.tasks;  //getting the list of tasks from class
      }
    });

    if(taskList != null) return taskList;
    else {
      print('Tasklist was null ');
      throw new Exception('Failed to load data ');
    }

  }

  @override
  void initState() {
    super.initState();
  }

 @override
  Widget build(BuildContext context){

   _signedOut(){
     widget.onSignedOut();
   }

//To CREATE LIST VIEW 
   Widget createTasksListView(BuildContext context, AsyncSnapshot snapshot) {
     var values = snapshot.data;
     return ListView.builder(
       itemCount: values == null ? 0 : values.length,
       itemBuilder: (BuildContext context, int index) {

         return values.isNotEmpty ? Ink(....
         ) : CircularProgressIndicator();
       },
     );
   }


//MY COLUMN VIEW 

   Column cardsView = Column(
      children: <Widget>[
      ....
        Expanded(
          child: FutureBuilder(
              future: getAllTasks(),
              initialData: [],
              builder: (context, snapshot) {
                return createTasksListView(context, snapshot);
              }),
        ),
      ],
   );


   return Scaffold(

     body:  cardsView,
     );
 }
}

而不是一次被调用...我的setData函数被重复调用..我该如何解决..请帮助

3 个答案:

答案 0 :(得分:1)

您将在每次重新构建窗口小部件时创建Future对象。而且,由于您是在setState方法内调用setData,因此它将以递归方式触发重建。

要解决此问题,您必须保留对Future对象的引用。并将该引用用于FutureBuilder,则可以理解它是以前使用过的引用。

例如:

Future<List<Task>> _tasks;
@override
  void initState() {
    _tasks = getAllTasks();
    super.initState();
  }

然后在小部件树中像这样使用它:

Expanded(
          child: FutureBuilder(
              future: _tasks,
              initialData: [],
              builder: (context, snapshot) {
                return createTasksListView(context, snapshot);
              }),
        ),

答案 1 :(得分:0)

您需要将Future保存在状态中,因为进行getAllTasks()会触发每个构建回调上的调用。

initState中:

this.getAllTasksFuture = getAllTasks();

然后,您将在FutureBuilder中使用此Future属性。

答案 2 :(得分:0)

Flutter提供给我们的FutureBuilder小部件可根据某些将来的状态来创建小部件,每次重建时都会不断刷新该未来! 每次我们调用setState时,FutureBuilder都会再次经历其整个生命周期!

一个选项是记住

简而言之,

记忆化是缓存一个函数的返回值,并在再次调用该函数时重新使用它。 记忆主要用于函数式语言中,在这些语言中,函数是确定性的(对于相同的输入,它们总是返回相同的输出),但是我们可以在此处针对问题使用简单的记忆,以确保FutureBuilder始终接收相同的未来实例。

为此,我们将使用 Dart的AsyncMemoizer 。 这个备忘录器正是我们想要的!它需要一个异步函数,第一次调用它,然后缓存其结果。对于此函数的所有后续调用,记忆器将返回相同的先前计算的未来。 因此,要解决我们的问题,我们首先在窗口小部件中创建一个AsyncMemoizer实例:

final AsyncMemoizer _memoizer = AsyncMemoizer();

注意:您不应该在StatelessWidget内实例化备忘录,因为Flutter在每次重建时都会丢弃StatelessWidgets,您应该在StatefulWidget或可以持久保存的地方实例化它。 然后,我们将修改我们的 _fetchData函数以使用该备忘录:

_fetchData() {
  return this._memoizer.runOnce(() async {
  await Future.delayed(Duration(seconds: 2));
  return 'REMOTE DATA';
   });
}

注意:您必须仅将runOnce()包裹在主体内,而不是将函数调用包裹在内

特别感谢AbdulRahman AlHamali。