"await" in Widget build FLUTTER - flutter

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

Related

Flutter Custom State Management

What I am trying to achieve is a small custom state management solution that I believe is powerful enough to run small and large apps. The core is based on the ValueNotifier and ValueListenable concepts in flutter. The data can be accessed anywhere in the app with out context since I am storing the data like this:
class UserData {
static ValueNotifier<DataLoader<User>> userData =
ValueNotifier(DataLoader<User>());
static Future<User> loadUserData() async {
await Future.delayed(const Duration(seconds: 3));
User user = User();
user.age = 23;
user.family = 'Naoushy';
user.name = 'Anass';
return user;
}
}
So by using UserData.userData you can use the data of the user whenever you want. Everything works fine until I encountered a problem of providing a child to my custom data consumer that rebuilds the widget when there is a new event fired. The DataLoader class looks like this:
enum Status { none, hasError, loading, loaded }
class DataLoader<T> {
Status status = Status.none;
T? data;
Object? error;
bool get hasError => error != null;
bool get hasData => data != null;
}
which is very simple. Now the class for consuming the data and rebuilding looks like this:
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:testing/utils/dataLoader/data_loader.dart';
class DataLoaderUI<T> extends StatefulWidget {
final ValueNotifier<DataLoader<T>> valueNotifier;
final Widget noneStatusUI;
final Widget hasErrorUI;
final Widget loadingUI;
final Widget child;
final Future<T> future;
const DataLoaderUI(
{Key? key,
required this.valueNotifier,
this.noneStatusUI = const Text('Data initialization has not started'),
this.hasErrorUI = const Center(child: Text('Unable to fetch data')),
this.loadingUI = const Center(
child: CircularProgressIndicator(),
),
required this.child,
required this.future})
: super(key: key);
#override
State<DataLoaderUI> createState() => _DataLoaderUIState();
}
class _DataLoaderUIState extends State<DataLoaderUI> {
Future startLoading() async {
widget.valueNotifier.value.status = Status.loading;
widget.valueNotifier.notifyListeners();
try {
var data = await widget.future;
widget.valueNotifier.value.data = data;
widget.valueNotifier.value.status = Status.loaded;
widget.valueNotifier.notifyListeners();
} catch (e) {
log('future error', error: e.toString());
widget.valueNotifier.value.error = e;
widget.valueNotifier.value.status = Status.hasError;
widget.valueNotifier.notifyListeners();
}
}
#override
void initState() {
super.initState();
log('init state launched');
if (!widget.valueNotifier.value.hasData) {
log('reloading or first loading');
startLoading();
}
}
//AsyncSnapshot asyncSnapshot;
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<DataLoader>(
valueListenable: widget.valueNotifier,
builder: (context, dataLoader, ui) {
if (dataLoader.status == Status.none) {
return widget.noneStatusUI;
} else if (dataLoader.status == Status.hasError) {
return widget.hasErrorUI;
} else if (dataLoader.status == Status.loading) {
return widget.loadingUI;
} else {
return widget.child;
}
});
}
}
which is also simple yet very effective. since even if the initState function is relaunched if the data is already fetched the Future will not relaunch.
I am using the class like this:
class TabOne extends StatefulWidget {
static Tab tab = const Tab(
icon: Icon(Icons.upload),
);
const TabOne({Key? key}) : super(key: key);
#override
State<TabOne> createState() => _TabOneState();
}
class _TabOneState extends State<TabOne> {
#override
Widget build(BuildContext context) {
return DataLoaderUI<User>(
valueNotifier: UserData.userData,
future: UserData.loadUserData(),
child: Text(UserData.userData.value.data!.name??'No name'));
}
}
The error is in this line:
Text(UserData.userData.value.data!.name??'No name'));
Null check operator used on a null value
Since I am passing the Text widget as an argument with the data inside it. Flutter is trying to pass it but not able to since there is no data yet so its accessing null values. I tried with a normal string and it works perfectly. I looked at the FutureBuilder widget and they use a kind of builder and also the ValueLisnableBuilder has a builder as an arguement. The problem is that I am not capable of creating something like it for my custom solution. How can I just pass the child that I want without having such an error and without moving the ValueLisnable widget into my direct UI widget?
I have found the solution.
Modify the DataLoaderUI class to this:
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:testing/utils/dataLoader/data_loader.dart';
class DataLoaderUI<T> extends StatefulWidget {
final ValueNotifier<DataLoader<T>> valueNotifier;
final Widget noneStatusUI;
final Widget hasErrorUI;
final Widget loadingUI;
final Widget Function(T? snapshotData) child;
final Future<T> future;
const DataLoaderUI(
{Key? key,
required this.valueNotifier,
this.noneStatusUI = const Text('Data initialization has not started'),
this.hasErrorUI = const Center(child: Text('Unable to fetch data')),
this.loadingUI = const Center(
child: CircularProgressIndicator(),
),
required this.child,
required this.future})
: super(key: key);
#override
State<DataLoaderUI<T>> createState() => _DataLoaderUIState<T>();
}
class _DataLoaderUIState<T> extends State<DataLoaderUI<T>> {
Future startLoading() async {
widget.valueNotifier.value.status = Status.loading;
widget.valueNotifier.notifyListeners();
try {
var data = await widget.future;
widget.valueNotifier.value.data = data;
widget.valueNotifier.value.status = Status.loaded;
widget.valueNotifier.notifyListeners();
} catch (e) {
log('future error', error: e.toString());
widget.valueNotifier.value.error = e;
widget.valueNotifier.value.status = Status.hasError;
widget.valueNotifier.notifyListeners();
}
}
#override
void initState() {
super.initState();
log('init state launched');
if (!widget.valueNotifier.value.hasData) {
log('reloading or first loading');
startLoading();
}
}
//AsyncSnapshot asyncSnapshot;
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<DataLoader<T>>(
valueListenable: widget.valueNotifier,
builder: (context, dataLoader, ui) {
if (dataLoader.status == Status.none) {
return widget.noneStatusUI;
} else if (dataLoader.status == Status.hasError) {
return widget.hasErrorUI;
} else if (dataLoader.status == Status.loading) {
return widget.loadingUI;
} else {
return widget.child(dataLoader.data);
}
});
}
}
and use it like this:
DataLoaderUI<User>(
valueNotifier: UserData.userData,
future: UserData.loadUserData(),
child: (user) {
return Text(user!.name ?? 'kk');
});
Take a look at my version of the same sort of state management approach here: https://github.com/lukehutch/flutter_reactive_widget

