Flutter futureBuilder的异步回调教程

916 阅读5分钟

在很多情况下,我们需要异步地构建一个小部件,以反映应用程序或数据的正确状态。一个常见的例子是从REST端点获取数据。

在本教程中,我们将使用Dart和Flutter处理这种类型的请求。Dart是一种单线程语言,利用事件循环来运行异步任务。然而,Flutter中的构建方法是同步的。

让我们开始吧!

Dart的事件循环

一旦有人打开一个应用程序,许多不同的事件会以不可预测的顺序发生,直到应用程序被关闭。每次事件发生时,它都会进入一个队列并等待处理。Dart事件循环检索队列顶部的事件,对其进行处理,并触发回调,直到队列中所有的事件都完成。

Dart中的FutureStream 以及asyncawait 关键字都是基于这个简单的循环,使得异步编程成为可能。在下面的代码片段中,用户输入是使用回调来响应按钮部件上的交互。

ElevatedButton(
  child: Text("Hello Team"),
  onPressed: () {
    const url = 'https://majidhajian.com';
    final myFuture = http.get(url);
    myFuture.then((response) {
      // (3)
      if (response.statusCode == 200) {
        print('Success!');
      }
    });
  },
)

ElevatedButton widget

ElevatedButton widget提供了方便的参数来响应一个被按下的按钮。一旦onPressed 事件被触发,它就在队列中等待。当事件循环到达这个事件时,匿名函数将被执行,并且这个过程继续。

构建Flutter小部件

现在我们已经了解了异步编程在Dart中的工作原理,我们明白了Flutter背后的秘密。现在,我们可以处理future 请求,并构建我们的 Flutter 小部件。

由于Flutter中的build 方法是同步运行的,我们需要找到一种方法来确保应用程序将根据未来收到的数据来构建小部件。

StatefulWidget

一种方法是使用 [StatefulWidget](http://​​https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html)并在获得信息的同时设置状态。

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<String> fetchName() async {
  final Uri uri = Uri.https('maijdhajian.com', '/getRandonName');
  final http.Response name = await http.get(uri);
  return jsonDecode(name.body);
 }
class MyFutureWidget extends StatefulWidget {
  @override
  _MyFutureWidgetState createState() => _MyFutureWidgetState();
}
class _MyFutureWidgetState extends State<MyFutureWidget> {
  String? value;
  @override
  void initState() {
    super.initState();

    // fetchName function is a asynchronously to GET http data
    fetchName().then((result) {
      // Once we receive our name we trigger rebuild.
      setState(() {
        value = result;
      });
    });
  }
  @override
  Widget build(BuildContext context) {
    // When value is null show loading indicator.
    if (value == null) {
      return const CircularProgressIndicator();
    }
    return Text('Fetched value: $value');
  }
}

在这个例子中,你可能已经注意到,我们没有正确处理可能出现的异常,我们可以通过添加一个error 变量来解决这个问题。上面的过程会有效,但我们可以在此基础上进行改进。

FutureBuilder 小组件

FutureBuilder在Flutter中提供了一种更干净、更好的方式来处理futureFutureBuilder ,接受一个future ,并在数据被解决后建立一个widget。

const FutureBuilder({ 
    Key? key, 
    this.future, 
    this.initialData, 
    required this.builder, 
  }) : assert(builder != null), super(key: key);

让我们仔细看看FutureBuilder widget是如何工作的。

FutureBuilder<String>(
  future: FUTURE,
  intialData: null, 
  builder: (BuildContext context, AsyncSnapshot<String> snapshot) {

  }
);

build 函数中的第二个参数是一个类型为 [AsyncSnapshot](https://api.flutter.dev/flutter/widgets/AsyncSnapshot-class.html)具有指定的数据类型。例如,在上面的代码中,我们已经定义了String

快照是与异步计算的最新交互的不可改变的表示。它有几个属性。当一个异步计算发生时,了解当前连接的状态是有益的,这可以通过snapshot.connectionState

[connectionState](https://api.flutter.dev/flutter/widgets/ConnectionState-class.html)有四个通常的流量。

  1. none :也许有一些初始数据
  2. waiting: 异步操作已经开始。数据通常为空
  3. active: 数据为非空,并有可能随时间变化
  4. done: 数据为非空值

snapshot.data 返回最新的数据,snapshot.error 返回最新的错误对象。snapshot.hasDatasnapshot.hasError 是两个方便的获取器,用于检查是否收到错误或数据。

FutureBuilder 是一个使用状态作为快照的StatefulWidget 。看一下FutureBuilder 源代码,我们可以认识到下面代码片断中显示的初始快照。

   _snapshot = widget.initialData == null
        ? AsyncSnapshot<T>.nothing()
        : AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData as T);

我们发送一个小部件订阅的future ,根据它来更新状态。

  void _subscribe() {
    if (widget.future != null) {
      final Object callbackIdentity = Object();
      _activeCallbackIdentity = callbackIdentity;
      widget.future!.then<void>((T data) {
        if (_activeCallbackIdentity == callbackIdentity) {
          setState(() {
            _snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data);
          });
        }
      }, onError: (Object error, StackTrace stackTrace) {
        if (_activeCallbackIdentity == callbackIdentity) {
          setState(() {
            _snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error, stackTrace);
          });
        }
      });
      _snapshot = _snapshot.inState(ConnectionState.waiting);
    }
  }

