Flutter使用Stream还是Future?

时间:2019-10-29 20:27:50

标签: firebase flutter dart google-cloud-firestore stream

我正在使用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();
}

在我的存储库类中,我这样称呼它:

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<List<Job>>,因为API不会返回Stream(我不知道是否可能)。

我会有另一个这样的API类,现在返回Future<List<Job>>

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只会返回一个响应,因此我将失去实时功能。

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

在此先感谢您的任何想法或建议。

编辑:非常感谢您的评论,我非常感谢他们。实际上,我不知道应该将哪个标记为可接受的答案,因为它们都对我有很大帮助,所以我决定对所有人进行正面投票。如果有人不同意该协议,或者这不是Stackoverflow中的正确行为,请告诉我

5 个答案:

答案 0 :(得分:1)

这一切取决于我认为的应用程序。如果实时更新是影响用户体验的重要功能,请坚持使用Firebase数据流。如果不是必须进行实时更新,则可以使用期货获得一次数据。实时数据更新的Firebase替代品可以是GraphQL订阅。我建议您查看Hasura,以快速实现GraphQL API。

答案 1 :(得分:1)

我建议使用Future方法,如果您需要休息一下并比较这两个代码,则需要使用Future方法编写更多代码,但是体系结构更简洁,强大和可扩展。以我的经验,这是做好事的正确方法。很棒的工作

答案 2 :(得分:1)

这是一个很好的问题。

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

使通用代码在这里行不通。如您所说:

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

即使UX也有所不同。

  • 在Stream版本中,您不需要刷新指示器。
  • 在将来的版本中,您可以使用“按需刷新”方式重新加载数据。

在这种情况下,我不建议将来对您的代码进行过验证。

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

仅当/当您决定转向REST API时,才可以将所有API(和UX)转换为使用Futures。

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

答案 3 :(得分:1)

您还可以在api /存储库中同时包含这两种方法,并根据自己的意愿检索Future或在集团中收听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;

 }
}

答案 4 :(得分:1)

首先,在我看来,firebase并非旨在支持成熟的项目。最后,您将获得一个REST api来备份您的应用程序。的确,您可能最终会同时使用两者,但出于不同的目的。因此,我认为您应该考虑将Firebase用作MVP /概念验证的工具。我知道Firebase很酷,而且效果很好,等等,但是最终产品的成本并不可行。

现在,没有人说您不能拥有将返回Stream的REST客户端实现。查看此Stream.fromFuture(theFuture)。您可以将REST api视为仅发出一个事件(Rx等效项:Single)的流

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