I've got a screen that has a CupertinoSliverRefreshControl controlling the pull-to-refresh capability that looks something like this:
CupertinoSliverRefreshControl(
onRefresh: () async {
_bloc.add(MyBlocEvent.reload());
},
);
Because CupertinoSliverRefreshControl only shows the spinner for as long as the onRefresh block takes to run (which is basically instantaneous in this case), the spinner disappears immediately... whereas the natural iOS UX is that it stays visible until the reload has completed.
I've tried adding an await Future.delayed() after I add the event to the bloc - which sort of works in that it will show the spinner for that duration... however, that duration is not actually related to how long the bloc takes to reload the content.
I've even tried waiting for, say, 20 seconds and hoping that the bloc reload would blow away the spinner - which didn't work initially (presumably because CupertinoSliverRefreshControl is stateful)... if I add a UniqueKey to the refresh control then when the bloc finishes the spinner does disappear, but it is a jarring transition because a whole new refresh control is getting built (instead of nicely easing back into place)
I'm curious how folk uses this control in the context of a bloc. Thanks a lot.
I haven't worked much with bloc implementations in which I had to add events to it with add() function, but I usually use Cubit and simply call functions from it.
So if you could refactor your Bloc implementation to Cubit, or regular Bloc but in which you can call functions from it instead of adding events with add function you can easily do something like:
class MyCubit extends Cubit<String> {
MyCubit(String initialOption) : super(initialOption);
Future<void> reload() async {
final newValue = await Future.delayed(const Duration(seconds: 5)); //Here is your reloading logic
emit(newValue);
}
}
This way if you'll call from Widget:
await context.read<MyCubit>().reload(); in the onRefresh function, loader will disappear right after you've reloaded data and emitted new one.
I hope it helps.
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.
I have a navbar, change between 2 screens nested with Provider create blocs. Whenever bloc constructor called. It's call api to get data and add to the stream. So the problem here, user can spam switch between 2 screens and make the bloc dispose and init => api was called multiple times.
Class Bloc1 {
const Bloc1(){
data = await fetch() //Call api
stream1.add(data) //Then add to stream
}
}
I have tried the lock. But it does not work because when recreate, the lock is recreate too -> useless.
Class Bloc1{
var lock = false;
const Bloc1(){
if(lock == false) {
data = await fetch() //Call api
stream1.add(data) //Then add to stream
}
}
}
In my opinion, the issue you are mentioning is a kind of a border case where the user will have an aggressive behavior with the switch.
That being said, I think you should clarify a few points:
Do you really want to change the switch behavior to avoid this scenario?
Does it make sense to fetch new data every time the user switches tabs? I mean, does the data change that often? Because if the data does not change, maybe it does not makes sense to make a new request to the API at that point, and you should consider fetching this data at some other point.
I think there is no way to avoid this scenario if you instantiate/dispose your bloc every time the user switches screens (unless you save the lock or previously retrieved data outside the bloc, so next time you instantiate this bloc you can provide this value via the constructor, and this way you can avoid making the API request).
Depending on your Bloc logic, a solution could be that you instantiate the Bloc above these two screens, by doing this the Bloc will not be disposed once you switch screens and therefore it won't be instantiated again. Take into account that this approach will make the Bloc to be alive no matter how many times the user switches screens, and it is possible that this is something that you want to avoid.
Here is the problem: I have a list of items displayed in a list view. I can edit these items with the dialogs displayed by clicking on them. After editing and closing the dialog, I also need to update the items on the list view. I'm currently doing this with the following code snippet in my list view item widget:
showDialog(
context: context,
builder: (context) {
return UpdateItemDialog(item: _item);
},
).then((updatedItem) {
if (updatedItem != null) {
setState(() => _item = updatedItem);
}
});
and by calling Navigator.of(context).pop(_item); from my dialog.
It works perfectly fine when there are no rebuilds occur until the dialog is dismissed. However, for example, if the orientation is changed when the dialog is open, I get Unhandled Exception: setState() called after dispose() error since the list view also rebuilt because of the orientation change and the then clause in my code runs on the destroyed widget.
Is there a way to access the rebuilt widget from a destroyed widget?
Or should I use a different approach to this problem?
By the way, changes should only be accepted after the dialog is dismissed, so I should return the item from the dialog.
I believe your best bet would be to introduce even a simple state management solution (Provider would be good) to handle communication between a dialog and other widgets.
Check out the Gist below (Run it on Dartpad.dev) as an example how you can keep it simple, yet clean and decoupled
https://gist.github.com/romanejaquez/8aed8d699fba8fdfff4b0966dfe47663
in which I show that instead of passing data from a dialog back to another widget, a State Management solution would allow you for a decoupled way of passing data back and forth, notifying each other and triggering rebuilds (which is kind of calling setState() since pretty much that's what you want - trigger a rebuild on the other widget. In this example, I'm sending a value back to the originating widget and triggering a rebuild (thanks to the Consumer widget listening to the changes triggered in the common service. That'd be my honest suggestion.
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
}
Ok, I am working on a media player app using flutter. Now when I press the play button, it changes to pause button. (Only the icon on the button changes)
The onPressed function for the button changes a boolean value and calls the setState() method.
It goes something like this.
bool _playing = false;
void _onPlayButtonPressed() {
setState () {
if (_playing)
_playing = false;
else
_playing = true;
}
}
I also have a function that returns the icon based on the value of _playing.
Widget _playButtonIcon() {
//This function has no setState() call
if (_playing)
//return pause icon
else
//return play icon
}
Everything works fine. The icon changes everytime I press the button. However as mentioned in docs and also in Flutter Demo App, setState() method calls the build method. Which redraws the entire widget tree including child widgets that are completely unchanged.
Is this approach justified or is this an overkill?
Do I have to put the button on a different Stateful Widget Class and call its build method via setState() everytime I tap this playButton?
What if I have other widgets that also changes the state of UI. Possibly changing different widgets?
What is the proper way to do this without having a performance hit?
Creating a play button that is a widget of its own with a state that maintains whether it is playing or not definitely makes sense. Now when you call setState on the parent widget it does call the build method, but as far as I know it does not necessarily redraw everything from scratch. If no changes are found in some of the embedded widgets it does not redraw them since they are already in the widget tree. Finally, it is okay to call setstate, however if your app starts getting bigger and you find yourself calling set state in too many places, and want to use global keys, I would advise looking into the Provider package, and making use of the ChangeNotifier/Consumer pattern.