Async Data Initialization in initState

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();
});
}

Flutter: Stateful Widget does not update

Imagine two Widgets: Main that manages a tabbar and therefore holds several Widgets - and Dashboard.
On Main Constructor I create a first Instance of Dashboard and the other tabbar Widgets with some dummy data (they are getting fetched in the meanwhile in initState). I build these with Futurebuilder. Once the data arrived I want to create a new Instance of Dashboard, but it won't change.
class _MainState extends State<HomePage> {
var _tabs = <Widget>[];
Future<dynamic> futureData;
_MainState() {
_tabs.add(Dashboard(null));
}
#override
void initState() {
super.initState();
futureData = _getData();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: futureData,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data != null) {
tabs[0] = Dashboard(snapshot.data);
} else {
return CircularProgressIndicator();
}
});
}
}
class DashboardScreen extends StatefulWidget {
final data;
DashboardScreen(this.data,
{Key key})
: super(key: key) {
print('Dashboard Constructor: ' + data.toString());
}
#override
_DashboardScreenState createState() => _DashboardScreenState(data);
}
class _DashboardScreenState extends State<DashboardScreen> {
var data;
_DashboardScreenState(this.data);
#override
void initState() {
super.initState();
print('InitState: ' + data.toString());
}
#override
void didUpdateWidget(Widget oldWidget) {
super.didUpdateWidget(oldWidget);
print('didUpdateWidget');
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
print('didChangeDependencies' + data.toString());
}
#override
Widget build(BuildContext context) {
return Text(data.toString());
}
}
When I print on several available methods it comes clear that the DasboardScreenState is not recreated. Only the DashboardScreen Constructor is called again when the data arrived, but not it's state...
flutter: MainConstructor: null
flutter: Dashboard Constructor: null
flutter: InitState: null
flutter: didChangeDependencies: null
flutter: Dashboard Constructor: MachineStatus.Manual <- Here the data arrived in futureBuilder
How can I force the State to recreate? I tried to use the key parameter with UniqueKey(), but that didn't worked. Also inherrited widget seems not to be the solution either, despite the fact that i don't know how to use it in my use case, because the child is only available in the ..ScreenState but not the updated data..
I could imagine to inform dashboardScreenState by using Stream: listen to messages and then call setState() - I think, but that's only a workaround.
Can anyone help me please :)?
I know I have had issues with the if statement before, try:
return FutureBuilder(
future: futureData,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) { //use hasData
DataType data = snapshot.data; //Declare Values first
tabs[0] = Dashboard(data);
} else {
return CircularProgressIndicator();
}
});

Check constraint and return the body accordingly

I want to show onboarding screen only for the first time user opens the application, so at the final page of Onboarding screen I put OnBoardingStatus value to be "Done" and move to the main screen. But when user opens the application for the next time this code flash the Onboarding screen for few milliseconds and then opens the mainScreen.
Here is my code
class App2 extends StatefulWidget {
App2({Key key}) : super(key: key);
#override
_App2State createState() => _App2State();
}
class _App2State extends State<App2> {
String onBoardingStatus;
#override
void initState() {
// TODO: implement initState
getOnBoardingStatus();
super.initState();
}
Future<void> getOnBoardingStatus() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
var onboardingstatus = prefs.getString('OnBoardingStatus');
setState(() {
onBoardingStatus = onboardingstatus;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: onBoardingStatus != null
? MainScreen()
: OnboardingScreen());
}
}
Currently you have no way to know if onBoardingStatus is null because the SharedPreferences instance hasn't been retrieved yet, or because the OnBoardingStatus really is empty. You can work around this with a FutureBuilder:
class App2 extends StatelessWidget {
App2({Key key}) : super(key: key);
Future<String> getOnBoardingStatus() async =>
(await SharedPreferences.getInstance()).getString('OnBoardingStatus');
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getOnBoardingStatus(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
//TODO: Return a widget that indicates loading
}
return Scaffold(
body: snapshot.data != null
? MainScreen()
: OnboardingScreen());
},
);
}
}
However I don't think it's the best solution. For starters, App2 should get the status from an outer source - this way if you ever decide to change your storage solution you wouldn't need to touch App2.

Flutter: How to make a sequence of http requests on a widget before build method

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();
}),
);
});