Flutter Update Text Widget Variable without Reloading - firebase

Im using Flutter with Firestore to make Investing Application.
enter image description here
In this page, I get Firestore's data in Streambuilder.
The data what i recieved are displayed in Listview with Inkwell.
What I want to do is when I click the Inkwell, then change the bottom left's Text Widget's text into Listview's number.
So, I used the setstate method with FutureBuilder.
It works but there are some problems.
When I click the Inkwell, then whole page reloads.
So, My Streambuilder widgets displays circleprogressindicator short.
However I think this is not good for users.
I want to solve this problem.
Is there any solution?
My code is in here.
I'm sorry for my poor English and I hope my problem was properly communicated.
https://github.com/RGLie/Investing-Game-ICISTS/blob/main/lib/startup_1_trade.dart

You can fix the rebuilds by storing the streams in variables outside of the build method and using them in the StreamBuilder widgets.
class Startup1Trade extends StatefulWidget {
...
}
class _Startup1TradeState extends State<Startup1Trade> {
...
CollectionReference prices = FirebaseFirestore.instance.collection('startup_1');
CollectionReference users = FirebaseFirestore.instance.collection('users');
final Stream priceStream = prices.doc('price').snapshots();
final Stream userStream = users.doc(widget.user.uid).snapshots();
#override
Widget build(BuildContext context) {
...
}
}
Use them like this:
StreamBuilder<DocumentSnapshot>(
stream: priceStream,
builder: (context, snap) {
...
}
}
StreamBuilder<DocumentSnapshot>(
stream: userStream,
builder: (context, snap) {
...
}
}

Related

Flutter & Firebase : How do I get a specific field from a specific document into a stream?

I have been trying to get a specific field from a specific document into a stream. But I always got null. How do I fix this?
Function
Stream<List<String>> getError() {
DocumentReference errorReference =
appSettingCollection.document('report_error');
final Stream<DocumentSnapshot> snapshots = errorReference.snapshots();
return snapshots.map((doc) {
//print(doc.data['types']);
return doc.data['types'];
});
}
Main
return StreamBuilder<List<String>>(
stream: User_DatabaseService().getError(),
builder: (context, snapshot) {
final List<String> errorTypes = snapshot.data;
print('In Error Report : $errorTypes'); // I got null??? But why?
///--------
}}
I think you have taken wrong stream.
Try to correct Stream
StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance
.collection('CollectionName')
.document("DocumentID")
.get(),
And use it below for accessing specific field as:
document["FieldName"]
For accessing single field you will need to access whole document.I will suggest to use FutureBuilder follow the link below and you will get your answer more precise.
https://firebase.flutter.dev/docs/firestore/usage/

Flutter FutureBuilder throws error, but on simulation it works as expected

