What I want to achieve: I want to open a pop up explaining my app when it starts.
My approach: As far as I understand it from googling the issue, I should use the showDialog() method. In its most basic form:
showDialog(
context: context,
builder: (context) {
return Text('data');
});
I tried returning actual dialogs (e.g. AlertDialog) but it doesn't change the behavior so I'm just using Text() with a string as a placeholder for now.
The problem:
No matter where I place the showDialog function, it doesn't work as intended (also see scrennshots below):
Placing it in initState: I get an error message about inherited Widgets being called before the initState is done + an explanation about dependiencies I can barely follow.
Placing it in the build method: I get an error message that setState() or markNeedsBuild() gets called while the app is already buildung widgets.
Placing it in DidChangeAppLifeCycleState(): This is actually working and opening the pop when I pause the app and then resume it. It is not opening on app startup though.
Wrapping it in WidgetsBinding.instance!.addPostFrameCallback(): An idea I picked up here: How to show a popup on app start in Flutter. Doesn't change the outcome of the error messages, neither in initState nor in build.
Any ideas?
Screenshots:
From initState:
From build method:
From DidChangeAppLifecycleState (the "succesful" variant:
Will you please try below code in your init method? I hope this may work.
Future.delayed(Duration.zero, () async {
myFunction();
});
Using WidgetsBinding.instance.addPostFrameCallback inside initState perform its inner task after the 1st frame is complete.
addPostFrameCallback Schedule a callback for the end of this frame.
Next issue arise for not having material. You can directly return AlertDialog on builder or wrap with any material widget like Material, Scaffold..
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
showDialog(
context: context,
builder: (context) {
return const AlertDialog(
content: Text('data'),
);
},
);
});
}
If you are running version<3 null safety, use WidgetsBinding.instance?.addPostFrameCallback
One of the methods with WidgetsBinding.instance!.addPostFrameCallback() works fine .
If you show a normal show dialog with the press of a button too it will produce the same result.
Here, you need to wrap the text("data") in a dialog widget such as alertDialog or simpleDialog widget as needed and it will display the dialog within the current scaffold as -
WidgetsBinding.instance!.addPostFrameCallback((_) async {
return await showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text("data"),
);
});
});
I tried adding this in the init state and the dialog pops up fine when I restart the app
Thanks a lot for your answers. I ficed the issue by rewriting with your suggestions; and it works. I tihnk the issue was that I did not have _ or anything else in my WidgetsBinding code. So I did:
WidgetsBinding.instance?.addPostFrameCallback(() {})
instead of
WidgetsBinding.instance?.addPostFrameCallback((_) {})
Related
Hello,
From the main screen of my app, pressing a button switches to another screen.
IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return screen2();
},
),
);
},
When I am in screen2 and I want to go back or I can press a button.
Navigator.pop(context); or press the android back button.
How can I call a function as soon as I go back to the main screen?
Thank you.
From what I understood you want to trigger a function when you go to the previous page!
So,
Make the main screen as a Stateful widget and then initialize an init state and inside the init state put the function you want to trigger
Such as like this below code
#override
void initState() {
// TODO: implement initState
super.initState();
FunctionYouWantToTrigger();
}
When clicking on an OPEN LIST button, the app creates a series of lists needed for future display. I found out that when the number of items in the list increases, these calculations can take a little time (2, 3 seconds). So for better UX, I would like to add something similar to a loading indicator telling the user the "lists are being prepared".
In my app, I use the package Loading Indicator : it works fine.
So I wanted to use it for this situation.
Here's what I did :
I transformed my "void" list creating functions into "Future Void".
I added the async keyword to the function plugged to my "OPEN LIST BUTTON".
But... for some reason, it never displays the loading indicator....
Here's the code (UI part) :
onMenuOuvrir: () async {
DialogBuilder(context).showLoadingIndicator(
text: 'Ouverture de la liste', color: Colors.black45);
uD.setSelectedCarnetList(
index, uD.userInfo!.carnetList![index].ref!);
await uD.getListReady();
DialogBuilder(context).hideOpenDialog();
Navigator.pushReplacementNamed(
context, EditCarnetScreen.id);
},
Here's the code (Provider / back end part) :
Future<void> getListReady() async {
await createBufferCarnetWordBank();
await createBufferCarnetList();
await createBufferGrammarList();
await createBufferLevelList();
setEditMode(false);
clearSearchList();
}
The functions "createBuffer...List" are all of type Future .
What am I doing wrong ?
I actually found out that the problem was elsewhere... not in the creation of the lists, but in the building of the "listview.builder" in the EditCarnetScreen.
So here's the question now... how can we display some kind of indicator while this task is being processed.... it seems to be "in between screens"...
Create a widget in a separate dart file :
import 'package:flutter/material.dart';
Loading(BuildContext context) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
),
);
});
}
Then use it anywhere in your code:
Loading(context)
Dismiss in whenever you want:
Navigator.of(context).pop()
You can use FutureBuilder widget for that, e.g.
FutureBuilder(future: Provider.of<//your provider>(context)getListReady(), builder: (context, snapshot) => snapshot.connectionState == ConnectionState.waiting ? Center(child: CircularProgressIndicator()) : //your widget)
If you process the calculation in the main thread, even if the indicator shows up the UI freezes and the spinning animation would be laggy.
You may process the heavy lifting tasks by Isolates. Search for Isolate or compute method of dart.
I have used the fluttertoast: ^8.0.8 package as shown below. You can set the toast length to a longer duration if needed. FutureBuilder is not useful in the case, where your widget themselves take a long time render. FutureBuilder is useful while fetching the data required for your widgets.
#override
Widget build(BuildContext context) {
Fluttertoast.showToast(
msg: 'Loading...'
);
//Code for your complex widget here
}
So the first picture has codes from the first page where I have a StreamBuilder in my flutter app, there I tried navigating to the stateful widget named PickupScreen containing the code in second picture and it gave me an error which says SetState( or markNeedsbuild() called during build.So I called the widget directly and it worked but the problem is I cannot go the previous page I know I can't use pop but I need a solution for returning to previous page and show the else code.Currently I am Navigating to another page named HomePage.
You can't do it like that. Either you manage your screen state based on your widget.channel.stream or you must design your UI in a different way so you can actually push a new screen on your screen 1 and then you will be able to pop.
If you still want to keep it like that, you can do something like the following in your first screen:
return StreamBuilder(
stream: widget.channel.stream,
builder: (context, snapshot){
if(snapshot.hasData && jsonDecode(snapshot.data['ntype'] == 10){
WidgetsBinding.instance.addPostFrameCallback((_) => Navigator.push(context, MaterialPageRoute(builder: (_) => PickupScreen(data: jsonDecode(snapshot.data))));
return const SizedBox();
} else {
return _isLoading ? LoadingScreen() : Scaffold();
}
});
Also, next time, make sure you copy/paste code instead of screenshots to make it easy to edit with modifications. :)
I'm trying to detect that user is no longer authenticated and redirect user to login. This is how I'm doing it
Widget build(BuildContext context) {
return FutureBuilder(
future: _getData(context),
builder: (context, snapshot) {
try {
if (snapshot.hasError && _isAuthenticationError(snapshot.error)) {
Navigator.push(context, MaterialPageRoute(builder: (context) => LoginView()));
}
Unfortunately doing navigation on build is not working. It throws this error
flutter: setState() or markNeedsBuild() called during build.
flutter: This Overlay widget cannot be marked as needing to build because the framework is already in the
flutter: process of building widgets. A widget can be marked as needing to be built during the build
I cannot just return LoginView widget since parent widget containts app bar and floating button and login view needs to be displayed without these controlls.. I need to navigate.
Is it possible to do it?
Wrap it in Future.microtask. This will schedule it to happen on the next async task cycle (i.e. after build is complete).
Future.microtask(() => Navigator.push(
context,
MaterialPageRoute(builder: (context) => LoginView())
));
Streams in flutter
The usual thing is to use a flow where user changes occur.
When the user logs off, he detects that change and can direct it to another window.
problem here :
snapshot.hasError && _isAuthenticationError(snapshot.error)
Instead of this use OR
snapshot.hasError || _isAuthenticationError(snapshot.error)
I have a screen, which shows a button. If I press it, an async job is started. During this time, I want to show an AlertDialog with a spinning wheel. If that job is finished, i will dismiss the dialog or show some errors. Here is my (simplified) code:
Widget build(BuildContext context) {
if (otherClass.isTaskRunning()) {
showDialog( ... ); // Show spinning wheel
}
if (otherClass.hasErrors()) {
showDialog( ...); // Show errors
}
return Scaffold(
...
FlatButton(
onPress: otherClass.startJob
)
);
}
The build will be triggered when the job status is changed or if there are errors. So far, so good, but if I run this code, I got this error message:
Exception has occurred. FlutterError (setState() or markNeedsBuild()
called during build. This Overlay widget cannot be marked as needing
to build because the framework is already in the process of building
widgets. A widget can be marked as needing to be built during the
build phase only if one of its ancestors is currently building. This
exception is allowed because the framework builds parent widgets
before children, which means a dirty descendant will always be built.
Otherwise, the framework might not visit this widget during this build
phase. The widget on which setState() or markNeedsBuild() was called
was: Overlay-[LabeledGlobalKey#357d8] The widget which
was currently being built when the offending call was made was:
SettingsScreen)
So, the repaint of the screen will be overlap somehow. I am not sure how to fix this. It feels like I am using this completely wrong. What is the prefered way to handle this kind of interaction (trigger "long" running task, show progress indicator and possible errors)?
The problem is the dialog is going to show while the build method hasn't already finish. So if you want to show a Dialog, you should do it after the build method has finished. To do that, you can use this: WidgetsBinding.instance.addPostFrameCallback(), that will call a function after the last frame was built (just after build method ends).
Other thing you can do is using the ternary operator to show a loading widget like so:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: otherClass.isTaskRunning()
? CircularProgressIndicator()
: FlatButton(
onPressed: () {},
),
);
}
As being said in the comment, calling showDialog and similar inside build is anti-pattern. Here is the detailed explanation.
Although is a bit late, it is important to note showDialog is indeed a async method, returning at the time that the dialog dismisses, and build is a sync in nature. Under the hood, it use Navigator.of(context).push.
Referring to this question,
The build method is designed in such a way that it should be pure/without side effects. This is because many external factors can trigger a new widget build, such as: Route pop/push
So this directly caused flutter to complain setState is called during build. You could just use a FutureBuilder inside the dialog.
One thing is clear is that your SettingsScreen is not required to fetch anything and so you should not brother with any of them. As you are deferring to do that as the button fires, then you should do it within dialog.
Widget build(BuildContext context) {
return Scaffold(
body: FlatButton(
onPress: () async {
await showDialog(
context: context,
builder: (BuildContext context) {
return FutureBuilder(
future: otherClass.startJob(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) // Show error
if (snapshot.hasData) // Show data, or you can just close it by Navigator.pop
else // show spinning wheel
}
}
);
}
);
}
)
);
}