阅读 30

Flutter Future与FutureBuilder异步操作(3.3)

今天给大家介绍一下Future和FutureBuilder异步请求数据,以及FutureBuilder如何进行不必要的重绘~

Future

要了解Future,最应该想到的是:什么是Future?
Future表示在接下来的某个时间的值或错误,借助Future我们可以在Flutter实现异步操作。

Future的2中状态:

  • pending - 执行中
  • completed - 执行结束

Future是dart:async包中的一个类,使用它时需要导入dart:async包

Future常用用法:

  • Future.then(Future的值,{Future异常返回})
  • Future.whenComplete()
  • Future.delayed()
  • 结合async,await

Future.then(Future的值,{Future异常返回})

 /**
   * Register callbacks to be called when this future completes.
   *
   * When this future completes with a value,
   * the [onValue] callback will be called with that value.
   * If this future is already completed, the callback will not be called
   * immediately, but will be scheduled in a later microtask.
   *
   * If [onError] is provided, and this future completes with an error,
   * the `onError` callback is called with that error and its stack trace.
   * The `onError` callback must accept either one argument or two arguments
   * where the latter is a [StackTrace].
   * If `onError` accepts two arguments,
   * it is called with both the error and the stack trace,
   * otherwise it is called with just the error object.
   * The `onError` callback must return a value or future that can be used
   * to complete the returned future, so it must be something assignable to
   * `FutureOr<R>`.
   *
   * Returns a new [Future]
   * which is completed with the result of the call to `onValue`
   * (if this future completes with a value)
   * or to `onError` (if this future completes with an error).
   *
   * If the invoked callback throws,
   * the returned future is completed with the thrown error
   * and a stack trace for the error.
   * In the case of `onError`,
   * if the exception thrown is `identical` to the error argument to `onError`,
   * the throw is considered a rethrow,
   * and the original stack trace is used instead.
   *
   * If the callback returns a [Future],
   * the future returned by `then` will be completed with
   * the same result as the future returned by the callback.
   *
   * If [onError] is not given, and this future completes with an error,
   * the error is forwarded directly to the returned future.
   *
   * In most cases, it is more readable to use [catchError] separately, possibly
   * with a `test` parameter, instead of handling both value and error in a
   * single [then] call.
   *
   * Note that futures don't delay reporting of errors until listeners are
   * added. If the first `then` or `catchError` call happens after this future
   * has completed with an error then the error is reported as unhandled error.
   * See the description on [Future].
   */
  Future<R> then<R>(FutureOr<R> onValue(T value), {Function? onError});
复制代码

Future参数:

  • 参数一:返回的是Future的值(网络请求返回值)
  • 参数二:可选参数,返回的是Future异常

使用上一章:Flutter Http网络请求(3.2)的请求网络数据代码来调试:

 /**
   *   异步请求网络数据
   */
  Future<ChickenSoupBean> _HttpGet() async {
    final responce = await http.get("https://v1.hitokoto.cn/");
    //UTF-8防止乱码
    Utf8Codec utf8codec = Utf8Codec();
    //序列化返回数据
    final decode = json.decode(utf8codec.decode(responce.bodyBytes));
    return ChickenSoupBean.fromJson(decode);
  }


		 InkWell(
                onTap: () {
                  _HttpGet().then((ChickenSoupBean value) {
                    setState(() {
                      _mValue = "\n${value.hitokoto}\n---${value.fromWho}";
                      print("_HttpGetvalue:${value}");
                    });
                  }, onError: (onErr) {
                    print("onError错误${onErr}");
                  }).catchError((onCatchError) {
                    print("catchErrorr错误${onCatchError}");
                  });
                },
             child: ..... ,
             }
复制代码

Bean类代码

补充:

当执行onError和catchError同时存在时只会调用onError代码

Future.whenComplete()

Future.whenComplete相当于Android中的try-catch,try-catch有个finally代码块,Future和Android中的finally模块使用一样.

 _HttpGet().then((ChickenSoupBean value) {
                    setState(() {
                      _mValue = "\n${value.hitokoto}\n---${value.fromWho}";
                      print("_HttpGetvalue:${value}");
                    });
                  }, onError: (onErr) {
                    print("onError错误${onErr}");
                  }).catchError((onCatchError) {
                    print("catchErrorr错误${onCatchError}");
                  }).whenComplete(() => {
                        print("代码执行完啦!!"),
                      });
复制代码

在这里插入图片描述
当运行完成的时候就会执行这块代码

Future.delayed

Future.delayed是延迟操作

 Future.delayed(new Duration(seconds: 2)).then((value) {
     print("Future.delayed:我是老大,但我在两秒之后执行的");
  });
  print("Future.delayed:我是老二,我先执行");
复制代码

在这里插入图片描述
使用还是非常简单,就不解释了,不明白的地方请您在评论区留言哦~

结合async,await

  • Future: 对象表示异步操作的结果,我们通常通过then()来处理返回的结果
  • async: 用于标明方法是一个异步方法,其返回值类型是Future类型
  • await: 用来等待耗时操作的返回结果,这个操作会阻塞到后面的代码

网络请求都是耗时操作,Flutter又是单线程的,所以在网络请求是一定要使用async关键字来标识该方法是异步方法

FutureBuilder

先上代码和效果在一步步解释吧:

