Currently, I have a seperate page to view a specific entity.
On the seperate page, users may delete this entity.
To make the code more "clean", I want to first pop this separate page and then delete the entity from the list. However, I cannot delete it right away because there is a short time frame where the page still exists. My workaround is to add a delay to make sure that the page is "disposed". Is there a cleaner way?
onTap: () async {
final result = await Navigator.push<MyPageResult>(context, MaterialPageRoute(
builder: (_) => MyEntityPage(id: id),
);
if (result == MyPageResult.delete) {
// wait for page to be correctly disposed (animation to be finished)
// this is the dirty approach, is there a cleaner way?
await sleepAsync(1000);
ref.read(dataProvider.notifier).deleteEntity(id: id);
}
}
Related
I'm trying to open a page and get returned result with go_router package.
In Navigation 1.0 I use this:
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SecondRoute()),
);
// handle result
But I can't seem to do it with go_router. Any solution or explaination?
You can do this with GoRouter.of(context).addListener.
First you push your new page and add a listener afterwards
GoRouter.of(context).push("/page/${page!.id}/edit");
GoRouter.of(context).addListener(watchRouteChange);
The listener function can look something like this
watchRouteChange() {
if (!GoRouter.of(context).location.contains("/edit")) { // Here you check for some changes in your route that indicate you are no longer on the page you have pushed before
// do something
GoRouter.of(context).removeListener(watchRouteChange); // remove listener
}
}
Presently there is no way to achieve this using go_router. You can use go_router_flow which is exactly like go_router with this pop with value feature.
final bool? result = await context.push<bool>('/page2');
WidgetsBinding.instance.addPostFrameCallback((_) {
if(result){
print('Page returned $result');
}
});
You can use the callback by putting the function in extra object when push new screen.
Example
Screen A -push-> Screen B ->pop with result -> Screen A (get results)
Define the function type to put
typedef AddNewEventResult = void Function(Result result);
Push A -> B
GoRoute(
path: kScreenB,
builder: (BuildContext context, GoRouterState state) => ScreenB(addNewEventResultstate.extra! as AddNewEventResult),
)
When screen B has done, just pop from B to A and attached the result (Result)
Navigator.of(context).pop();
widget.addNewEventResult(true, Result());
This flow is described in the docs here: https://gorouter.dev/user-input
Generally you have to update the data and return some value back as a route with params and the screen itself should manage updates / data manipulation.
I don't want to copy paste their code here, but the answer you are looking for is in the docs page above.
Updated with link from archive: https://web.archive.org/web/20220325235726/https://gorouter.dev/user-input
Thanks ahmetakil
When you are done with the next screen just use Navigator.pop(context,true); and true is something that you want to send to the previous screen. You can send anything I'm just using true for reference. This will allow your result variable to get data and perform anything.
i need to remove listview items on button click
local_data_source
Future<void> deleteTask(int index) async {
final box = await asyncBox;
await box.deleteAt(index);
}
tasks_repository
Future<void> deleteLocalTask(int index) async {
await tasksLocalDataSource.deleteTask(index);
}
tasks_provider
Future<void> deleteTasks(int index) async {
await tasksRepository.deleteLocalTask(index);
}
button that removes element
return GestureDetector(
onTap: () {
final delete = context
.read(tasksProvider.notifier)
.deleteTasks(tasks.length);
delete;
log('$delete');
},
You did not post enough code to actually see if this is the only mistake.
But in the code you posted, you are passing tasks.length as the index of the task to delete. While I don't know which task you want to delete, tasks.length is definetely not the index of one of your tasks, it's practically guaranteed to not be an index of your tasks at all.
Your methods all seem to deal with the async/ await pattern just fine and then in your onTap method it reads as if you never heard of Future<> before. If those methods were written by different persons, maybe contact the first one and ask for advice. If all of it was written by you, you need a break and a coffee or something.
Currently, I can submit edits to a single page in a PageView and then either Navigator.push to a newly created single edited page or Navigator.pop back to the original Pageview containing the unedited page.
But I'd prefer to pop back to the the same place in an updated/refreshed Pageview. I was thinking I could do this on the original PageView page:
Navigator.pushReplacement(context,new MaterialPageRoute(
builder: (BuildContext context) => EditPage()),);
But after editing, how can I pop back to a refreshed PageView which is scrolled to the now updated original page? Or is there a better way? Someone mentioned keys, but I've not yet learned to use them.
The question deals with the concept of Reactive App-State. The correct way to handle this is through having an app state management solution like Bloc or Redux.
Explanation: The app state takes care of the data which you are editing. the EditPage just tells the store(App-State container) to edit that data and the framework takes care of the data that should be updated in the PageView.
as a temporary solution you can use an async call to Navigation.push() and refresh the PageView State once the EditPage comes back. you can also use an overloaded version of pop() to return a success condition which aids for a conditional setState().
Do you know that Navigator.pushReplacement(...) returns a Future<T> which completes when you finally return to original context ?
So how are you going to utilize this fact ?
Lets say you want to update a String of the original page :
String itWillBeUpdated="old value";
#override
Widget build(BuildContext ctx)
{
.
.
.
onPressesed:() async {
itWillBeUpdated= await Navigator.pushReplacement(context,new MaterialPageRoute(
builder: (BuildContext context) => EditPage()),);
setState((){});
},
}
On your editing page , you can define Navigator.pop(...) like this :
Navigator.pop<String>(context, "new string");
by doing this , you can provide any data back to the original page and by calling setState((){}) , your page will reflect the changes
This isn't ideal, but works somewhat. First I created a provider class and added the following;
class AudioWidgetProvider with ChangeNotifier {
int refreshIndex;
setRefreshIndex (ri) {
refreshIndex = ri;
return refreshIndex;
}
}
Then in my PageView Builder on the first page, I did this;
Widget build(context) {
var audioWidgetProvider = Provider.of<AudioWidgetProvider>(context);
return
PreloadPageView.builder(
controller: PreloadPageController(initialPage: audioWidgetProvider.refreshIndex?? 0),
Then to get to the EditPage (2nd screen) I did this;
onPressed: () async {
audioWidgetProvider.setRefreshIndex(currentIndex);
Navigator.pushReplacement(context,new MaterialPageRoute(builder: (BuildContext context) => EditPage()),); }
And finally I did this to return to a reloaded PageView scrolled to the edited page;
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) =>HomePage()));
The only problem now is that the PageView list comes from a PHP/Mysql query and I'm not sure what to do if new items are added to the list from the Mysql database. This means the currentIndex will be wrong. But I guess that's the topic of another question.
For ex.
I am on Screen-1 , push screen and goto Screen-2.
and then I replace Screen-2 with Screen-3
and now I pop that Screen-3 with pass some data
and come back to Screen-1
and I want refresh data in Screen-1
You can use something like this (but you must go from 3 to 2 and finally 1) to get some value then refresh it:
var result = await Navigator.of(context).push(
MaterialPageRoute(builder: (context) => Screen2()));
if (result != null) {
setState(() {
valueYouChangeIt = result;
});
}
And then send this value back from Screen2 with:
_sendDataBack(BuildContext context) {
Navigator.pop(context, valueToSendBack);
}
Or if you want to refresh everything you can write _initState function where you initialize your data and then call _initState on result.
Said that, all this can be super tedious process, so if it's a small app with 2 to 5 screens it can be ok, but with more screens, like someone else commented, I'd prefer to use Provider.
I have a "list page". and an add page. User clicks "add page", goes to a new page where they add some entity. Then I pop the Navigator, and user goes back to "list page".
At this point, I want to get notified somehow - callback maybe? override a method, I am not really sure - and re-read the information from database.
Is there a hook like this in Flutter? Or is there another pattern that I should use?
The Navigator.push method returns a future of the generic type of the Route:
Navigator.of(context)
.push(new MaterialPageRoute<String>(...))
.then((String value) {
print(value);
});
And inside the new route, when you have the value you need:
Navigator.of(context).pop('String');
If at your list page, you create function let say retrieveData() to read list of the data then call it inside initState, if you're using pushNamed route, the simple solution would be
Navigator.pushNamed(context, '/adddatapage').whenComplete(retrieveData());
If you're using push route,
Navigator.of(context).push(new MaterialPageRoute(builder: (context) => AddPage())).whenComplete(retrieveData);
That should be enough. No need to additional code on the add page. This will be works if user press back button also, again, without additional code in the add page.
Adding a option to #ian 's answer
Navigator.of(context)
.push(new MaterialPageRoute<String>(...))
.then((value) => setState(() => {}));
This worked for me. This will rebuild whenever page is loaded.
Write below code when redirect to screen 2,
Navigator.pushNamed(context, '/screen2').then((_) {
// This block runs when you have returned back from screen 2.
setState(() {
// code here to refresh data
});
});
Use
Navigator.pop(context);
in screen 2 to back to screen 1
i use Navigator.pushAndRemoveUntil, it will refresh the page and load the data again, this is example :
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) =>
CustomerMainPage()),
(Route<dynamic> route) =>
false);
I passed a callback method into the "add" page as a Navigator route argument. The callback is a method in my list page. The "add" page executes the callback when exiting. The callback method then simply refreshes the data and calls setState to refresh the screen.
My architecture is using Provider and viewmodels, but it should be straight-forward in a standard app as well.
For named route:
Navigator.pushNamed(context, '/page2').then((_) {
// This block runs when you have returned back to the 1st Page from 2nd.
setState(() {
// Call setState to refresh the page.
});
});
For unnamed route:
Navigator.push(context, MaterialPageRoute(builder: (_) => Page2())).then((_) {
// This block runs when you have come back to the 1st Page from 2nd.
setState(() {
// Call setState to refresh the page.
});
});