I'm calling an async method getMyLocation() to get my current location in my initState(). The method can take a while...
I wanted to understand the behavior of initState() in these cases. Does the method still execute in the background as build() renders or does initState() timeout since it needs to complete before build() renders?
In my build() I have a statement checking if my latitude is null, in which case I return a Loading() widget. Sometimes Screen() renders and sometimes Loading() goes on indefinitely. I am assuming sometimes the getMyLocation() successfully executes during initState() and sometimes it timesout?
#override
void initState() {
super.initState();
final userData = Provider.of<MyUser>(context, listen: false);
final myUser = userData.getUser();
userData.getMyLocation();
}
getMyLocation() async {
_myUser.longitude = await getCurrentLongitude();
_myUser.latitute = await getCurrentLatitude();
notifyListeners();
}
Widget build(BuildContext context) {
final userData = Provider.of<MyUser>(context);
final myUser = userData.getUser();
myUser.latitude == null?
return Loading()
: return Screen()
Great question. First of all, initState() runs synchronously, it prepares various things needed for build() method to run properly. If you are executing some async function here, it will just return a Future because you can't await it in the initState(). In your case you probably need a FutureBuilder. The "proper way" of dealing with futures would be something like:
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Future<void> getMyLocation() async {
final userData = Provider.of<MyUser>(context, listen: false);
final myUser = await userData.getUser();
// if getUser() is async then we have to await
myUser.longitude = await getCurrentLongitude();
myUser.latitute = await getCurrentLatitude();
// notifyListeners();
// You probably do not need this, should be done in provider methods instead
}
Widget build(BuildContext context) {
return FutureBuilder(
future: getMyLocation(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return SomeErrorWidget();
}
if (snapshot.hasData) {
return Screen(snapshot.data);
}
return SomeLoadingWidget();
});
}
Related
I've been stuck for several hours with a problem on flutter. If you can help me that would be really nice.
I need to put "await" in my Widget build(BuildContext context){} but it's impossible to put "async".
How to do ?
When i test void _myAsyncMethod()async{} :
To Fix your issue you can put async in the body of method like this
Before=> Widget build(BuildContext context) {
After=> Widget build(BuildContext context) async{
Although this will not solve your problem as flutter wiill warn you as this is not the proper way to do it.
It's not a good practice to call await inside flutter's build method Because
Generally an apps need to run a 60 frames per second on an average hence flutter's build method we'll be called over and over to re-render the ui.
Another reason is that, doing calling await function() in build method will block your UI.
Solution
use FutureBuilder
call await auth.currentUser() in initState method
Another way to solve this is to use FutureBuilder
sample Code for 1
FutureBuilder(
builder: (BuildContext ctx, AsyncSnapshot<userModel> snapshot) {
if(ConnectionState.done == snapshot.connectionState) {
return Text(snapshot.data.userId);
} else {
return CircularProgressIndicator();
}
},
future: auth.currentUser(),
);
sample Code for 2(stateful widget)
late UserModel;
void initState() {
UserModel user = await auth.currentUser();
}
this is very basic code but it's enough for you to get started.
Note: I've assumed userModel mentioned above is response type of auth.currentUser() you can change it accordingly.
What you want to do is not optimal but you can create a method and put your await variable in there:
late final FirebaseUser _user;
void _myAsyncMethod()async{
_user = await auth.currentUser;
}
#override
Widget build(BuildContext context) {
_myAsyncMethod();
return Scaffold(appBar: AppBar(), body: Container());
}
If your are using stateful widget you can instantiate firebase auth in initstate() method.
class testFirless extends StatefulWidget {
var currentuseid = "";
testFirless({Key? key}) : super(key: key);
#override
_testFirlessState createState() => _testFirlessState();
}
class _testFirlessState extends State<testFirless> {
#override
Widget build(BuildContext context) {
return Container();
}
// ------------------------------------>heree
#override
Future<void> initState() async {
FirebaseAuth auth = FirebaseAuth.instance;
var user = await auth.currentUser;
if (user == null) {
widget.currentuseid = user!.uid;
} else {
print('User is signed in!');
}
}
}
FutureBuilder
class fbuilder extends StatelessWidget {
const fbauth({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
FirebaseAuth auth = FirebaseAuth.instance;
// --------------->
return Container(child: FutureBuilder(
builder: (BuildContext ctx, AsyncSnapshot<User> snapshot) {
if (ConnectionState.done == snapshot.connectionState) {
return Text(snapshot.data.userId.toString());
} else {
return CircularProgressIndicator();
}
},
future: auth.currentUser(),
));
}
}
in stateless or stateful widget
String currentuseid="";
class fbauth extends StatelessWidget {
const fbauth({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
FirebaseAuth auth = FirebaseAuth.instance;
// ------------------------>
auth.currentUser().then((user) {
if (user == null) {
currentuseid = user!.uid;
} else {
print('User is signed in!');
}
// other logic after the user retrieval
});
return Container();
}
}
Nb: Instead of instantiating firebase auth in every widget .you must instantiate in `void main` method
First I have initialized my camera controller (camera: ^0.9.4+11) like this and it works:
class TakePictureScreen extends StatefulWidget {
final CameraDescription camera;
const TakePictureScreen({required Key key, required this.camera})
: super(key: key);
#override
TakePictureScreenState createState() => TakePictureScreenState();
}
class TakePictureScreenState extends State<TakePictureScreen> {
late CameraController _controller;
late Future<void> _initializeControllerFuture;
#override
void initState() {
super.initState();
_controller = CameraController(
widget.camera,
ResolutionPreset.max, // TODO: this should come from SharedPreferences
);
_initializeControllerFuture = _controller.initialize();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {
return (snapshot.connectionState == ConnectionState.done)
? CameraPreview(_controller)
: Text("");
}
),
);
}
}
But now I want to load the ResolutionPreset dynamically from SharedPreferences (shared_preferences: ^2.0.13).
What's a good way to do this?
I failed when trying it like this (adding some variables and changing initState method):
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
late Future<int> _resolutionIndex;
List<ResolutionPreset> resolutions = ResolutionPreset.values.toList(growable: false);
static const String sharedPrefResolution = "resolution";
#override
void initState() {
super.initState();
_resolutionIndex = _prefs.then((SharedPreferences prefs) {
int resolutionIndex = prefs.getInt(sharedPrefResolution) ?? (resolutions.length - 1);
_controller = CameraController(
widget.camera,
resolutions[resolutionIndex],
);
_initializeControllerFuture = _controller.initialize();
return resolutionIndex;
});
}
Getting the error: LateInitializationError: Field '_initializeControllerFuture#19039262' has not been initialized.
initState can't be an async method, and getting a value from SharedPreferences is an async function. You can't use await (or then) in initState, I mean you can use but the code execution will not wait for this to complete. So what happens here is that your build method will run earlier than the future getting the value from SharedPreferences completes. And as I presume your _initializeControllerFuture is marked as late, so when your build tries to use it, it is still null, and that will get you this error.
The common way to solve this issue is to use a FutureBuilder. Get the values from SharedPreferences with FutureBuilder, display a progress indicator while it is being loaded (it will be quick so if you think you can skip this part), and then when you get the value from it, build your widget using the value coming from SharedPreferences, and initialize CameraController only after this.
I'm trying to access userEmail in shared preferences, inside my build method. Here's some of the code for context:
Widget build(BuildContext context) {
final prefs = await SharedPreferences.getInstance();
final userEmail = prefs.getString('userEmail') ?? '';
...
Return Scaffold(
body: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: StreamBuilder<QuerySnapshot>(
stream: _firestore.collection(userEmail).orderBy('time', descending: false).snapshots(),
...
The issue I have is, an error comes up highlighting the await. When i hover over it with my cursor for more info, it say The await expression can only be used in an async function. Try marking the function body with either 'async' or 'async*'.
There is then an option to add 'async' modifier. So i clicked that, which transformed code into this:
Future<Widget> build(BuildContext context) async {
...
This causes another error message: '_HomeScreenState.build' ('Future<Widget> Function(BuildContext)') isn't a valid override of 'State.build' ('Widget Function(BuildContext)').
Any ideas how to solve this issue? I've tried saving the userEmail using the Provider package. This works perfectly when the user first signs in or registers, but if you hot reload, the stream doesn't work.
You can use WidgetsBinding.instance.addPostFrameCallback, this helps you to run a callback during a frame, just after the persistent frame callbacks (which is when the main rendering pipeline has been flushed). If a frame is in progress and post-frame callbacks haven't been executed yet, then the registered callback is still executed during the frame. Otherwise, the registered callback is executed during the next frame.
In code, you can use it something like this.
#override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
final prefs = await SharedPreferences.getInstance();
final userEmail = prefs.getString('userEmail') ?? '';
});
Hope this answers your question.
Long Story short you should not perform any side effects inside your build method . See here
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late final prefs ;
#override
void initState() async{
super.initState();
prefs = await SharedPreferences.getInstance();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: prefs,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if(snapshot.hasData){}else if (snapshot.hasError)
return Center(child: CircularProgressIndicator());
},);
}
}
As your build method can be called multiple times you should not perform network calls or call complex methods because as the docs say. This method can and will be called multiple times.
In your case I used a FutureBuilder to handle the future's state and awaited it in the initState insida a stateful widget.
Check this article for more info
As #croxx5f and #AhmetKAYGISIZ suggested, I ended up using FutureBuilder to solve this problem. Thank you both so much for your help with this.
Here's the final code for anyone else who is stuck on this problem:
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
var prefs;
#override
void initState() {
super.initState();
getUserEmailFromSharedPrefs();
}
Future<String> getUserEmailFromSharedPrefs() async {
prefs = await SharedPreferences.getInstance();
final userEmail = prefs.getString('userEmail') ?? '';
return userEmail;
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getUserEmailFromSharedPrefs(),
builder: (context, AsyncSnapshot<String> snapshot) {
if(snapshot.hasData) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: StreamBuilder<QuerySnapshot>(
stream: _firestore.collection(snapshot.data).orderBy('time', descending: false).snapshots(),
...
So in summary, I wrapped my streambuilder in a futurebuilder.
I want to use the variable dbRef in inputData() in future Builder builder: you can see the variable in between asterisk .
void inputData() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
final uid = user.uid;
final **dbRef** = FirebaseDatabase.instance.reference().child("Add Job Details").child(uid).child("Favorites");
}
#override
Widget build(BuildContext context) {
return FutureBuilder (
future: **dbRef**.once(),
builder: (context, AsyncSnapshot<DataSnapshot> snapshot) {
if (snapshot.hasData) {
List<Map<dynamic, dynamic>> list = [];
for (String key in snapshot.data.value.keys) {
list.add(snapshot.data.value[key]);
}
This is one more approach to tackle the problem.
The idea is to use a variable _loading and set it to true initially.
Now, after in your inputData() function, you can set it to false once you get the dbref.
Store dbref, the way I stored _myFuture in the code below i.e., globally within the class.
Use your _loading variable to return a progress bar if its true else return FutureBuilder with your dbref.once() in place. Now, that you have loaded it, it should be available at this point.
class MyWidget extends StatefulWidget {
#override
createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
// Is the future being loaded?
bool _loading;
// This is the future we will be using in our FutureBuilder.
// It is currently null and we will assign it in _loadMyFuture function.
// Until assigned, we will keep the _loading variable as true.
Future<String> _myFuture;
// Load the _myFuture with the future we are going to use in FutureBuilder
Future<void> _loadMyFuture() async {
// Fake the wait for 2 seconds
await Future.delayed(const Duration(seconds: 2));
// Our fake future that will take 2 seconds to return "Hello"
_myFuture = Future(() async {
await Future.delayed(const Duration(seconds: 2));
return "Hello";
});
}
// We initialize stuff here. Remember, initState is called once in the beginning so hot-reload wont make flutter call it again
#override
initState() {
super.initState();
_loading = true; // Start loading
_loadMyFuture().then((x) => setState(() => _loading = false)); // Set loading = false when the future is loaded
}
#override
Widget build(BuildContext context) {
// If loading, show loading bar
return _loading?_loader():FutureBuilder<String>(
future: _myFuture,
builder: (context, snapshot) {
if(!snapshot.hasData) return _loader(); // still loading but now it's due to the delay in _myFuture
else return Text(snapshot.data);
},
);
}
// A simple loading widget
Widget _loader() {
return Container(
child: CircularProgressIndicator(),
width: 30,
height: 30
);
}
}
Here is the output of this approach
This does the job but, you might need to do it for every class where you require your uid.
========================================
Here is the approach I described in the comments.
// Create a User Manager like this
class UserManager {
static String _uid;
static String get uid => _uid;
static Future<void> loadUID() async {
// Your loading code
await Future.delayed(const Duration(seconds: 5));
_uid = '1234'; // Let's assign it directly for the sake of this example
}
}
In your welcome screen:
class MyWidget extends StatefulWidget {
#override
createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
bool _loading = true;
#override
void initState() {
super.initState();
UserManager.loadUID().then((x) => setState(() => _loading = false));
}
#override
Widget build(BuildContext context) {
return _loading ? _loader() : Text('Welcome User ${UserManager.uid}!');
}
// A simple loading widget
Widget _loader() {
return Container(child: CircularProgressIndicator(), width: 30, height: 30);
}
}
The advantage of this method is that once you have loaded the uid, You can directly access it like this:
String uid = UserManager.uid;
thus eliminating use of futures.
Hope this helps!
I have 3 classes: Users, Posts and Comments. User has many Posts and
Posts has many Comments.
I want that all data to be fetched before the widget's build method is called.
I tryed to use initState() to do this:
class FetchDataExample extends StatefulWidget {
final User _user;
FetchDataExample(this._user);
#override
_State createState() => _State(_user);
}
class _State extends State<FetchDataExample> {
final User _user;
_State(this._user);
#override
void initState() {
_user.setPosts();
super.initState();
}
#override
Widget build(BuildContext context) {
print(this._user.posts[0]);
return Container(
);
}
}
In User class I have:
void setPosts() async {
String url = 'https://jsonplaceholder.typicode.com/posts?userId=' + this.id.toString();
var request = Requester.get(url); // Returns a Future<Response>
await request.then((value) => this.posts = Post.jsonToPosts(json.decode(value.body)));
this.posts.forEach((post) => post.setComments());
print(this.posts[0]);
}
The 'setComments()' has the same logic.
I have two prints:
Inside build that returns null;
Inside setPosts the returns Instance of 'Post';
So, by the time that Build method is called in the widget, the initState has not finished yet.
I need it be finished, does anyone know how can I do that?
You can use a FutureBuilder to build a widget by using latest result from a future.
And also you can combile multiple futures into a single one using Future.wait method.
Here is a sample code:
_getPageData() async {
var _combinedFutures = await Future.wait([setPosts, setComments]);
//do stuff with data
}
...
#override
Widget build(BuildContext context) {
return FutureBuilder(
future:_getPageData(),
builder: (context, snapshot) {
return Container();
}),
);
});