I have a stateless page with a provider. I use Google AdMob to display ads (in_app_purchase package).
On the page I have a start button. onPressed event has below code to go to the next page:
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const NextScreen()),
);
To implement rewarded ad I have added code to onPressed event to detect whether the ads are required, and if so, instead of routing to the next page, a dialog box is displayed asking user to watch an ad. If the user accepts, this starts the ad process in the provider.
This all works great, but my problem is that when the reward is granted in the provider event, I do not know how to trigger the move to the next page.
The ad code in the provider class looks like this:
await _rewardedAd!.show(
onUserEarnedReward: (AdWithoutView ad, RewardItem reward) {
//here I can notify listeners to change the UI look, but not to trigger a page move
}
);
At that point I can notify listeners to change UI look, but not to trigger a page move. It would be useful to have some sort of a "function widget" that does nothing but listen for a value change and execute some code, but could not find such a thing.
At a time of desperation I even tried putting and invisible widget on the page to do that, but that triggers an error when I call navigator route from a widget that is being rebuilt.
It would be nice if button onPressed even could listen for provider change, but I don't know if this is possible.
What is the proper way to do this? I suspect this is a very common scenario. Is this even possible to achieve with a stateless widget and provider pattern? Must I switch to a stateful widget?
Ended up implementing this solution which works but seems a bit hacky and not sure if it will cause some problems with the widget tree.
Will accept a better solution.
In a nutshell, provider has a special trigger variable that now a StateFulWidget screen listens for in didChangeDependencies event.
In the provider I have this code to display the ad and listen for rewarded event:
await _rewardedAd!.setImmersiveMode(true);
await _rewardedAd!.show(
onUserEarnedReward: (AdWithoutView ad, RewardItem reward) {
//set the trigger and notify listeners
_startWorkoutTrigger = true;
notifyListeners();
});
I converted the page to a StatefulWidget and added this code to didChangeDependencies event:
#override
Future<void> didChangeDependencies() async {
super.didChangeDependencies();
if (Provider.of<Settings>(context, listen: false)
.getStartTrigger()) {
Provider.of<Settings>(context, listen: false).cancelStartTrigger();
Future.delayed(
Duration.zero,
() => Navigator.push(
context,
MaterialPageRoute(builder: (context) => const NextScreen()),
));
}
}
Related
Hello hope you guys are ok.
the problem I'm facing is we have a main_page which leads to a page doing some changes on data which are show on the main page.
after some process if the user touches back button and goes to main_page app loads it from stack and the data are not shown because it does not get rebuilt.
I don't want to control back button because there are other pages which lead to data changing page and I also tried using valuelistenablebuilder but I don't know why it goes wild and gets into a screen refresh loop without even changing the valueListenable I used redux to manage the value.
actual reason I want main page to rebuild is to call a method. why do I not call that method in second page is complicated and because i don't want to.
in conclusion I want main page to rebuild whenever it shows up even when it's read from the stack or even is there a way to tamper with stack of the pages without visual changes to the user.
you need to use Shared preferences plugin
https://pub.dev/packages/shared_preferences
If I understood the question correctly
The flow should be:
Screen A
#override
void initState() {
loadSomeDataFromDB();
super.initState();
}
Screen B
#override
void initState() { *//INIT AS EXAMPLE*
changeSomeDataFromDB();
super.initState();
}
You can try this in Screen A
onPressed: () async {
bool? shouldLoadDataAgain = await
Navigator.of(context).push(PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) =>
const ScreenB(),
));
if(shouldLoadDataAgain!=null&&shouldLoadDataAgain ==true){
loadSomeDataFromDB();
}
},
and this in Screen B when user press back button
onPressed: () async {
Navigator.of(context).pop(true)
},
solution for my problem was that used redux store had an object and updating object property or a list does not count as variable change to redux so the widget wouldn't rebuild.
This is my App's flow:
How do I update the Home Page when I press back and return to it from the MaterialPageRoute? I attempted to use the setState method in the dispose() method of MaterialPageRoute, but it gave me an error.
You can use Navigator.push(context,MaterialPageRoute(builder: (context) => TheSecondPage())).then((value) { setState(() {}); thus when you pop from TheSecondPage() you will immediatelly call the setState refreshing the page.
Actually, the Home Page is automatically updated, however I made a mistake by updating the variable within the dispose() method, which is invoked after the Home Page has been updated.
I am struggling with the following problem:
I built a list of widgets using StreamBuilder (and made it searchable). The widgets are Cards and inside them the user can make his selection and then push a button.
Everything is (was) working just fine.
Then I wanted to add a little animation and make the Icon associated to the button an animated one.
Now it is a mess, the StreamBuilder is in an infinite loop and I have also some problem on my list. If I comment out the animated icon and put in again the previous Icon ...everything starts to work fine again.
...
child: FloatingActionButton(
onPressed: (){
addFood();
mealListState.getAcontroller(id).forward();
},
child:
// MyAddIcon(id), //--> my animated Icon. It does not work
Icon(Icons.add), //--> it works
....
I read that the problem is that adding States management (Unnecessary Widget Rebuilds While Using Selector (Provider) inside StreamBuilder) inside the stream mess things up and that you have to make the widget building the stream Stateful and set up the stream in the initState.
I tried to follow this way but I need the context to build my card list, so I could follow the above hint just to read the data from db (firestore) and ...it is not enough
Could someone point me in the right direction or I have to leave the idea and move on?
Thanks
If you need the context, you can try to listen to the the stream in the didChangeDependencies method of your State class.
Use the Streamsubscription returned by stream.listen to cancel your subscription like so to avoid subsribing twice or even more times:
#override
didChangeDependencies() {
final stream = Provider.of<MyStream>(context); // context available here.
if (this.streamsubscription != null) {
this.streamsubscription.cancel();
}
this.streamsubscription = stream.listen((value) {
// your callback code here
});
super.didChangeDependencies(); // important
}
I'm new to the flutter, and I have a question. When the user creates the account, I would like to redirect him to the configuration page with a certain Widget on the page already open. It's possible?
Navigator.push(context, MaterialPageRoute(builder: (context){
return SettingsWidget();
}));
I'm using this code to direct the Configuration page, but the Widget that has the person inside would have to click on the button, but I wanted it to be already open.
I think you want to show a dialog as soon as the page opens! If that is your requirement you can do this:
#override
void initState() {
super.initState();
SchedulerBinding.instance
.addPostFrameCallback((_) => showDialog(context));
}
I have a page which is calling multiple classes and different widgets
and by clicking on it user can hop to any page.
Currently the issue is I want to refresh the original page everytime it is shown to user.
When it is freshly loaded (causing no issue for now).
When user comes back to it from some other page using back key.
old data is shown on the page, which I am trying to get rid of.
I have tried using Navigator.push method (Observer class method) and tried to listen when user presses backkey.
but there are multiple pages serving unique requests and I don't want to link everyone with that first page.
as while clicking Navigator.pop(context) method i'll have to pass some string.
Does any one know
how can I refresh the page when users comes back to it using backkey.
Navigator.push returns a Future when the MaterialPageRoute passed into it is resolved. You could await that Future to know when some other view has popped back to it.
Example,
Navigator.of(context).push(MaterialPageRoute(
builder:
(context) => NewView(),
),
).then((_) {
// Call setState() here or handle this appropriately
});
Had the same issue, the following worked for me (below code is in FirstPage()):
Navigator.push( context, MaterialPageRoute( builder: (context) => SecondPage()), ).then((value) => setState(() {}));
After you pop back from SecondPage() to FirstPage() the "then" statement will run and refresh the page.
NavigatorObserver can listen to route changes.
You need to create your implementation overriding didPop, didPush etc.
Then you can pass it to navigatorObservers field of MaterialApp or Navigator