I'm trying to load in the details of an employee that I saved in storage via SharedPreferences before making any requests to my API to make requests.
This is the last thing I have tried
#override
void initState() {
super.initState();
WidgetsBinding.instance?.addPostFrameCallback((_) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
loggedInEmployee =
Employee.fromJson(jsonDecode(prefs.getString("employee")!));
});
_loadDayTemplates();
}
Other things I have tried include making a Future function that returns the Employee object and put that in my initState() function, as well as trying to pass through the employee object from SharedPreferences through from a previous page. None of these solutions have worked so far unfortunately, the page always seems to be loaded in before the employee object is actually present.
Are there any other things I could try to fix this?
initState call before building 1st frame and we have a addPostFrameCallback to load data. But _loadDayTemplates() method is outside the scope of callback. means it will only load on initState and wont refresh on addPostFrameCallback.
To load data and refresh the we need to include _loadDayTemplates() and use setState inside addPostFrameCallback.
#override
void initState() {
super.initState();
WidgetsBinding.instance?.addPostFrameCallback((_) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
loggedInEmployee =
Employee.fromJson(jsonDecode(prefs.getString("employee")!));
_loadDayTemplates(); // reload the templates
setState((){}); refresh the UI
});
_loadDayTemplates();
}
Related
You can see the strange behaviour in this video: https://streamable.com/r5ld2y
The InitValue is the correct one, but when I restart the App it first goes to zero, AFTER loading the Screen OR press a button, it loads the shared prefs...
This is my Cubit (Only with the LoadCounter Func):
class DrinkCubit extends Cubit<DrinkState> {
DrinkCubit() : super(DrinkState(drinkValue: 0));
Future<void> loadCounter() async {
final prefs = await SharedPreferences.getInstance();
state.drinkValue = (prefs.getInt('counter') ?? 0);
}
}
And this is my InitFunction in the main window!
#override
void initState() {
super.initState();
Future.delayed(Duration.zero,()
{
BlocProvider.of<DrinkCubit>(context).loadCounter();
});
}
So how do I fix this, that the correct value is directly after starting the app showed
Try this:
getData(){
BlocProvider.of<DrinkCubit>(context).loadCounter();
}
#override
void initState() {
SchedulerBinding.instance.addPostFrameCallback((_) {
getData();
});
super.initState();
}
SchedulerBinding.instance.addPostFrameCallback ensures that code inside run before UI code.
And if it allows me to give you a hint, is better remove SharedPreferences of your Bloc and put in another class.
Structure of code:
I have a function, that on a Button click returns a stateful widget A.
In a separate file, I have a ChangeNotifier class B, which A
needs (it needs the decoded json file) and I use the ChangeNotifier class as a general memory container which all the different widgets have access to.
B:
class Database_Controller extends ChangeNotifier {
Future<void> _readVeryLargeJsonFrame() async {
final String response =
await rootBundle.loadString('assets/tables/largejson.json');
final data = await json.decode(response);
return data;
}
Other functions to give back entries of data
}
Problem:
I would like to execute _readVeryLargeJsonFrame as soon as A is called (potentially with a loading spinner for the user) and before A is loaded (or at least in parallel).
How do I call the ChangeNotifier function in the "init" part of a stateful widget? Peter Koltai mentioned the initState method. But how do I call the ChangeNotifier function from this?
(2. Context problem: So far, I would be using Provider.of<Database_Controller>(context,listen: false)._readVeryLargeJsonFrame(); but how do I get the context argument here?)
(3. Is the Future<void> ... async nature of the _readVeryLargeJsonFrame function a problem here?)
void initState() {
super.initState();
WidgetsBinding.instance
.addPostFrameCallback((_) => yourFunction(context));
}
This is my controller.dart file which checks if users is verified or not and then return the page according to the conditions.
My question is that why the build widget is executing first before initState() ? I tried to debug this code using breakpoints and noticed that build() widget is running first and then the initState()Why this is happening and how could I fix it ?
This is my code :
class _ControllerState extends State<Controller> {
late bool auth;
#override
Widget build(BuildContext context) {
return (auth==false) ? Onbording() : IndexPage();
}
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((_) async {
await this.checked_if_logged();
});
}
Future<void> checked_if_logged() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
if(prefs.getBool('verified')==true){
setState(() {
auth = true;
});
}else{
setState(() {
auth = false;
});
}
}
}
This is a snapshot of my debug code where the blue line is showing that it runs first before init and because the bool auth is a late type so it throws lateInitializationErrror and after that initState() is called which initializes the auth variable which rebuild the widget and removes the error
Update:
I noticed that when I replace the WidgetsBinding.instance!.addPostFrameCallback((_) with just check_if_logged(), the initState() is calling first but before completion of check_if_logged() the build widget executes first which again throws lateInitializationError
I don't know where you got addPostFrameCallback from or what you want to achieve, but this is not the way.
Your problem is, that checked_if_logged is async and there is no way to await an async method in initState. That is by design and there is no way around that.
The proper way to handle this is to use a FutureBuilder widget.
See What is a Future and how do I use it?
I am adding some data into the SharedPreferenceson page2 of my app and I am trying to retrieve the data on the homepage. I have used an init function on page 1 as follows:
#override
void initState() {
super.initState();
_getrecent();
}
void _getrecent() async {
final prefs = await SharedPreferences.getInstance();
// prefs.clear();
String b = prefs.getString("recent").toString();
Map<String, dynamic> p = json.decode(b);
if (b.isNotEmpty) {
print("Shared pref:" + b);
setState(() {
c = Drug.fromJson(p);
});
cond = true;
} else {
print("none in shared prefs");
cond = false;
}
}
Since the initState() loads only once, I was wondering if there was a way to load it every time page1 is rendered. Or perhaps there is a better way to do this. I am new to flutter so I don't have a lot of idea in State Management.
you can override didChangeDependencies method. Called when a dependency of the [State] object changes as you use the setState,
#override
void didChangeDependencies() {
// your codes
}
Also, you should know that using setState updates the whole widget, which has an overhead. To avoid that you should const, declaring a widget const will only render once so it's efficient.
First thing is you can't force initState to rebuild your widget.
For that you have setState to rebuild your widget. As far as I can
understand you want to recall initState just to call _getrecent()
again.
Here's what you should ideally do :
A simple solution would be to use FutureBuilder. You just need to use _getrecent() in FutureBuilder as parent where you want to use the data you get from _getrecent(). This way everytime your Widget rebuilds it will call _getrecent().
You simply use setState() methode if you want to update widget. Here's documentation for this https://api.flutter.dev/flutter/widgets/State/setState.html
init function will render it only once initially, to avoid that -
You can use setState( ) method to re-render your whole widget.
Im trying to set up the basic user sign up, login ,edit profile, view profile pages. Currently the signup, login and edit profile work fine and i can write to the firestore. However im having some issues with retrieving the data, ive look at the code on how to retrieve data but its not working for my code. I have a user class that gets the users current info and sets some variables to that so that i can only get the info from database once. However, when i try and construct a user inside the profile page class and get the info, what happens is it skips the get info method because its async since the getCurrentUser nested inside is async because the auth.currentuser method needs to be async.
So i understand what the problem is and i tested it out and got values that prove this in the console but how do i fix this? What changes can i make in my code to be able to not skip getting the info before calling to get the info and returning null? I am new to flutter so i am sorry if this may be obvious, any help is greatly appreciated thank you,
Also my code works to get the specific values from the database, so dont worry about that if its not related since it shows up correctly in the console but after being skipped since its async.
These are the methods i mentioned that are present INSIDE the User class:
final _firestore = Firestore.instance;
final _auth= FirebaseAuth.instance;
FirebaseUser loggedInUser;
Future<void> getCurrentUser() async{
try{
final user= await _auth.currentUser();
if(user!=null){
loggedInUser=user;
email=loggedInUser.email;
}}
catch(e){
print(e);
}
}
void getInfo() async {
await getCurrentUser();
DocumentReference documentReference =
_firestore.collection("users").document("$email");
documentReference.get().then((DocumentSnapshot datasnapshot) {
if (datasnapshot.exists) {
displayName=datasnapshot.data['displayName'].toString();
bio=datasnapshot.data['bio'].toString();
print(bio);
}
else {
print("No such user");
}
});
}
As you can see getCurrentUser needs to be async. I have other variables and methods and constructors but i tried sharing only what needs to be shown.
Then i have this code that is inside the profilepagestate:
User currentUser;
String bio;
#override
void initState() {
// TODO: implement initState
super.initState();
currentUser= new User();
currentUser.getInfo();
bio= currentUser.getBio();
}
Then further down the profile page i create a text widget:
Text('$bio', style: TextStyle(fontSize: 30.0, ))
However, the text always shows null on the screen, and i understand whats happening but i dont know how to fix it. I've tried switching things around making something not async or atleast trying but i cant figure it out. Please do not delete my post, ive looked around and this question is specific to my code i really do not know how to fix this i just dont want to waste any more time.
You need to show an alternative widget (commonly a loading indicator) while the data is still loading.
return bio == null
? CircularProgressIndicator()
: Text('$bio', style: TextStyle(fontSize: 30.0, ));
UPDATE
Your loading indicator stucks because your page never gets rebuilt when the data is loaded. You need to rebuild the page by calling setState when the currentUser data is done loading.
#override
void initState() {
// TODO: implement initState
super.initState();
currentUser= new User();
currentUser.getInfo().then((_) =>
setState(() => bio = currentUser.getBio()));
}
To be able to chain the async call, you need to switch the return value of getInfo() from void to Future<void>.
Future<void> getInfo() async {
await getCurrentUser();
// ..
}