当我们处置这个小部件时,它就取消订阅了。

@override
void dispose() {
  _unsubscribe();
  super.dispose();
}

void _unsubscribe() {
  _activeCallbackIdentity = null;
}

让我们重构我们上面的例子,使用FutureBuilder

class MyFutureWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: getName(),
      builder: (BuildContext context, AsyncSnapshot snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return CircularProgressIndicator();
        }

        if (snapshot.hasData) {
          return Text(snapshot.data);
        }

        return Container();
      },
    );
  }
}

build 注意,我在我的FutureBuilder 方法里面直接使用了getName() 函数。
每次FutureBuilder's parent 被重建时,异步任务就会被重新启动,这不是好的做法。

解决这个问题的方法是将future 尽可能早地获得--例如,在initStateStatefulWidget

class MyFutureWidget extends StatefulWidget {
  @override
  _MyFutureWidgetState createState() => _MyFutureWidgetState();
}

class _MyFutureWidgetState extends State<MyFutureWidget> {
  Future<String> _dataFuture;

  @override
  void initState() {
    super.initState();
    _dataFuture = getName();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: _dataFuture,
      builder: (BuildContext context, AsyncSnapshot snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return CircularProgressIndicator();
        }

        if (snapshot.hasData) {
          return Text(snapshot.data);
        }

        if (snapshot.hasError) {
          return Text('There is something wrong!');
        }

        return SizedBox();
      },
    );
  }
}

initState() 在每次创建小组件时都被调用。因此,getName future 函数将被备忘在一个变量中。虽然我的widget每次都可以改变状态并重建,但我的数据将保持不变。

StreamBuilder 小部件

还值得一看的是 [StreamBuilder](https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html),另一个处理stream 的widget。StreamBuilderFutureBuilder 几乎是相同的。然而,StreamBuilder 是定期传递数据的,所以你必须比FutureBuilder 更频繁地监听它,因为你必须只监听一次。

StreamBuilder widget会自动订阅和取消订阅stream 。当处置一个widget时,你不必担心取消订阅的问题,这可能会导致内存泄漏。

@override
  Widget build(BuildContext context) {
    return StreamBuilder<String>(
      stream: dataStream,
      builder: (BuildContext context, AsyncSnapshot<String> snapshot) {

      },
    );
  }

结论

在本教程中,你已经学会了如何在Flutter中执行异步回调,从REST端点获取数据。异步编程是一种强大的力量,可以节省开发人员的时间和精力。Flutter提供了独特的工具,进一步简化了这个过程。

FutureBuilderStreamBuilder 构建小部件是使用Dart和Flutter来构造你的用户界面的一个严重好处。希望现在你能理解这两个小部件是如何通过Dart事件循环在基本层面上工作的。

The postAsync callbacks with Flutter FutureBuilder appeared first onLogRocket Blog.