Flutter 使用 Stream 还是 Future?

Posted

技术标签:

【中文标题】Flutter 使用 Stream 还是 Future?【英文标题】:Flutter use Stream or Future? 【发布时间】:2020-02-25 03:17:07 【问题描述】:

我正在使用 Firestore 数据库来存储对象列表。要检索它们,我使用 Firestore 包提供的 Stream,如下所示:

 class FirestoreApi implements Api 

  FirestoreApi._();
  static final instance = FirestoreApi._();

  @override
  Stream<List<Job>> getJobList() 
    final path = "users/myUserId/jobs";
    final reference = Firestore.instance.collection(path);
    final snapshots = reference.snapshots();

    return snapshots.map((snapshot) => snapshot.documents.map(
      (snapshot) => Job(
        id: snapshot.data['uid'],
      name: snapshot.data['name']
     ),
    ).toList());
  

它实现了一个abstract 类:

abstract class Api 
  Stream<List<Job>> getJobList();

在我的 Repository 类中,我这样称呼它:

class Repository 
  final FirestoreApi _firestoreApi = FirestoreApi.instance;

  Stream<List<job>> getJobList() => _firestoreApi.getJobList();

然后在我的 BloC 中调用存储库:

class JobBloc 

  final _repository = new Repository();

  Stream<List<Job>> getJobList() 
    try 
      return _repository.getJobList();
     catch (e) 
      rethrow;
     finally 
  

最后是我在Widget 中使用它的方式:

Widget _buildBody(BuildContext context) 
  final JobBloc _jobBloc = Provider.of<JobBloc>(context);

  return StreamBuilder<List<Job>>(
  stream: _jobBloc.getJobList(),
  builder: (BuildContext context, AsyncSnapshot<List<Job>> snapshot) 
    if (snapshot.hasData) 
      return RefreshIndicator(
        child: JobList(snapshot.data),
        onRefresh: () => _jobBloc.refreshJobList(),
      );
     else 
      if(snapshot.connectionState == ConnectionState.waiting) 
        return Center(child: CircularProgressIndicator());
       else 
        return Center(child: Text("No data"));
      

    
  ,
);
 

直到这里一切正常,当 Firestore 数据库中的某些内容发生更改时,我的 Widget 会实时更新。

但现在我想更进一步。可以说,也许将来我需要更改我的 api 实现并使用 REST api 而不是 Firestore。我希望我的代码为此做好准备。

在这种情况下,所有getJobList() 方法都应该返回Future&lt;List&lt;Job&gt;&gt;,因为API 不会返回Stream(我不知道这是否可能)。

我会有另一个这样的 API 类,它现在返回 Future&lt;List&lt;Job&gt;&gt;

class RestApi implements Api 

 RestApi._();
 static final instance = RestApi._();

 @override
 Future<List<Job>> getJobList() 
   //TODO: my rest api implementation
 

因此 API abstract 类将被修改如下:

abstract class Api 
  Future<List<Job>> getJobList();

这里是更新的存储库:

class Repository 
  final RestApi _restApi = RestApi.instance;

  Future<List<job>> getJobList() => _restApi.getJobList();

最后,在我的 BloC 中,我将 sink API 返回的列表在 StreamController 中,如下所示:

class JobBloc 
  final StreamController _jobController = StreamController<List<Job>>.broadcast();

  // retrieve data from stream
  Stream<List<Job>> get jobList => _jobController.stream;

  Future<List<Job>> getJobList() async 
    try 
      _jobController.sink.add(await _repository.getJobList());
     catch (e) 
      rethrow;
     finally 
 

现在的问题是:我真的很喜欢 Firestore 返回一个Stream,它使我的应用程序可以实时更新。但另一方面,我希望我的架构是一致的。

由于我无法让我的 REST api 返回 Stream,我认为唯一可能的方法是将 Firebase Stream 转换为 Future,但这样我就会失去实时更新功能。

类似这样的:

class FirestoreApi implements Api 

   FirestoreApi._();
   static final instance = FirestoreApi._();

    @override
    Future<List<Job>> getJobList() async 
      final path = "users/myUserId/jobs";
      final reference = Firestore.instance.collection(path);
      final snapshots = reference.snapshots();

    Stream<List<Job>> jobs = snapshots.map((snapshot) => snapshot.documents.map(
        (snapshot) => Job(
          id: snapshot.data['uid'],
          name: snapshot.data['name'],
        ),
       ).toList());

      List<Job> future = await jobs.first;
      return future;

     
   