I'm a beginner to Flutter and currently I'm having a problem with correctly implementing FutureBuilder in flutter.
I'm trying to build a user page, where I have my user information stored in Firebase, and every time I access the user page it retrieves the current user data and shows the data on the page. Here's the code I've written for the implementation:
class UserPage extends StatefulWidget{
#override
UserPageState createState() => UserPageState();
}
class UserPageState extends State<UserPage>{
String userName;
String userEmail;
String collegeName;
Future _infoInit() async {
userName = await HelperFunctions.getUserNamePreference();
userEmail = await HelperFunctions.getUserEmailPreference();
collegeName = await HelperFunctions.getUserCollegePreference();
}
Widget userScaffold(BuildContext context, AsyncSnapshot snapshot){
return Scaffold(
appBar: AppBar(
elevation: 0,
title: Text(
userName
),
backgroundColor: Colors.lightBlue,
),
body:Center(
child: Text("This is User Page")
)
);
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _infoInit(),
builder: (context,AsyncSnapshot snapshot) => userScaffold(context, snapshot)
);
}
}
At the moment the only part I've written is showing the currently logged in user on the app bar, and when I run the code, it seems that it's successfully running. However, when I look at the android studio console I could see that it's actually facing and error which I think is related to async function executed in the FutureBuilder widget.
Error Message:
Performing hot reload...
Syncing files to device iPhone 11...
Reloaded 7 of 650 libraries in 397ms.
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following assertion was thrown building FutureBuilder<dynamic>(dirty, state: _FutureBuilderState<dynamic>#772f1):
A non-null String must be provided to a Text widget.
'package:flutter/src/widgets/text.dart':
Failed assertion: line 298 pos 10: 'data != null'
The relevant error-causing widget was:
FutureBuilder<dynamic> file:///Users/nossu3751/Downloads/flutter_project/moim_app/lib/user/profile.dart:39:12
When the exception was thrown, this was the stack:
#2 new Text (package:flutter/src/widgets/text.dart:298:10)
#3 UserPageState.userScaffold (package:moimapp/user/profile.dart:26:18)
#4 UserPageState.build.<anonymous closure> (package:moimapp/user/profile.dart:41:54)
#5 _FutureBuilderState.build (package:flutter/src/widgets/async.dart:732:55)
#6 StatefulElement.build (package:flutter/src/widgets/framework.dart:4619:28)
More exactly, it says that the userName that I'm trying to use in the FutureBuilder is null, even though I believe I assigned the value already through running _infoInit() method and it does actually show correctly on the simulator.
I would really appreciate it if someone can let me know what I'm doing wrong here, and what I can do to stop this message from appearing again. Thank you very much in advance!
The problem is that the FutureBuilder's builder method is invoked each time the AsyncSnapshot is changed (and, initially, the snapshot has no data). Therefore, in the first couple of times that builder is called, userName will be null, thus giving you that error; but after some point, the username will have been fetched, and when the builder function is called you'll see the username on the screen correctly.
The idiomatic way to use FutureBuilder is the following:
FutureBuilder(
future: myFuture,
builder: (context, AsyncSnapshot snapshot) {
// Try adding this print to your code to see when this method is being executed!
print('Building with snapshot = $snapshot');
if (snapshot.hasData) {
// return widget with data - in your case, userScaffold
}
else if (snapshot.hasError) {
// return widget informing of error
}
else {
// still loading, return loading widget (for example, a CircularProgressIndicator)
}
},
);
So, initially, the builder function will be called will be with a snapshot without data (the "else" branch). You'll probably want to show a loading widget in that case. Then, after some time, the Future completes, and the builder function is called with a snapshot having either the data or some error.
Another important thing in your code is that your function _infoInit doesn't actually return anything. So, in fact, your FutureBuilder isn't using the data from the AsyncSnapshot (which means that the above snippet will not actually work, since snapshot.hasData will never be true). With FutureBuilder, you normally want to build the widget using the data returned by the AsyncSnapshot. Instead, what happens in your code is:
FutureBuilder is created. This calls _infoInit(), which triggers fetching the data from Firebase;
FutureBuilder's builder method is called. It tries using userName, but it is null so Flutter shows the failed assertion;
_infoInit() fetches all the data, and returns a Future (this future is returned automagically because of the async clause in the method signature; however, without a return clause, it doesn't actually return any data). But despite the Future not having any data, the 3 variables in the state (including userName) have been updated, and now contain some data.
Since the future passed to the FutureBuilder has completed, the builder method is called again. This time, userName has data, so it builds correctly.
It's fine to write the code as you did, but in that case, you don't need to use a FutureBuilder. You could just invoke _infoInit() from the widget's initState() method (initState is a method called once when the State is first built) and, after the data is fetched, call setState(). Here's how that would look:
class UserPage extends StatefulWidget {
#override
UserPageState createState() => UserPageState();
}
class UserPageState extends State<UserPage> {
String userName;
String userEmail;
String collegeName;
bool loadingData = true;
#override
void initState() {
_infoInit();
}
// This can be void now, since we're changing the state, rather than returning a Future
void _infoInit() async {
String userName = await HelperFunctions.getUserNamePreference();
String userEmail = await HelperFunctions.getUserEmailPreference();
String collegeName = await HelperFunctions.getUserCollegePreference();
setState(() {
// In theory, we could have just updated the state variables above, but the
// recommended practice is to update state variables inside setState.
this.userName = userName;
this.userEmail = userEmail;
this.collegeName = collegeName;
loadingData = false;
});
}
Widget userScaffold(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
title: Text(userName),
backgroundColor: Colors.lightBlue,
),
body: Center(child: Text("This is User Page")));
}
#override
Widget build(BuildContext context) {
if (loadingData) {
// We don't have the data yet, so return a widget to indicate some loading state
return Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return userScaffold(context);
}
}
The above snippet doesn't treat for errors in fetching the data, which you'll probably want to do. And to do that, you might use a flag called 'hasError' or something - which ultimately, will give very similar code to how the "idiomatic" FutureBuilder builder method is written.
Both are valid approaches; FutureBuilder maybe uses less code (and might be simpler to use if the rest of your code already uses Futures), but ultimately, it's up to your preference.
You need to use ConnectionState inside your builder. Look at this code template: (Currently your builder return userScaffold without waiting for the future to complete)
return FutureBuilder(
future: yourFuture(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// future complete
// if error or data is false return error widget
if (snapshot.hasError) {
return _buildErrorWidget('SOMETHING WENT WRONG, TAP TO RELOAD');
}
// return data widget
return _buildDataWidget();
// return loading widget while connection state is active
} else
return _buildLoadingWidget();
},
);

Returning null user data from Firestore. How to reference it globaly instead?

