I'm using Firestore database to store a list of objects. To retrieve them I use the Stream
provided by the Firestore package, like this:
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());
}
}
It implements an abstract
class:
abstract class Api {
Stream<List<Job>> getJobList();
}
In my Repository class I call it like this:
class Repository {
final FirestoreApi _firestoreApi = FirestoreApi.instance;
Stream<List<job>> getJobList() => _firestoreApi.getJobList();
}
Then in my BloC I call the Repository:
class JobBloc {
final _repository = new Repository();
Stream<List<Job>> getJobList() {
try {
return _repository.getJobList();
} catch (e) {
rethrow;
} finally {}
}
}
And finally here is how I use it in my 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"));
}
}
},
);
}
Until here everything works great and my Widget
gets updated in real time when something is changed in the Firestore database.
But now I want to go one step further. Lets say that maybe in the future I need to change my api implementation and use a REST api instead of Firestore. I want that my code is prepared for that.
In that case, all the getJobList()
methods should return a Future<List<Job>>
since the API will not return a Stream
(I don't know if that's possible).
I would have another API class like this that now returns Future<List<Job>>
:
class RestApi implements Api {
RestApi._();
static final instance = RestApi._();
@override
Future<List<Job>> getJobList() {
//TODO: my rest api implementation
}
}
So the API abstract
class would be modified like this:
abstract class Api {
Future<List<Job>> getJobList();
}
Here the updated Repository:
class Repository {
final RestApi _restApi = RestApi.instance;
Future<List<job>> getJobList() => _restApi.getJobList();
}
And finally in my BloC I would sink
the list returned by the API in a StreamController
like this:
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 {}
}
}
Now the question: I really like that Firestore returns a Stream
, it makes my app to be updated in real time. But on the other hand, I would like that my architecture is consistent.
Since I cannot make my REST api to return a Stream
, I think the only way possible would be converting the Firebase Stream
to a Future
but then I would loose the real-time update feature.
Something like this:
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;
}
}
Until now what I've researched is that using the Future
will return only one response, so I will lose the real-time functionality.
I would like to know if loosing the real-time feature would be worthy just to make the architecture consistent or if there is a better approach.
Thanks in advance, any ideas or suggestion will be appreciated.
EDIT: Thanks a lot for your comments, I really appreciate them. I actually don't know which one should be marked as accepted answer since all of them have helped me a lot so I decided to give a positive vote to all of you. If anyone doesn't agree with that or this is not the right behaviour in Stackoverflow please let me know