Fast question: how can I perform a navigation inside the build method of a widget?
I'm developing a Flutter App.
I use Bloc architecture.
I have screen with a create form. When the user presses a button, it calls a REST api. While the call is being executed I display a circular progress. When the progress ends I want the screen to be popped (using navigation).
To display the job status I use a Stream in the bloc and a StreamBuilder in the widget. So I want to do something like this:
return StreamBuilder<Job<T>>(
stream: jobstream,
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.jobStatus == JobStatus.progress)
// job being executed, display progress
return Center(child: CircularProgressIndicator());
else if (snapshot.data.jobStatus == JobStatus.success)
Navigator.pop(context); // Problem here!
return Center(child: CircularProgressIndicator());
} else {
return DisplayForm();
}
});
I have problems with the line: Navigator.pop(context);
Because navigation is not allowed during a build.
How can I do this navigation?
My currect dirty solution is using the following function, but its ugly:
static void deferredPop(BuildContext context) async{
Future.delayed(
Duration(milliseconds: 1),
() => Navigator.pop(context)
);
}
You can add a callback to be executed after the build method is complete with this line of code:
WidgetsBinding.instance.addPostFrameCallback((_) => Navigator.pop(context));
'Because navigation is not allowed during a build.' that being said you should consider creating a separate function that will receive as an input something that you will take into consideration whether to pop that screen.
Related
I have tasks list from flutter_downloader but that list is not enough to show in list. I need to show other information in list view as well as download information.
I already got the download tasks in initial state but I need to wait to get another list from bloc. after DownloadedSongListLoaded, I want to call _combineList(favouriteSongs); But I only want to return the Container after _combineList(favouriteSongs) finished.
So, how can I call async function in widget in BlocBuilder or other way around.
child: BlocBuilder<FavouriteSongBloc, FavouriteSongState>(
builder: (context, state) {
if (state is FavouriteSongError) {
return SomethingWentWrongScreen(error: state.error);
} else if (state is DownloadedSongListLoaded) {
favouriteSongs = state.favouriteSongs;
await _combineList(favouriteSongs); <== here, I want to manipulate the favouriteSongs list before binding the below Container widget.
return const Container() //ListView builder will be here
}
return const CircularProgressIndicatorWidget();
},
)
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
}
I have a HomePage with DataTable with several hundred rows, which takes a while to load. The HomePageState reloads every time I sort the data table - while building, the whole page freezes for a second or two.
Is it possible to add a progress indicator, build the new state in the background and show it after the build is done?
In other words, I want this to happen when a user sorts the table:
CircularProgressIndicator shows up
The new state is shown after the new state is built
Hoping you are using FutureBuilder for getting data.
FutureBuilder(
future: fetchData(),
builder: (_, dataSnapshot) {
if (dataSnapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else {
return Container(), // Your widgets code
);
}
},
)
inside else block get data like dataSnapshot.data -- Use this to render widgets.
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 want to populate my lists by making API calls when moving to a screen in flutter. I tried calling the async function in the init state however it takes time to load the data of course, and that leads to a null error.
I tried using a future builder in my view. However, I want to make multiple API calls and I don't think that is possible with one future builder. And I would like to avoid having different future builders for each list.
Is there a neat way to do this?
Also is it advisable to load data and pass it on from the previous screen.. since I would like to avoid the loading time?
current code
FutureBuilder(
future: getAllData,
initialData: [],
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data.isEmpty) return Center(child: CircularProgressIndicator(valueColor: new AlwaysStoppedAnimation<Color>(Colors.red),));
else {
return createNewTaskView(context, snapshot.data);
}
}),
init method
#override
void initState() {
this.getAllData = getSocieties();
}