I'm quite new to Flutter and I've been struggling to access a user's document on Firestore.
On the profile page,
I'm setting the current user's UID inside initState, but uid returns null for a quick second, then the page updates with correct info.
So I am able to retrieve a certain field (like displayName), but it isn't quite the best practice. I don't want to have a bunch of boilerplate code and await functions mixed with UI and such.
Code:
FirebaseUser user;
String error;
void setUser(FirebaseUser user) {
setState(() {
this.user = user;
this.error = null;
});
}
void setError(e) {
setState(() {
this.user = null;
this.error = e.toString();
});
}
#override
void initState() {
super.initState();
FirebaseAuth.instance.currentUser().then(setUser).catchError(setError);
}
Then in my body I have a Stream builder to get the document.
body: StreamBuilder(
stream: Firestore.instance
.collection('users')
.document(user.uid)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<Color>(Colors.deepOrange),
),
);
} else {
var userDocument = snapshot.data;
return showProfileHeader(userDocument);
}
},
)
I want to make 'global' references to be accessed throughout the app. Instead of getting the user's id on every page and streaming a specific field when I might need multiple ones.
The only ways I found online to do something similar, created lists with all the data in it. I feel like this might get extra fields I don't need.
How can I make data from Firestore available across the app?
I am using the "Provider" package for doing state management across my app. Nowadays its also the suggested way by the google flutter team when it comes to state management. See the package here: https://pub.dev/packages/provider
Regarding Firebase Auth and accessing the credentials application wide, i am using that said package like stated on this page:
https://fireship.io/lessons/advanced-flutter-firebase/
Short version below. Bootstrap your app like so:
import 'package:provider/provider.dart';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// Make user stream available
StreamProvider<FirebaseUser>.value(
stream: FirebaseAuth.instance.onAuthStateChanged),
// not needed for your problem but here you can see how
// to define other Providers (types) for your app.
// You need a counter class which holds your model of course.
ChangeNotifierProvider(builder: (_) => Counter(0)),
],
// All data will be available in this child and descendents
child: MaterialApp(...)
);
}
}
Then in your child widgets, just do:
// Some widget deeply nested in the widget tree...
class SomeWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
var user = Provider.of<FirebaseUser>(context);
return Text(user.displayName) // or user.uid or user.email....
}
}
This should do the trick.
That happens because FirebaseAuth.instance.currentUser() returns a future, and until that future is completed, you will not have the proper FirebaseUser object.
Making the user object global is not a bad idea. In addition, you can hook it up to the FirebaseAuth stream so that it gets updated everytime the user auth status changes, like so in a user.dart file:
class User {
static FirebaseUser _user;
static get user => _user;
static void init() async {
_user = await FirebaseAuth.instance.currentUser();
FirebaseAuth.instance.onAuthStateChanged.listen((firebaseUser) {
_user = firebaseUser;
});
}
}
You can call User.init() in main() and access the user object with User.user.

How to properly wait until future is complete in dart

There is a slight bug in my app made with Flutter, that when the user has signed in, it fetches the user information from my database but not fast enough and causes a visual error on my front end of the app. The app has layouts that use the user information (name, location, and image) and it is not being loaded quick enough. I was wondering if there is a way to wait for my future to complete and once it is done, it can navigate the user to the front end with no problem.
You Should fetch your date from the database in the initState() function, then you have to modify your widget builder to use FutureBuilder, here's an example:
Widget build(BuildContext context) {
return FutureBuilder(
future: getProfile(),
builder: (BuildContext context, AsyncSnapshot<SharedPreferences> snapshot) {
if(snapshot.connectionState == ConnectionState.done){
return new MaterialApp();
}
}
)
}
note that you can replace AsyncSnapshot<SharedPreferences> with the type your Future function returns.

Passing Firestore document reference to flutter widget

I am having trouble passing a Firestore document reference to a widget in Flutter. The Idea is to have the class Home display a ListView of thumbnail images, which with the help of onTap route you to a VideoView that displays the video associated with the thumbnail image.
Now the Problem is that although I have all the classes and the layout set up, I am having trouble handing over the Firestore reference with the necessary metadata for the video to the VideoView class, like the url of the video in question or its title and comments.
All the thumbnail urls and the matching titles in the Home Widget are fetched from Firestore and added to the ListView as seen in the examples section here
My plan is to have apart from the title and thumbnailUrl field in Firestore also a reference to the matching Video document which holds all the metadata necessary to display the video and its comments in the VideoView widget.
Is this possible with the cloud_firestore plugin in flutter right now and if so, how could I implement it?Thanks in advance!
ListPage
ListTile(
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => DetailsPage(msg: snapshot.data.documents[index]))),
)
DetailsPage
When using statefull widget
class DetailsPage extends StatefulWidget {
DocumentSnapshot msg;
DecryptorPage({#required this.msg});
#override
_DecryptorPageState createState() => _DecryptorPageState();
}
class _DecryptorPageState extends State<DecryptorPage> {
#override
Widget build(BuildContext context) {
final String msg = widget.msgs.data['msg'];
return Container(
child: Text('$msg)
);
}
}

Resources