Flutter Cubit InitState - flutter

I am at the begin of my Cubit learning and i tried to create a "Liter-Tracker" with sharedPrefs. Everything works but not the init state. I have to press a Button first because I initialize the drinkValue with 0. I tried to return an Int with the value from shared prefs but this dont work :( May you help me?
This is my cubit:
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);
}
Future<int> loadInitCounter() async {
final prefs = await SharedPreferences.getInstance();
return state.drinkValue = (prefs.getInt('counter') ?? 0);
}
}
and this my cubit state:
class DrinkState {
int drinkValue;
int? amount;
DrinkState({
required this.drinkValue,
});
}
I also tried something like this in my MainPage, how i usually init my state with setState:
#override
void initState() {
super.initState();
BlocProvider.of<DrinkCubit>(context).loadCounter();
}

Context is not accessible in initstate, try using didChangeDependencies life cycle method Flutter get context in initState method

Firstly, I strongly advise you to avoid the StatefulWidget when you use BLoC, but it doesn't mean you can't use it at all. Just be careful because setState() can rebuild BlocProvider inside the stateful widget.
As for the initialization process, I suggest you use this approach on the BlocProvider.
class DrinkScreen extends StatelessWidget {
const DrinkScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => DrinkCubit()..loadCounter(), // Or call other initialization method
child: DrinkView(),
);
}
}
This approach works really well if you reuse this screen multiple times, for example, you redirect to DrinkScreen every time you want to fill data and you dispose of the screen afterward (Navigate.pop(), etc). This way you can automatically initialize the cubit every time you redirect into this screen, you don't need to use StatefulWidget to init the cubit.

Related

Flutter run a function before the buildcontext with FutureBuilder