代码:


 FutureBuilder<ChickenSoupBean>(
            //异步请求的接口
            future: _HttpGet(),
            builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
              //判断snapshot连接状态
              if(snapshot.connectionState == ConnectionState.done ){
             	 /**
                 * 判断数据是否返回成功
                 * 也可以通过snapshot.hashData 判断数据是否返回成功
                 */
                if (snapshot.hasError) {
                  return Text("HasError");
                }else{
                  return Text("${snapshot.data.hitokoto}\n----${snapshot.data.fromWho}");
                }
              }else{
                 return CircularProgressIndicator();
              }

            },
          ),
复制代码

Bean类代码

请求方法:

 /**
   *   异步请求网络数据
   */
  Future<ChickenSoupBean> _HttpGet() async {
    final responce = await http.get("https://v1.hitokoto.cn/");
    //UTF-8防止乱码
    Utf8Codec utf8codec = Utf8Codec();
    //序列化返回数据
    final decode = json.decode(utf8codec.decode(responce.bodyBytes));
    return ChickenSoupBean.fromJson(decode);
  }
复制代码

效果:

在这里插入图片描述
通过效果图可以看出:

  • 切换的时候别切网络数据没请求到之前有一个进度条的展示
  • 当刷新时,并且网络数据没请求到之前有一个进度条的展示
FutureBuilder参数类型说明
futureFuture通常情况都是我们的异步方法
initialDataT(泛型),简单理解就是初始数据,应该不是很常用
builderAsyncWidgetBuilder返回两个参数(BuildContext, AsyncSnapshot),主要用来获取请求数据以及异常处理等

builder中返回两个参数:

  • BuildContext context 上下文
  • AsyncSnapshot< dynamic > snapshot 这个参数封装了connectionState(连接状态),data(网络请求返回数据),error(异常信息)

补充:

AsyncSnapshot< dynamic > snapshot有四种状态:

  • none :当前未连接到任何异步计算。
  • waiting : 连接成功等待交互
  • active :正在交互中,可以理解为正在返回数据
  • done :交互完成,可以理解为数据返回完成,此时如果是正确的返回则data就有数据了

通常情况只需要判断交互完成之后的操作就可以了

数据返回之后FutureBuilder就会自动重新刷新build完成数据的加载,

讲到这里,上面的效果就很好写了

只需要在builder方法中判断是否连接成功,然后在判断是否获取到数据,若没获取到数据则显示一个进度条,获取到数据则显示数据即可.

讲了这么多,可能我讲的不是很明白,还是大家自己动手写一遍记忆会更加深刻,老化说的好看千遍不如手写一遍嘛~

FutureBuilder如何进行不必要的UI重绘

出现了什么问题呢?为什么要进行不必要的重绘?

解:因为项目中有很多地方要使用setState()来重新绘制build,达到数据更新的效果,所以在FutureBuilder()上也会重新绘制,致使网络数据不停地更新,然而更新的还是同样的数据,不禁浪费流量用户体验也不好!

解决思路:
只需要让请求网络数据的代码执行一次,然后重复调用即可,所以需要将请求数据的代码放到initState()中,这样的话他只在页面初始化的时候请求一次.

@override
  void initState() {
    _httpGet= _HttpGet();
  }

 FutureBuilder<ChickenSoupBean>(
   future : _httpGet,
   builder : ......,
)
复制代码

举例:
在页面添加一个floatingActionButton按钮()用来刷新build

floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {

          });
        },
        child: Text("刷新"),
      ),
      
FutureBuilder<ChickenSoupBean>(
   future : _httpGet,
   builder : ......,
   )
   
   /**
   *   异步请求网络数据
   */
  Future<ChickenSoupBean> _HttpGet() async {
      var   responce =  await http.get("https://v1.hitokoto.cn/");
    //UTF-8防止乱码
    Utf8Codec utf8codec = Utf8Codec();
    //序列化返回数据
    final decode = json.decode(utf8codec.decode(responce.bodyBytes));
   return  ChickenSoupBean.fromJson(decode);

  }
复制代码

现在FutureBuilder()中的future是使用的initState()初始化的,来看看效果吧:
在这里插入图片描述
可以看出,我点击刷新按钮,并不会重新请求接口!

在来看看直接调用_HttpGet()网络请求的方法:

floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {

          });
        },
        child: Text("刷新"),
      ),
      
FutureBuilder<ChickenSoupBean>(
	future: _HttpGet(),
	builder: .....,
)

 /**
   *   异步请求网络数据
   */
  Future<ChickenSoupBean> _HttpGet() async {
      var   responce =  await http.get("https://v1.hitokoto.cn/");
    //UTF-8防止乱码
    Utf8Codec utf8codec = Utf8Codec();
    //序列化返回数据
    final decode = json.decode(utf8codec.decode(responce.bodyBytes));
   return  ChickenSoupBean.fromJson(decode);

  }
复制代码

在这里插入图片描述
可以看出,每次点击刷新按钮网络数据都会请求,

因为我这里只获取的是一句话的接口,所以看到请求还是很快的,通常情况下得到的数据是不会变的,而且是一个List,所以还是建议大家这里优化一下

完整代码

上一章:Flutter Http网络请求(3.2)

下一章:Flutter shared_preferences本地存储(3.4)

原创不易,您的点赞就是对我最大的支持,留下您的点赞吧~

文章分类
Android
文章标签