How to rebuild the state after pushing a new screen - flutter

When i push a new screen onTap with Navigator and pass a new class constructor, how can I have that new screen updates every time _playerTimer updates without having to click again
Since the state of my new class only updates onTap, please help!
The build method of FullScreenDialog is called once since its only being built when onTap is pressed
InkWell(
onTap: () {
return Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => FullScreenDialog(
_playerTimer,
));
},
child: VideoPlayer(
_controller,)
);

you have to use setState to rebuild the UI.
ex:
setState((){
_playerTimer = _playerTimer + 1;
});
that's about all the help I can give without seeing the rest of your code

When you instantiate a new class (in your code would be the FullScreenDialog) by passing an attribute, you're only saying to your code that a new class will be initialized by using the argument you provided.
If you want your FullScreenDialog class to always be updated when _playerTimer changes, you must observe this attribute inside this class by using setState(), which is a build-in function for StatefulWidgets that makes apps' UI update every time the observable attribute changes.
Example:
setState( () {
_playerTimer = getUpdatedTimer();
});
Supposing that inside getUpdatedTimer() method you will manage the logic for updating this variable, calling a service or so.
If you want this variable to be updated without interacting with interface, you probably will need a timer, too. Check this question to more details about it.
If you're starting with Flutter development, I suggest you to read this article (Adding interactivity to your Flutter app) about state management
and
setState method documentation.
Hope that helps.

Related

using ref.read inside build method for StateControllers

As per the Riverpod documentation, asynchronously we use ref.read such as inside a function and for synchronous code, we use ref.watch such as inside the build method.
Once I press a button, the function with ref.read will fire up and it will be for only one time. Here, I should use ref.watch as it is now inside build method and with onPressed it will be ref.read
Case 1:
// Bad practice
// Documentation says, "DON'T use ref.read inside the build method".
final counterProvider = StateProvider((ref) => 0);
Widget build(BuildContext context, WidgetRef ref) {
StateController counter = ref.read(counterProvider.notifier);
return ElevatedButton(
onPressed: () => counter.state++,
child: const Text('button'),
);
}
Case 2:
// Good Practice
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: const Text('button'),
),
Case 3:
// Good Practice
Widget build(BuildContext context, WidgetRef ref) {
StateController counter = ref.watch(counterProvider.notifier);
return ElevatedButton(
onPressed: () => counter.state++,
child: const Text('button'),
);
}
In all 3 cases, code remains asynchronous since it is called only when the button is pressed then why is case1 bad and case 3 good practice?
the docs says you shouldn't use ref.read inside your build method because it will not cause a rebuild when the state changes, and in most cases you'll need to update your ui.
You can definitely use it inside the build method, but it will just "read" the data, if your state updates, the ui won't, this why you should always use watch inside your build method, to always update your ui when the state changes.
check this dartpad example from riverpod-v2 docs, try to use read instead of watch, it will work, but it's not actually working right? the ui doesn't update when you press the increment button.
As per Remi Rousselet,
"It's about when read/watch are invoked, not when the state change is triggered."

Return data when a screen is closed

I have several sub-screens which give the user the option to save some data. When that screen is closed, I want the parent screen, which pushed the sub-screen, to know whether data was saved or not. The sub-screens maintain a didSave flag and are set to true when data is saved.
There are several ways for the sub-screens to be closed:
hardware/software back button.
The close button on the AppBar.
A button on the screen itself.
I can handle the 3rd case using Navigator.pop(context, didSave) and in the parent that didSave flag is captured using final didSave = await Navigator.push<bool>(context, myRoute).
However, for the first 2 cases, the result will obviously be null.
I have looked into WillPopScope but that only is used to determine whether the screen should be closed or not. It doesn't seem that I can set any data to be returned for the push call.
I have also looked into wrapping the parent screen in a Provider where it can listen to didSave states but that will trigger immediately when emitted which is not desirable for my use case. I only want to act when the sub-screen is closed not when data is saved.
I can potentially use WillPopScope and emit an event there if a save operation has occurred but I would like a simpler solution if available.
Is there a way for this that I am missing?
Thanks!
as you said the Provider will listen to didSave and this doesn't fit in your case you can use just a simple inheritedWidget:
wrapping the parent like this:
InheritedExampleWidget(
didSave: false,
child: Parent(),
),
you need to set a setter to didSave
then on the ascendant widgets on the widget tree, you can:
InheritedExampleWidget.of(context).didSave = true;
this will not trigger it immediately, which the Provider package solves.
then you can manage the state however you want
Did you try to create the variable with state management? And there is method with the push that do task after user come from the child screen. So, when they come you can checkout the use have saved data or not.
For EG:
saved = false; //State management variable
//We are pushing on another screen.
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) =>
new ScreenName(),
),
).then((val) {
//This part with work when user popped the screen on which we have pushed
if (!saved) {//Do Something when not saved}
});
Try above code and let me know if you get any error or you're facing any issue.
when you push a new route, using StatefulWidget, it will have a lifecycle starting from an createState, when the widget isn't there on the widget tree ( when we pop ), the dispose method will be called.
those cases of popping the route:
hardware/software back button.
The close button on the AppBar.
A button on the screen itself.
will trigger the dispose method to execute.
so you can put inside it your logic that you want.
exmaple :
class classTest {
bool didSave = false;
}
then when on the property where you want to push the screen set it to that classTest's didSave, as an example:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const YourRouteWidget(didSave: classTest.didSave,
),
),
);
on that point it's false, then when the user will complete using the screen, going back with any king of popping the route (with Navigator.pop(context), or with back button...), the dispose method will be called, so you can :
void dispose() {
if(/* your conditions*/ ) {
classTest.didSave = true;
}
super.dispose();
}
it will be back with a true value to the parent page.

Flutter/Dart - Edit Page in PageView, then Refresh and Scroll back to Same Place?

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.

Can a state in flutter call itself?

I have a stateful widget, that gets updated with button press events, is it viable to call the state from inside the same state on button press event? The states are a lot in number. So I want to avoid initializing too many states.
Yes you can call setState((){}) inside a StatefulWidget. According to the documentation:
Notify the framework that the internal state of this object has changed.
That means that, if you want to update any value on your StatefulWidget, make sure to call it inside the setState((){}), like this: setState(() { _myState = newValue; });
From what I understood, you don't want to have too many calls to the setState((){}) function. Since you are using a button, you can do it like this:
FlatButton(
child: Text("Tap me!"),
onPressed: () => setState((){ tapped = true}),
),
The arrows are syntatic sugar that substitute the curly brackets. Useful when there is only one line of code.

How do we pass or propagate data backwards through the Navigation stack in Flutter?

1.FlatButton(
onPressed: () async {
var typedName=await Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return CityScreen();
},
),
);
print(typedName); //prints the value of cityName
}
2. FlatButton(
onPressed: () {
Navigator.pop(context, cityName);
},
The no.1 is coming from file loading_screen and no.2 is coming from city_screen. Can you anyone help me understand what is happening that when you pass a variable or anything in the pop? And when come that onPress method is still working because the the method Navigator.push has been already assigned to the variable but still that method Navigator.push is working when I pressed the button?Does that onPress doesn't care about the variable TypedName and just looks for the method Navigator.push?
If you use push method it will create the new screen over your current page and pop will remove last screen, you cannot pass name with both, you can check basics of navigation here, if you want to pass name to next page you should use something like pushnamed method and you can see the logic of passing name here. And for all methods you should check here.