Placing two Firestore.instances inside an initState() - firebase

Now this maybe good practice or a complete no no!!
I was trying to resist placing more StreamBuilder(s) under build(BuildContext context) and tried to use initState() instead. I am having trouble due to not using Future/async/await correctly. The String _leaseTenantName (first initState() Firestore.instance) would have correct value but Strings _leaseUnitName & _leaseUnitPropertyUid (second initState() Firestore.instance) usually would return as null. StreamBuilder<PropertyDetails> below build would give the error message 'Invalid document reference. Document references must have an even number of segments, but properties has 1, null)' but kept trying and eventually worked when _leaseUnitPropertyUid finally had a value.
I believe the solution is to somehow wrap the two initState() Firestore.instances in a Future/async/await but could not work out a way to do this. Any ideas?? Or should I just use yet more nested StreamBuilders?
class _LeaseTileState extends State<LeaseTile> {
String _leaseTenantName = '';
String _leaseUnitPropertyUid = '';
String _leaseUnitName = '';
String _leasePropertyName = '';
String _leasePropertyUnitName = '';
#override
void initState() {
super.initState();
Firestore.instance
.collection("companies")
.document(widget.leaseDetails.tenantUid)
.snapshots()
.listen((snapshot) {
_leaseTenantName = snapshot.data['companyName'];
});
Firestore.instance
.collection("units")
.document(widget.leaseDetails.unitUid)
.snapshots()
.listen((snapshot) {
_leaseUnitName = snapshot.data['unitName'];
_leaseUnitPropertyUid = snapshot.data['propertyUid'];
});
}
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
return StreamBuilder<PropertyDetails>(
stream: DatabaseServices(propertyUid: _leaseUnitPropertyUid)
.propertyByDocumentID,
builder: (context, userCompany) {
if (!userCompany.hasData) return Loading();
_leasePropertyName = userCompany.data.propertyName;
_leasePropertyUnitName = '$_leasePropertyName - $_leaseUnitName';
return Card(

That's a big no no.
Firstly, there's nothing wrong in using multiple StreamBuilder, StreamBuilder help you simplify the usage of Streams so you don't end up messing things up with their subscriptions... like you did in initState().
When you call listen() on snapshots() as you did on initState(), you created a subscription, that subscription should be canceled on dispose(), but you don't cancel it, so you are leaking memory right there, a StreamBuilder would saved you here as it manages this for you.
Another thing to keep in mind is that you are using _leaseUnitPropertyUid on build(), but you don't check if _leaseUnitPropertyUid is valid. _leaseUnitPropertyUid is only going to be set after the Firebase snapshot() Stream emits one value and build() could be called before that. Again, StreamBuilder would have saved you here as well as you could check if it has emitted a value or not.
Also you are hardcoding the Firebase.instance on your code, which makes it very hard to test. Take a look on Dependency Injection and try to inject the Firebase.instance onto your class, like a Repository pattern or something similar, so you can swap the Firebase.instace for a testing Mock and make your code more testable.

Related

Flutter FutureBuilder inside StreamBuilder gets rebuild

Title might be a little off topic. I would like to create a profile page which users can see which posts they liked. My Likes structure in Firebase is like this. It has liked post's id and post's DocumentReference. I store the Post's DocumentReference because:
Prevent duplicate data.
Post's owner can change their name, it would not be affected in the duplicate data.
Hard to maintain like count of the Post.
Users should be able to unlike the posts from this page. When they unlike, it should be directly seen in the page (PostTile will be removed from ListView). To create this behaviour I use StreamBuilder as stream value as below:
Stream<QuerySnapshot> likedQuotes(String uid, int limit, Timestamp creationTime) {
return _likesCollection
.where('userId', isEqualTo: uid)
.orderBy('creationTime', descending: true)
.limit(limit)
.snapshots();
}
To not load all of the posts I had to limit the snapshot and load new data whenever user scrolls down to near end. Should I increase the limit or add .startAfter([creationTime]) condition to query?
For now, I increase the limit and this causes every PostTile to rebuild. Also when all of the data is loaded, whenever user scrolls the ListView, PostTiles in the ListView get rebuild.
To build the PostTile I use FutureBuilder as future value as below:
class PostTileFromDocument extends StatefulWidget {
final Stream docRef;
final String uid;
PostTileFromDocument({this.uid, this.docRef});
#override
_PostTileFromDocumentState createState() => _PostTileFromDocumentState();
}
class _PostTileFromDocumentState extends State<PostTileFromDocument> {
Future tileFuture;
#override
void initState() {
tileFuture = widget.docRef.get();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: tileFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
currentQuote = PostData.fromSnaphot(snapshot.data);
return Card(...);
}
}
);
}
}
Should I change the structure of the data in Firebase? Is my way of building PostTiles are wrong (FutureBuilder inside StreamBuilder)? What is the proper way of doing what I am trying to do?
Thanks in advance.

Getting firebase data in a stream

I built an application, which gets data from the firebase (realtime db). I did it whith this code, but I want, that I always get the new data. In the internet I found something like in a stream, but I didn't find a manual for that.
Does somebody know how this works?
This is my code:
void readData() {
FirebaseDatabase.instance.reference().child('CHECK').once().then(
(DataSnapshot dataSnapShot) {
print(dataSnapShot.value);
},
);
}
I want to get the data for example every 0.5 seconds
That's not really how Firebase works. But if you want to get the data from the database once right away, and then whenever it is updated, you can use onValue for that.
That'd look something like:
FirebaseDatabase.instance.reference().child('CHECK').onValue.listen((event) {
print(event.snapshot.value);
});
Give it a try: just set up the listener with this code, run the app, and then make a change to the database in the Firebase console. You'll see the data be printed once as soon as you run the app, and then again whenever you make a change.
From what I've read in your comments, you want the function to be executed repeatedly every 0.5 seconds.
A stream is not appropriate for that. However, you can use Timer
#override
void initState() {
super.initState();
timer = Timer.periodic(Duration(seconds: 15), (Timer t) => readData());
}
#override
void dispose() {
timer?.cancel();
super.dispose();
}
Your build() function will be called more than once once Timer.periodic is created.

Initial value in a TextFormField (Flutter) coming from a Future (Firebase)

folks.
I am trying to set an initialValue of a TextFormField from a String that comes from Firebase Firestore, so it is a Future. But i just can't make it work!
I have written a simplified code of my problem and is available on dartPad:
https://dartpad.dartlang.org/0649b56c10041ca7e6ab4440e7564dea
How should I do this correctly? Should I use a Future.then() instead of the FutureBuilder? Should I use a controller, instead of setting the initialValue? Any form I try, it ends up not working...
The text field will be empty until the task is complete but this works fine:
final _textEditingController = TextEditingController();
#override
void initState() {
super.initState();
asyncTask()
}
void asyncTask() async{
//do work
setState(() {
_textEditingController.text = workResult;
});
}
//in build method
TextFormField(
controller: _emailtextEditingController,
.
.
.
),

Why even after putting await keyword my app will show 0?

I have called trigger function inside initState function.In trigger function i will be taking data from an API and i parsed the data using storeddata.fromjson function. Then afterwards i will store those values in variables. As far as i know initState function will be called as soon as object of this statefull widget is created. But still in app, it will show exchangeval_bitcoin as 0 for some seconds and then it will get updated(I have initialized exchangeval_bitcoin as 0). does that mean build function will be called even before completion of initState function even after putting await keyword ? And how can i implement loading screen until that value is updated ?
void triggerfun() async {
var decodedmap;
Jsonparse p = Jsonparse(url: uri);
decodedmap = await p.cryptocovert();
Storeddata s = Storeddata.fromjson(decodedmap);
setState(() {
exchangeval_bitcoin = s.getdataforbitcoin();
exchangeval_etherium = s.getdataforetherium();
exchangeval_litecoin = s.getdataforlitecoin();
});
permanent = s;
}
//calling initstate
#override
void initState() {
super.initState();
triggerfun();
}
Thanks in advance.
triggerfun() is an asynchronous function. So in your initState(), it's going to call triggerfun(), but still continue with the rest of your program. When trigger function is finished, it will set state and rebuild the widget.
The await keyword will stop the program until that line is finished, but triggerfun() as a whole is still asynchronous.

Flutter use Stream or Future?

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
First of all, in my opinion, firebase is not designed to back up a mature project. In the end, you'll end up with a REST api backing up your app. It's true that, you might also end up using both but for different purposes. So i think you should think about firebase as a tool for MVP/proof of concept. I know that Firebase is cool and works well, etc. but the costs are not feasible for a final product.
Now, nobody says that you can't have a REST client implementation that will return a Stream. Check out this Stream.fromFuture(theFuture). You can think of the REST api like a stream that emits only one event (Rx equivalent: Single)
I would also advise to be careful with the real time update feature provided by Firebase, if you transition to a full REST api, you won't be able to do a real time update because REST doesn't work like that. Firebase is using Sockets for communication (if I remember correctly).
I recommended use the Future way, if you take a break and compare the two codes, with the Future way you need to write more, but the architecture is more clean, strong and scalable. In my experience, that's the right way to do good things. Great work
You can also include both methods in the api / repository, and either retrieve a Future or listen to the Stream in the bloc depending on what you want to do. I don't think you need to worry about violating the consistency of REST by also having a method that returns a stream. There is no better way to tap into the real-time functionality of Firestore than to use a stream like you described.
But to just return a Future, you don't have to go through a stream, you can just await a CollectionReference's getDocuments(), something like this:
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;
}
}
It all depends on your app I think. If real time update is an important feature that effects user experience a lot, stick with the Firebase data streams. If real time updates are not a must, you can get data once using Futures. An alternative to Firebase for real time data updates could be GraphQL subscriptions. I would recommend you to check out Hasura for quick implementation of GraphQL API.
It's a good question.
Firestore vs REST API will result in different APIs (Stream vs Future).
Making the code generic won't work here. As you said:
Stream-based APIs will be realtime
Future-based APIs will not
Even the UX would be different.
In the Stream version, you don't need a refresh indicator.
In the Future version, you can reload the data with pull-to-refresh.
I would not recommend to future-proof your code in this case.
If Firestore works well for you, use Streams in all your APIs.
Only if/when you decide to move to a REST API, then you can convert all your APIs (and UX) to use Futures.
Giving up realtime capabilities upfront doesn't seem worth it.

Resources