Initial value in a TextFormField (Flutter) coming from a Future (Firebase) - 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,
.
.
.
),

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.

Placing two Firestore.instances inside an initState()

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.

how to use singleton firebase service on Flutter?

I have 4 pages. I called getRide() method in every 4 pages. it's means 4 times database call. Am I right? Is it possible to create a singleton for this scenario?
Firebase Service:
class FirebaseService {
final Firestore _db = Firestore.instance;
Stream<List<RideModel>> getRide() {
return _db.collection('ride')
.snapshots()
.map((list) => list.documents.map((doc) => RideModel.fromFirestore(doc))
.toList());
}
}
Calling Method:
#override
void initState() {
super.initState();
db.getRide().listen(getRide);
}
void getRide(List<RideModel> model) {
if (!mounted) return;
setState(() {
rideModel = model;
});
}
I can't pass rideModel through Navigator. because when change data in ride collection need to change 4 pages UI.
Someone tells me this answer is correct for the above problem.
I found this way to solve this problem.
I used get_it package and create service locator,
GetIt locator = GetIt.instance;
void setupSingletons() async {
locator.registerLazySingleton<FirebaseService>(() => FirebaseService());
}
And then added to the main class
void main() {
setupSingletons();
runApp(MultiProvider(
providers: globalProviders,
child: MyApp(),
));
}
And every screen I added,
class _Screen1 extends State<Screen1> {
// final db = FirebaseService();
FirebaseService db = GetIt.I.get<FirebaseService>();

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.

How to cancel firebase async requests in flutter instead of checking mounted

I have a flutter app talking to the Firebase Realtime Database. I get the data asynchronously, obviously, but my UI allows the user to move to a different part of the app, which means by the time the request completes, the Widget may be unmounted. Best practices say to cancel the async work instead of checking the mounted property but I cannot seem to figure out how to do this for some reason.
#override void initState() {
super.initState();
firebaseRealtimeReference.child('myData').once().then((results) {
if (mounted) {
setState(() {
_myLocalData = results;
}
}
}
/* Alternately with async/await: */
_myLocalData = firebaseRealtimeDatabaseReference.child('myData').once();
}
#override Widget build(BuildContext context) {
return new MyWidget(_myLocalData);
}
#override dispose() {
// Instead of checking mounted in the future, I should instead
// cancel the work in progress here.
super.dispose();
}

Resources