The problem I have is that I want to run a function before the buildcontext is rendered.
What the function does is read the storage to get the token. The function works with Async Await.
Why do I want to do this?
Because in the build context I have a FutureBuilder and in this I need token data.
class ProfileScreen extends StatefulWidget {
ProfileScreen({Key? key}) : super(key: key);
#override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
dynamic token;
getToken()async{
token= await decodeJwt();
print("first this");
print(token);
return token;
}
#override
void initState() {
getToken();
super.initState();
}
#override
Widget build(BuildContext context) {
print("after this");
final profileService=Provider.of<ProfileService>(context);
final listDropDownService=Provider.of<ListDropDown>(context);
return FutureBuilder(
future: Future.wait([profileService.getSpecificWalker(token["id"]), listDropDownService.getDropDown("services")]),
builder: (context,dynamic snapshot){
as you can see i used some print also to see what the sequence was like.
But first it prints "then this" and then "first this"
My strongest suggestion would be to use some state management solution, such as flutter_bloc, that is not directly dependent on the UI components.
If not, then why don't you just try and wrap the getToken() method and the profileService.getSpecificWalker(token["id"]) call in a single async method and call that from the FutureBuilder.
What you said shows that you need state manager, because in one state you want to get token then when token get available, call the futureBuilder. I recommended bloC.

How can i call my provider model into initState method

i have several widgets use my provider as a condition , and i need one call to access my provider to whole widget from init state instead of wrapping every widget into my provider and it's consumer
this is my provider
class ProviderForFiltter extends ChangeNotifier {
bool isFiltterrr = true ;
bool get isFiltter => isFiltterrr;
void changeStatus(bool status){
isFiltterrr = status;
notifyListeners();
}
}
this is my main.dart
class Myproject extends StatefulWidget {
const Myproject ({Key? key}) : super(key: key);
#override
_Myproject State createState() => _Myproject State();
}
class _Myproject State extends State<Myproject > {
#override
Widget build(BuildContext context) {
return
Provider(
create: (BuildContext context) {
return ProviderForFiltter();
},
child: const MaterialApp(
debugShowCheckedModeBanner: false,
home: WelcomeScreen()
),
),
);
}
}
this is my Stful Widget
ProviderForFiltter? isF ;
#override
void initState() {
super.initState();
// i tried this but it always give me errors that is isF null value
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
isF = context.read<ProviderForFiltter>();
});
// also itried this but it don't work
isF = Provider.of<ProviderForFiltter>(context, listen: false);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Text('change'),
)
}
}
in the fact i need to use it's bool value as condition into Consumer and change it
i hope any help guys
is better don't do use Provider in initState, but you can use Future.delayed
because you need context
#override
void initState() {
super.initState();
// i tried this but it always give me errors that is isF null value
Future.delayed(Duration(seconds: 1), () {
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
isF = context.read<ProviderForFiltter>();
});
// also itried this but it don't work
isF = Provider.of<ProviderForFiltter>(context, listen: false);
});
}
providers need context, in order to access it for one time you should override didChangeDependencies
#override
void didChangeDependencies() {
super.didChangeDependencies();
///access provider here and update your state if needed,
///this will be called one time just before the build method
**isF = Provider.of<ProviderForFiltter>(context, listen: false);**
}
There are multiple ways to deal with this.
The first option which I use is to add a Post Frame Callback like so:
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
aProvider = Provider.of< aProvider >(context, listen: false);
});
Alternatively, you could override the didChangeDependencies method to get the provider value once initState has been called - remembering to set the listen value to false.
I was facing the same issue and regarding the documentation of provider this should be the answer.
"This likely happens because you are modifying the ChangeNotifier from
one of its descendants while the widget tree is building."
In my case i am calling an http api async where the future is stored inside the notifier. So i have to update like this and it is working.
initState() {
super.initState();
Future.microtask(() =>
context.read<MyNotifier>().fetchSomething(someValue);
);
}
The best way is to use like this (when there's no "external parameter".
class MyNotifier with ChangeNotifier {
MyNotifier() {
_fetchSomething();
}
Future<void> _fetchSomething() async {}
}
source : https://pub.dev/packages/provider
You can use a different method called didChangeDependencies to get the value from the provider after the initState method is called. Also, make sure to set the listen value to false.
#override
void didChangeDependencies() {
super.didChangeDependencies();
final filtterData = Provider.of<ProviderForFiltter>(context, listen: false);
}

setState is not updating the UI even though state is updating

I am trying to wait till amplify configuration is done then load the login screen. Even though state seems to be getting updated I am still getting the loadinscreen. Why is that?
I am not sure if setState is proper method on the init : Importance of Calling SetState inside initState
As per the doc : https://docs.amplify.aws/start/getting-started/integrate/q/integration/flutter/#configure-amplify
Future<void> main() async {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _isAmplifyConfigured = false;
late AmplifyAuthCognito auth;
#override
void initState() {
_initializeApp();
super.initState();
}
Future<void> _initializeApp() async {
await _configureAmplify();
setState(() {
_isAmplifyConfigured = true;
});
}
Future<void> _configureAmplify() async {
auth = AmplifyAuthCognito();
try {
await Amplify.addPlugin(auth);
await Amplify.configure(amplifyconfig);
} on AmplifyAlreadyConfiguredException {
print(
'Amplify was already configured. Looks like app restarted on android.');
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: AppRoutes.onGenerateRoute,
initialRoute: _isAmplifyConfigured
? LoginScreen.routeName
: LoadingScreen.routeName,
);
}
}
I think the issue is with you trying to reassign your initialRoute. I'm not super familiar with this property, but given the name I assume this is set once and is not rebuilt, not even when the state changes. It would make sense, also, because the rest of your code sounds like it should work.
Before trying anything else, I'd recommend you move your logic to initialize Amplify to the LoginScreen, and having its body depend on the _isAmplifyConfigured boolean value. So show spinner if it's false, and show Login fields when it's true.
Even better would be to create a HomeScreen, so you can keep this Amplify initialization at the bottom of your app's stack. And then have your HomeScreen either show the Login widgets, the home screen of your app, or a loading state.

How to reload the page whenever the page is on screen - flutter

Is there any callbacks available in flutter for every time the page is visible on screen? in ios there are some delegate methods like viewWillAppear, viewDidAppear, viewDidload.
I would like to call a API call whenever the particular page is on-screen.
Note: I am not asking the app states like foreground, backround, pause, resume.
Thank You!
Specifically to your question:
Use initState but note that you cannot use async call in initState because it calls before initializing the widget as the name means. If you want to do something after UI is created didChangeDependencies is great. But never use build() without using FutureBuilder or StreamBuilder
Simple example to demostrate:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(MaterialApp(home: ExampleScreen()));
}
class ExampleScreen extends StatefulWidget {
ExampleScreen({Key key}) : super(key: key);
#override
_ExampleScreenState createState() => _ExampleScreenState();
}
class _ExampleScreenState extends State<ExampleScreen> {
List data = [];
bool isLoading = true;
void fetchData() async {
final res = await http.get("https://jsonplaceholder.typicode.com/users");
data = json.decode(res.body);
setState(() => isLoading = false);
}
// this method invokes only when new route push to navigator
#override
void initState() {
super.initState();
fetchData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: isLoading
? CircularProgressIndicator()
: Text(data?.toString() ?? ""),
),
);
}
}
Some lifecycle method of StatefulWidget's State class:
initState():
Describes the part of the user interface represented by this widget.
The framework calls this method in a number of different situations:
After calling initState.
After calling didUpdateWidget.
After receiving a call to setState.
After a dependency of this State object changes (e.g., an InheritedWidget referenced by the previous build changes).
After calling deactivate and then reinserting the State object into the tree at another location.
The framework replaces the subtree below this widget with the widget
returned by this method, either by updating the existing subtree or by
removing the subtree and inflating a new subtree, depending on whether
the widget returned by this method can update the root of the existing
subtree, as determined by calling Widget.canUpdate.
Read more
didChangeDependencies():
Called when a dependency of this State object changes.
For example, if the previous call to build referenced an
InheritedWidget that later changed, the framework would call this
method to notify this object about the change.
This method is also called immediately after initState. It is safe to
call BuildContext.dependOnInheritedWidgetOfExactType from this method.
Read more
build() (Stateless Widget)
Describes the part of the user interface represented by this widget.
The framework calls this method when this widget is inserted into the
tree in a given BuildContext and when the dependencies of this widget
change (e.g., an InheritedWidget referenced by this widget changes).
Read more
didUpdateWidget(Widget oldWidget):
Called whenever the widget configuration changes.
If the parent widget rebuilds and request that this location in the
tree update to display a new widget with the same runtimeType and
Widget.key, the framework will update the widget property of this
State object to refer to the new widget and then call this method with
the previous widget as an argument.
Read more
Some widgets are stateless and some are stateful. If it's a stateless widget, then only values can change but UI changes won't render.
Same way for the stateful widget, it will change for both as value as well as UI.
Now, will look into methods.
initState(): This is the first method called when the widget is created but after constructor call.
#override
void initState() {
// TODO: implement initState
super.initState();
}
didChangeDependecies() - Called when a dependency of this State object changes.Gets called immediately after initState method.
#override
void didChangeDependencies() {
super.didChangeDependencies();
}
didUpdateWidget() - It gets called whenever widget configurations gets changed. Framework always calls build after didUpdateWidget
#override
void didUpdateWidget (
covariant Scaffold oldWidget
)
setState() - Whenever internal state of State object wants to change, need to call it inside setState method.
setState(() {});
dispose() - Called when this object is removed from the tree permanently.
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
You don't need StatefulWidget for calling the api everytime the screen is shown.
In the following example code, press the floating action button to navigate to api calling screen, go back using back arrow, press the floating action button again to navigate to api page.
Everytime you visit this page api will be called automatically.
import 'dart:async';
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => ApiCaller())),
),
);
}
}
class ApiCaller extends StatelessWidget {
static int counter = 0;
Future<String> apiCallLogic() async {
print("Api Called ${++counter} time(s)");
await Future.delayed(Duration(seconds: 2));
return Future.value("Hello World");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Api Call Count: $counter'),
),
body: FutureBuilder(
future: apiCallLogic(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) return const CircularProgressIndicator();
if (snapshot.hasData)
return Text('${snapshot.data}');
else
return const Text('Some error happened');
},
),
);
}
}
This is the simple code with zero boiler-plate.
The simplest way is to use need_resume
1.Add this to your package's pubspec.yaml file:
dependencies:
need_resume: ^1.0.4
2.create your state class for the stateful widget using type ResumableState instead of State
class HomeScreen extends StatefulWidget {
#override
HomeScreenState createState() => HomeScreenState();
}
class HomeScreenState extends ResumableState<HomeScreen> {
#override
void onReady() {
// Implement your code inside here
print('HomeScreen is ready!');
}
#override
void onResume() {
// Implement your code inside here
print('HomeScreen is resumed!');
}
#override
void onPause() {
// Implement your code inside here
print('HomeScreen is paused!');
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text('Go to Another Screen'),
onPressed: () {
print("hi");
},
),
),
);
}
}
If you want to make an API call, then you must be (or really should be) using a StatefulWidget.
Walk through it, let's say your stateful widget receives some id that it needs to make an API call.
Every time your widget receives a new id (including the first time) then you need to make a new API call with that id.
So use didUpdateWidget to check to see if the id changed and, if it did (like it does when the widget appears because the old id will be null) then make a new API call (set the appropriate loading and error states, too!)
class MyWidget extends StatefulWidget {
Suggestions({Key key, this.someId}) : super(key: key);
String someId
#override
State<StatefulWidget> createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
dynamic data;
Error err;
bool loading;
#override
Widget build(BuildContext context) {
if(loading) return Loader();
if(err) return SomeErrorMessage(err);
return SomeOtherStateLessWidget(data);
}
#override
void didUpdateWidget(covariant MyWidget oldWidget) {
super.didUpdateWidget(oldWidget);
// id changed in the widget, I need to make a new API call
if(oldWidget.id != widget.id) update();
}
update() async {
// set loading and reset error
setState(() => {
loading = true,
err = null
});
try {
// make the call
someData = await apiCall(widget.id);
// set the state
setState(() => data = someData)
} catch(e) {
// oops an error happened
setState(() => err = e)
}
// now we're not loading anymore
setState(() => loading = false);
}
}
I'm brand new to Flutter (literally, just started playing with it this weekend), but it essentially duplicates React paradigms, if that helps you at all.
Personal preference, I vastly prefer this method rather than use FutureBuilder (right now, like I said, I'm brand new). The logic is just easier to reason about (for me).

Get InheritedWidget parameter in initState

i need some help understanding how to obtain data from inherited widget.
I usually get the parameter from my widget directly from the build method using
#override
Widget build(BuildContext context) {
//THIS METHOD
var data = StateContainer.of(context).data;
return Container(child:Text("${data.parameter}"));
}
But this method cant be called from initState since there is no buildContext yet.
I need in the initState method to have that parameter (i call my fetch from server in that and i need to pass that data to my function), so, how should i do it?
#override
void initState() {
otherData = fetchData(data);
super.initState();
}
I tried using didChangeDipendencies() but it is called every time the view is rebuilt (popping from screen, etc.) so it is not what i want to use and neither the FutureBuilder widget.
Any suggestion?
First, note that you probably do want to use didChangeDependencies. But you can't just do your call there without any check. You need to wrap it in an if first.
A typical didChangeDependencies implementation should look similar to:
Foo foo;
#override
void didChangeDependencies() {
super.didChangeDependencies();
final foo = Foo.of(context);
if (this.foo != foo) {
this.foo = foo;
foo.doSomething();
}
}
Using such code, doSomething will be executed only when foo changes.
Alternatively, if you are lazy and know for sure that your object will never ever change, there's another solution.
To obtain an InheritedWidget, the method typically used is:
BuildContext context;
InheritedWidget foo = context.inheritFromWidgetOfExactType(Foo);
and it is this method that cannot be called inside initState.
But there's another method that does the same thing:
BuildContext context;
InheritedWidget foo = context.ancestorInheritedElementForWidgetOfExactType(Foo)?.widget;
The twist is:
- this method can be called inside initState
- it won't handle the scenario where the value changed.
So if your value never changes, you can use that instead.
1, If you only need InheritedWidget as a Provider of parameter for Widget.
You can using on initState as bellow:
#override
void initState() {
super.initState();
var data = context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;
}
2, If you need listener to re-render widget when data of InheritedWidget change. I suggest you wrapper your StatefulWidget insider a StatelessWidget,
parameter of StatefulWidget is passed from StatelessWidget, when InheritedWidget change data, it will notify to StatelessWidget, on StatefulWidget we will get change on didChangeDependencies and you can refresh data.
This is code guide:
class WrapperDemoWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
DemoData data = StateContainer.of(context).data;
return Container();
}
}
class ImplementWidget extends StatefulWidget {
DemoData data;
ImplementWidget({this.data});
#override
_ImplementWidgetState createState() => _ImplementWidgetState();
}
class _ImplementWidgetState extends State<ImplementWidget> {
#override
void initState() {
super.initState();
//TODO Do sth with widget.data
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
//TODO Do change with widget.data
}
#override
Widget build(BuildContext context) {
return Container();
}
}
I prefer the solution with didChangeDependencies because Future.delayed solution is a bit hack, looks unprofessional and unhealthy. However, it works out of the box.
This is the solution I prefer:
class _MyAppState extends State<MyApp> {
bool isDataLoaded = false;
#override
void didChangeDependencies() {
if (!isDataLoaded) {
otherData = fetchData(data).then((_){
this.isDataLoaded = true;
});
}
super.didChangeDependencies();
}
...
You can also get the context in initState, try using a future with duration zero. You can find some examples here
void initState() {
super.initState();
Future.delayed(Duration.zero,() {
//use context here
showDialog(context: context, builder: (context) => AlertDialog(
content: Column(
children: <Widget>[
Text('#todo')
],
),
actions: <Widget>[
FlatButton(onPressed: (){
Navigator.pop(context);
}, child: Text('OK')),
],
));
});
}
i use it to make loading screens using inherited widgets and avoid some global variables