Flutter run a function before the buildcontext with FutureBuilder - flutter

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.

Related

What should I do when I have to use "void function() async" as a last resort in Flutter?

My function is as follows:
void function() async {
…
}
I used the above function in Widget build(BuildContext context).
Actually, I want it to be Future<void> function() async instead of void function() async.
However, I can't use await in Widget build(BuildContext context), so I can't use Future<void> function() async because Future requires await.
I shouldn't use FutureBuilder or then because I don't want to get the data in the function, I just want to run the function.
Appreciate if someone can advise. Thank you in advance!
You can define your function as future & call it in initState of StatefulWidget (without await)
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Future<void> myFutureFunction() async {
// some future work with
}
#override
void initState() {
super.initState();
// this will be called once, when this widget is apear to the screen
myFutureFunction();
}
#override
Widget build(BuildContext context) {
return SizedBox();
}
}
build method can run up to 60(maybe more with the newest phones)times per second, so it's not a good idea to run any heavy computation or API call in the build method. It's simply for building(widgets).
You may want to rethink your app architecture if you think you must.
Consider bloc pattern or just checkout other lifecycle methods for stateful widgets. This article has a list of them.
As for the question, async functions technically don't require you to await them. You can just call them without adding await in front of them.
If you have a warning, it might be coming from lint rules. Related rule.

Flutter Cubit InitState

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.

Flutter: How to show a loading page while another page is loading

I'm making a website and I would like to show a loading_page until the home_page is loaded and then transition from one to the other as soon as possible (no fixed timers).
There are multiple ways to do this (ie., using the simplest setState, Streams, multiple packages for state management, just to name a few). I'll give you a simple example just by using a StatefulWidget where you call your API on initState and then navigate when you're done to your new screen.
class LoadingPage extends StatefulWidget {
const LoadingPage({Key? key}) : super(key: key);
#override
_LoadingPageState createState() => _LoadingPageState();
}
class _LoadingPageState extends State<LoadingPage> {
#override
void initState() {
super.initState();
_fetchFromAPI();
}
Future<void> _fetchFromAPI() async {
// Call some API, do anything you want while loading
Navigator.pushReplacementNamed(context, '/home_page');
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: const CircularProgressIndicator(),
);
}
}
You can use future builder for this purpose.
Have a look at: https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
You can fetch the data as snapshot and use the snapshot.hasdata to check if data is being received or not and till then you can show CircularProgreswIndicator() to show the loading..

Why should didUpdateWidget be implemented for subscriptions and ChangeNotifiers?

I created a custom widget that listens to a ChangeNotifier and invokes a provided callback whenever the notifier fires. This is used for performing one-time tasks like navigation when the notifier changes.
Everything seems to work fine, but just by accident I stumbled upon the documentation of didUpdateWidget that states:
If a State's build method depends on an object that can itself change state, for example a ChangeNotifier or Stream, or some other object to which one can subscribe to receive notifications, then be sure to subscribe and unsubscribe properly in initState, didUpdateWidget, and dispose:
In initState, subscribe to the object.
In didUpdateWidget unsubscribe from the old object and subscribe to the new one if the updated widget configuration requires replacing the object.
In dispose, unsubscribe from the object.
I'm handling the first and last point for obvious reasons, but could somebody shed a light on why I also have to implement didUpdateWidget? What could go wrong if I don't?
Bonus question: I'm not using provider in my application, yet. Does it offer something like this already out of the box? I couldn't find something like this.
My widget code:
class ChangeNotifierListener<T extends ChangeNotifier> extends StatefulWidget {
final Widget child;
final T changeNotifier;
final void Function(T changeNotifier) onChanged;
ChangeNotifierListener(
{#required this.child,
#required this.changeNotifier,
#required this.onChanged});
#override
_ChangeNotifierListenerState createState() =>
_ChangeNotifierListenerState<T>();
}
class _ChangeNotifierListenerState<T extends ChangeNotifier>
extends State<ChangeNotifierListener<T>> {
VoidCallback _callback;
#override
Widget build(BuildContext context) => widget.child;
#override
void initState() {
super.initState();
_callback = () {
widget.onChanged(widget.changeNotifier);
};
widget.changeNotifier.addListener(_callback);
}
#override
void dispose() {
widget.changeNotifier.removeListener(_callback);
super.dispose();
}
}
This part of the documentation is about how it is feasible that your widget rebuilds with a different parameters.
For example, with StreamBuilder, a first build may be similar to:
StreamBuilder(
stream: Stream.value(42),
builder: ...
)
And then something changes, and StreamBuilder is rebuilt with:
StreamBuilder(
stream: Stream.value(21),
builder: ...
)
In which case, stream changed. Therefore, StreamBuilder needs to stop listening to the previous Stream and listen to the new one.
This would be done though the following didUpdateWidget:
StreamSubscription<T> subscription;
#override
void didUpdateWidget(StreamBuilder<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.stream != oldWidget.stream) {
subscription?.cancel();
subscription = widget.stream?.listen(...);
}
}
The same logic applies to ChangeNotifier and any other observable object.

Canonical way to use FutureBuilder with NetworkImage

I would like to be able to get to a network image within a single microtask if the image is already loaded. However, with the current API available in NetworkImage and FutureBuilder, this does not seem to be possible.
This is how we typically wire the two:
NetworkImage imageProvider = getSomeNetworkImage(id);
Completer<ui.Image> completer = Completer<ui.Image>();
imageProvider.resolve(ImageConfiguration()).addListener(
(ImageInfo info, _) => completer.complete(info.image));
return FutureBuilder<ui.Image>(
future: completer.future,
builder: (BuildContext futureBuilderContext, AsyncSnapshot<ui.Image> snapshot) {
if (!snapshot.hasData) {
return _buildPlaceholder();
} else {
return _buildActual(context, snapshot.data, imageProvider);
}
},
);
addListener() immediately calls completer.complete() if the image is already there. However, FutureBuilder is based off of completer.future which does not complete until the next microtask. So even when the image is available, placeholder is displayed momentarily.
What is the best way to avoid this? Perhaps, imageProvider should expose a Future that prevents us from piping this through a completer?
Instead of using a FutureBuilder, I would take advantage of the syncCall argument passed to the listener of the ImageStream. This will tell you if the image resolved immediately, meaning it is already cached. Otherwise you can call setState and trigger a rebuild when it does complete.
class Example extends StatefulWidget {
const Example({Key key, this.image, this.child}): super(key: key);
final ImageProvider image;
final Widget child;
#override
State createState() => new ExampleState();
}
class ExampleState extends State<Example> {
bool _isImageLoaded = false;
#override
void initState() {
widget.image
.resolve(const ImageConfiguration)
.addListener(_handleResolve);
super.initState();
}
#override
Widget build(BuildContext context) {
// if syncCall = true, then _handleResolve will have already been called.
if (_isImageLoaded)
return new Image(widget.image);
return widget.child;
}
void _handleResolve(ImageInfo info, bool syncCall) {
_isImageLoaded = true;
if (!syncCall) {
// we didn't finished loading immediately, call setState to trigger frame
setState(() { });
}
}
}