【问题标题】:How to reload the page whenever the page is on screen - flutter每当页面在屏幕上时如何重新加载页面 - 颤动
【发布时间】:2020-09-05 19:23:24
【问题描述】:

每次页面在屏幕上可见时,是否有任何回调可用?在 ios 中有一些委托方法,例如 viewWillAppearviewDidAppearviewDidload

我想在特定页面出现在屏幕上时调用 API。

注意:我不是在询问应用程序的状态,例如前台、后台、暂停、恢复。

谢谢!

【问题讨论】:

    标签: flutter dart flutter-layout


    【解决方案1】:

    特别针对您的问题:

    使用initState,但请注意,您不能在initState 中使用async 调用,因为它会在初始化小部件之前调用,就像名称的意思一样。如果您想在创建 UI 后做某事didChangeDependencies 很棒。但千万不要在不使用FutureBuilderStreamBuilder的情况下使用build()

    演示的简单示例:

    import 'dart:convert';
    
    import 'package:flutter/material.dart';
    import 'package:http/http.dart' as http;
    
    void main() {
      runApp(MaterialApp(home: ExampleScreen()));
    }
    
    class ExampleScreen extends StatefulWidget {
      ExampleScreen({Key key}) : super(key: key);
    
      @override
      _ExampleScreenState createState() => _ExampleScreenState();
    }
    
    class _ExampleScreenState extends State<ExampleScreen> {
      List data = [];
      bool isLoading = true;
    
      void fetchData() async {
        final res = await http.get("https://jsonplaceholder.typicode.com/users");
        data = json.decode(res.body);
        setState(() => isLoading = false);
      }
    
      // this method invokes only when new route push to navigator
      @override
      void initState() {
        super.initState();
        fetchData();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: isLoading
                ? CircularProgressIndicator()
                : Text(data?.toString() ?? ""),
          ),
        );
      }
    }
    

    StatefulWidgetState类的一些生命周期方法:

    initState():

    描述此小部件所代表的用户界面部分。

    框架在多种不同情况下调用此方法:

    After calling initState.
    After calling didUpdateWidget.
    After receiving a call to setState.
    After a dependency of this State object changes (e.g., an InheritedWidget referenced by the previous build changes).
    After calling deactivate and then reinserting the State object into the tree at another location.
    

    框架将这个小部件下面的子树替换为小部件 此方法返回,通过更新现有子树或通过 移除子树并膨胀一个新的子树,这取决于是否 此方法返回的小部件可以更新现有的根 子树,通过调用 Widget.canUpdate 确定。 Read more

    didChangeDependencies():

    在此 State 对象的依赖项发生更改时调用。

    例如,如果之前的 build 调用引用了一个 InheritedWidget 后来改变了,框架会调用这个 通知此对象有关更改的方法。

    initState 之后也立即调用此方法。这是安全的 从此方法调用BuildContext.dependOnInheritedWidgetOfExactTypeRead more

    build()(无状态小部件)

    描述此小部件所代表的用户界面部分。

    当这个小部件被插入到 树在给定的 BuildContext 中以及当这个小部件的依赖关系 更改(例如,此小部件引用的 InheritedWidget 更改)。 Read more

    didUpdateWidget(Widget oldWidget):

    在小部件配置更改时调用。

    如果父小部件重建并请求该位置在 树更新以显示具有相同 runtimeType 的新小部件和 Widget.key,框架会更新这个的widget属性 State 对象引用新的小部件,然后调用此方法 前一个小部件作为参数。 Read more

    【讨论】:

    • 我想在页面加载时自动调用 api 来渲染小部件,你能告诉我哪种方法是正确的选择吗?我确定 initState() 和 build() 不是调用 api 来呈现屏幕的正确位置 - 我有 3 个不同的 api 被调用来呈现我的屏幕部分,我有三种不同类型的小部件要在屏幕上呈现。
    • @Bala 我更新了答案。我通常使用initState,但请注意您不能在initState 中使用异步调用。如果你想在创建 UI 之后做某事didChangeDependencies 就可以了
    • @Blasanka 只要initState 本身不是async,您就可以在initState 中使用async 调用,因此您可以使用FutureBuilder 重绘该async 的用户界面打电话。
    • @Doc 我认识Doc先生。可能是我没有解释清楚。谢谢指出!
    • @Blasanka 问题是第一次调用 didChangeDependencies 或 initState,当我转到其他屏幕并再次来到此屏幕时,这些方法被调用/触发。
    【解决方案2】:

    有些小部件是无状态的,有些是有状态的。如果它是无状态小部件,则只有值可以更改,但 UI 更改不会呈现。

    有状态小部件的方式相同,它的值和 UI 都会发生变化。

    现在,将研究方法。

    1. initState():这是在创建小部件时但在构造函数调用之后调用的第一个方法。

    @override
    void initState() {
       // TODO: implement initState
       super.initState();
    }
    
    1. didChangeDependecies() - 在此 State 对象的依赖项发生更改时调用。在 initState 方法之后立即调用。

    @override
      void didChangeDependencies() {
        super.didChangeDependencies();
      }
    
    1. didUpdateWidget() - 只要小部件配置发生更改,就会调用它。框架总是在 didUpdateWidget 之后调用 build

    @override
    void didUpdateWidget (
       covariant Scaffold oldWidget
    )
    
    1. setState() - 当 State 对象的内部状态想要改变时,需要在 setState 方法中调用它。

     setState(() {});
    
    1. dispose() - 当此对象从树中永久移除时调用。

    @override
      void dispose() {
        // TODO: implement dispose
        super.dispose();
      }
    

    【讨论】:

      【解决方案3】:

      如果您想进行 API 调用,那么您必须(或确实应该)使用 StatefulWidget。

      遍历它,假设您的有状态小部件收到了一些它需要进行 API 调用的 id。

      每次您的小部件收到一个新 ID(包括第一次)时,您都需要使用该 ID 进行新的 API 调用。

      所以使用didUpdateWidget 来检查id 是否发生变化,如果发生变化(就像小部件出现时所做的那样,因为旧的id 将是null)然后进行新的API 调用(也设置适当的加载和错误状态!)

      class MyWidget extends StatefulWidget {
        Suggestions({Key key, this.someId}) : super(key: key);
      
        String someId
      
        @override
        State<StatefulWidget> createState() => MyWidgetState();
      
      }
      
      class MyWidgetState extends State<MyWidget> {
      
        dynamic data;
        Error err;
        bool loading;
      
        @override
        Widget build(BuildContext context) {
           if(loading) return Loader();
           if(err) return SomeErrorMessage(err);
           return SomeOtherStateLessWidget(data);
        }
      
      
        @override
        void didUpdateWidget(covariant MyWidget oldWidget) { 
          super.didUpdateWidget(oldWidget);   
      
          // id changed in the widget, I need to make a new API call
          if(oldWidget.id != widget.id) update();
        }
      
        update() async {
          // set loading and reset error
          setState(() => {
            loading = true,
            err = null
          });
      
          try {
              // make the call
              someData = await apiCall(widget.id);
              // set the state
              setState(() => data = someData)
          } catch(e) {
              // oops an error happened
              setState(() => err = e)
          }
      
          // now we're not loading anymore
          setState(() => loading = false);
        }
      
      }
      

      我是 Flutter 的新手(从字面上看,这个周末才开始使用它),但它本质上复制了 React 范例,如果这对你有帮助的话。

      个人喜好,我非常喜欢这种方法而不是使用FutureBuilder(现在,就像我说的,我是全新的)。逻辑更容易推理(对我来说)。

      【讨论】:

      • 是的,您的代码正是FutureBuilder 逻辑。起初理解它可能会很棘手。您还可以将setState 调用减少为:try{ resp=await apiCall(); data=resp;}catch(e){err = e;} setState(()=&gt;loading = false;); 保存的每个小部件重绘都将确保 UI 流畅。
      • @Doc 你是对的。我有一种感觉,随着时间的推移,flutter 会以与 React 相同的方式发展(它使用完全相同的概念,它们在概念上的相同程度几乎是愚蠢的)因此不会改变渲染结果的 setState 调用,呃构建,赢了其实没关系。保持你的组件/小部件非常小 - 单一责任原则 - 不要创建大量的渲染/构建函数,你的应用程序将是高性能的(教程代码对于具有史诗数量的逻辑的数百行构建函数来说是最糟糕的)。
      【解决方案4】:

      您不需要StatefulWidget 来在每次显示屏幕时调用 api。

      在以下示例代码中,按浮动操作按钮导航到 api 调用屏幕,使用返回箭头返回,再次按下浮动操作按钮导航到 api 页面。

      每次访问该页面时都会自动调用api。

      import 'dart:async';
      
      import 'package:flutter/material.dart';
      
      main() => runApp(MaterialApp(home: HomePage()));
      
      class HomePage extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          return Scaffold(
            appBar: AppBar(),
            floatingActionButton: FloatingActionButton(
              onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => ApiCaller())),
            ),
          );
        }
      }
      
      class ApiCaller extends StatelessWidget {
        static int counter = 0;
      
        Future<String> apiCallLogic() async {
          print("Api Called ${++counter} time(s)");
          await Future.delayed(Duration(seconds: 2));
          return Future.value("Hello World");
        }
      
        @override
        Widget build(BuildContext context) {
          return Scaffold(
            appBar: AppBar(
              title: Text('Api Call Count: $counter'),
            ),
            body: FutureBuilder(
              future: apiCallLogic(),
              builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
                if (snapshot.connectionState == ConnectionState.waiting) return const CircularProgressIndicator();
      
                if (snapshot.hasData)
                  return Text('${snapshot.data}');
                else
                  return const Text('Some error happened');
              },
            ),
          );
        }
      }
      

      这是零样板的简单代码。

      【讨论】:

      • 谢谢@Doc,这是我所期望的。感谢您的实现逻辑方式。
      • @Doc 在成功调用 api 后在同一活动上如何在不移动或访问同一活动的情况下刷新活动。
      • @s.j 在将类更改为有状态小部件后调用 setState。
      【解决方案5】:

      最简单的方法是使用need_resume

      1.将此添加到您的包的 pubspec.yaml 文件中:

      dependencies:
        need_resume: ^1.0.4
      

      2.使用ResumableState 类型而不是State 为有状态小部件创建状态类

      class HomeScreen extends StatefulWidget {
          @override
          HomeScreenState createState() => HomeScreenState();
      }
      
      class HomeScreenState extends ResumableState<HomeScreen> {
          @override
          void onReady() {
              // Implement your code inside here
      
              print('HomeScreen is ready!');
          }
      
          @override
          void onResume() {
              // Implement your code inside here
      
              print('HomeScreen is resumed!');
          }
      
          @override
          void onPause() {
              // Implement your code inside here
      
              print('HomeScreen is paused!');
          }
      
      
          @override
          Widget build(BuildContext context) {
              return Scaffold(
                  body: Center(
                      child: RaisedButton(
                          child: Text('Go to Another Screen'),
                          onPressed: () {
                             print("hi");
                          },
                      ),
                  ),
              );
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-11-05
        • 1970-01-01
        • 1970-01-01
        • 2018-05-22
        • 1970-01-01
        • 2014-10-04
        • 1970-01-01
        • 2019-02-22
        相关资源
        最近更新 更多