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<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
只会返回一个响应,因此我将失去实时功能。
我想知道失去实时功能是否值得仅仅为了使架构保持一致,或者是否有更好的方法。
提前致谢,如有任何想法或建议,我们将不胜感激。
编辑:非常感谢您的 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 中的 Sink 和 Stream 有啥区别?