我正在使用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中的正确行为,请告诉我
答案 0 :(得分:1)
这一切取决于我认为的应用程序。如果实时更新是影响用户体验的重要功能,请坚持使用Firebase数据流。如果不是必须进行实时更新,则可以使用期货获得一次数据。实时数据更新的Firebase替代品可以是GraphQL订阅。我建议您查看Hasura,以快速实现GraphQL API。
答案 1 :(得分:1)
我建议使用Future方法,如果您需要休息一下并比较这两个代码,则需要使用Future方法编写更多代码,但是体系结构更简洁,强大和可扩展。以我的经验,这是做好事的正确方法。很棒的工作
答案 2 :(得分:1)
这是一个很好的问题。
Firestore vs REST API将产生不同的API(Stream vs Future)。
使通用代码在这里行不通。如您所说:
即使UX也有所不同。
在这种情况下,我不建议将来对您的代码进行过验证。
如果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正在使用套接字进行通信(如果我没记错的话)。