到目前为止,我研究的是使用Future 只会返回一个响应,因此我将失去实时功能。

我想知道失去实时功能是否值得仅仅为了使架构保持一致,或者是否有更好的方法。

提前致谢,如有任何想法或建议,我们将不胜感激。

编辑:非常感谢您的 cmets,我非常感谢他们。我实际上不知道应该将哪一个标记为已接受的答案,因为他们都对我帮助很大,所以我决定给你们所有人投赞成票。如果有人不同意这一点,或者这不是 *** 中的正确行为,请告诉我

【问题讨论】:

仅供参考,反引号不用于对 Stack Overflow 的一般文本强调。它们用于代码的 sn-ps。 谢谢@DougStevenson 我已经编辑了我的代码,试图遵循你的建议 【参考方案1】:

首先,在我看来,firebase 并不是为了支持一个成熟的项目而设计的。最后,您将得到一个 REST api 来备份您的应用程序。确实,您最终也可能会同时使用两者,但目的不同。因此,我认为您应该将 firebase 视为 MVP/概念证明的工具。我知道 Firebase 很酷且运行良好等,但成本对于最终产品来说是不可行的。

现在,没有人说您不能拥有将返回 Stream 的 REST 客户端实现。看看这个Stream.fromFuture(theFuture)。你可以把 REST api 想象成一个只发出一个事件的流(Rx 等效:Single)

我还建议谨慎使用 Firebase 提供的实时更新功能,如果您转换到完整的 REST api,您将无法进行实时更新,因为 REST 不能那样工作。 Firebase 正在使用 Sockets 进行通信(如果我没记错的话)。

【讨论】:

这样的答案永远是无价的【参考方案2】:

您还可以在 api/repository 中包含这两种方法,并根据您想要执行的操作检索 Future 或侦听 bloc 中的 Stream。我认为您不必担心通过返回流的方法来违反 REST 的一致性。要利用 Firestore 的实时功能,没有比使用您所描述的流更好的方法了。

但是仅仅返回一个Future,你不必经过一个流,你可以等待一个CollectionReference的getDocuments(),像这样:

class FirestoreApi implements Api 

FirestoreApi._();
static final instance = FirestoreApi._();

CollectionReference jobsReference = Firestore.instance.collection("users/myUserId/jobs");

@override
Future<List<Job>> getJobList() async 
  QuerySnapshot query = await jobsReference.getDocuments();

  List<Job> jobs = query.documents.map((document) => Job(
      id: document.data['uid'],
      name: document.data['name'],
   )).toList();

  return jobs;

 

【讨论】:

【参考方案3】:

我认为这完全取决于您的应用。如果实时更新是影响用户体验的重要功能,请坚持使用 Firebase 数据流。如果不需要实时更新,您可以使用 Futures 获取一次数据。用于实时数据更新的 Firebase 的替代方案可能是 GraphQL 订阅。我建议您查看Hasura 以快速实现 GraphQL API。

【讨论】:

【参考方案4】:

我推荐使用Future方式,如果你休息一下,对比一下两个代码,用Future方式你需要写更多,但架构更干净,更强大和可扩展。根据我的经验,这是做好事的正确方法。干得好

【讨论】:

【参考方案5】:

这是个好问题。

Firestore 与 REST API 将产生不同的 API(Stream 与 Future)。

使代码通用在这里不起作用。正如你所说:

基于流的 API 将是实时的 基于未来的 API 不会

甚至用户体验也会有所不同。

在 Stream 版本中,您不需要刷新指示器。 在未来版本中,您可以使用 pull-to-refresh 重新加载数据。

在这种情况下,我不建议您的代码面向未来。

如果 Firestore 适合您,请在您的所有 API 中使用 Streams。

只有当您决定迁移到 REST API 时,您才能将所有 API(和 UX)转换为使用 Futures。

提前放弃实时功能似乎不值得。

【讨论】:

以上是关于Flutter 使用 Stream 还是 Future?的主要内容,如果未能解决你的问题,请参考以下文章

Flutter状态管理——单Stream和广播Stream

Flutter状态管理——单Stream和广播Stream

Flutter 中的 Sink 和 Stream 有啥区别?

Flutter-Stream使用

Flutter 使用 Stream 检索 Firestore 集合

Flutter - 从 Stream 接收然后修改数据