Here is my use case:
I have a following stream set:
PublishSubject<RewardedVideoAdEvent> _outVideoAdController =
PublishSubject<RewardedVideoAdEvent>();
StreamSink<RewardedVideoAdEvent> get _videoAdEvents => _outVideoAdController.sink;
Observable<RewardedVideoAdEvent> get outVideoAdEvents => _outVideoAdController.stream;
Now, I want to listen to outVideoAdEvents, hence I add this to my StatefullWidget initState method:
...
if (mounted) {
final AdMobBloc adMob =
BlocProvider.of<AppProvider>(context).application.adMobBloc;
adMob.outVideoAdEvents.listen((RewardedVideoAdEvent event) {
if (event == RewardedVideoAdEvent.rewarded){
// do something meaningfull
}
});
...
So far, so good.
The issue I have got - when if I open another widget and then come back to this one, initState executes again and hence, I have added another listener to the same stream. Then, the next time I issue an event into _outVideoAdController.sink, the callback will be executed twice.
Unfortunately, unlike initState, dispose does not execute each time I load another page, so I cannot figure out how to handle the above case.
Please note, the app uses rxdart: ^0.20.0
Any hints will be greatly appreciated!
you can unsubscribe the observable by this method:
_outVideoAdController?.close()
In case, someone else came across the same issue, these are the steps:
Add a private variable inside the widget from type StreamSubscription<T>, where T is your event type. In my case it is RewardedVideoAdEvent, hence I added StreamSubscription<RewardedVideoAdEvent> _videoAdSubscription;.
Then, when subscribing to the stream, the listen will return the value from this type, so just take it: _videoAdSubscription = adMob.outVideoAdEvents.listen((RewardedVideoAdEvent event) {});
Finally, when you want to unsubscribe, just call _videoAdSubscription?.cancel();
That's all.
Related
I have two drop downs, and I want to do things when they get values selected. One of those is to change the second buttondrop items based on what's selected in the first dropdown.
For example:
Dropdown1 is a list of car manufactuers
Dropdown2 is a list of their models
Dropdown1 selects mercedes
Dropdown2 gets "E Class, S Class" etc
Dropdown1 selects lexus
Dropdown2 gets "ES, LS", etc
(Eventually the second drop down will update a listview as well, but haven't gotten to that yet.)
Data wise, it works, I update the list. The problem is the UI won't update unless I do a hot reload
Currently I am just having the dropdowns fetch their data and using Future builders
Future? data1;
Future? data2;
void initState(){
super.initState();
data1 = _data1AsyncMethod();
data2 = _data2AsyncMethod();
}
_data2AsyncMethod([int? item1_id]) async{
if(item1_id == null){
item2Classes = await DefaultItems().getAllItem2Classes();
listOfItem2ClassNames = DefaultItems().returnListOfItemClassNames(item2Classes);
}
else{
// The methods below calls the DefaultItems methods which have Futures all in them.
// The getAllItems calls a network file with GET methods of future type to get data and decodes them, etc.
// They build a list of the object type, ex List<Item2>
item2Classes = await DefaultItems().getAllItem2Classes(item1_id);
listOfItem2ClassNames = DefaultItems().returnListOfItemClassNames(item2Classes);
}
}
I have this Future Builder nested in some containers and paddings
FutureBuilder{
future: data2,
builder: (context, snapshot){
if(snapshot.connectionState != done...)
// return a circle progress indictator here
else{
return CustomDropDown{
hintText: 'example hint'
dropDownType: 'name'
dropDownList: listOfItem2ClassNames
dropDownCallback: whichDropDown,
}
The onChanged in CustomDropDown passes the dropDownType and the dropDownValue
The callback
whichDropDown(String dropDownType, String dropDownValue){
if(dropDownType == 'item1'){
//so if the first dropdown was used
// some code to get item_1's id and I call the data2 method
_data2AsyncMethod(item1_id);
}
Again the data updates (listOfItem2ClassNames) BUT the UI won't update unless I hot reload. I've even called just setState without any inputs to refresh but doesn't work
So how do I get the UI to update with the data, and is my solution too convoluted in the first place? How should I solve? StreamBuilders? I was having trouble using them.
Thanks
If you do a setState in the whichDropDown function, it will rebuild the UI. Although I'm not exactly sure what you want, your question is really ambiguous.
whichDropDown(String dropDownType, String dropDownValue){
if(dropDownType == 'item1'){
//so if the first dropdown was used
// some code to get item_1's id and I call the data2 method
_data2AsyncMethod(item1_id).then((_) {
setState(() {});
});
}
}
I notice a couple things:
nothing is causing the state to update, which is what causes a rebuild. Usually this is done explicitly with a call to setState()
in whichDropdown(), you call _data2AsyncMethod(item1_id), but that is returning a new Future, not updating data2, which means your FutureBuilder has no reason to update. Future's only go from un-completed to completed once, so once the Future in the FutureBuilder has been completed, there's no reason the widget will update again.
You may want to think about redesigning this widget a bit, perhaps rather than relying on FutureBuilder, instead call setState to react to the completion of the Futures (which can be done repeatedly, as opposed to how FutureBuilder works)
Is it ok to return a value from a Cubit state function or is it better to emit a state and use BlocListener?
Future<Game?> addGame(List<String> players, int numOfRounds) async {
try {
Game game = await repository.addGame(DateTime.now(), players, numOfRounds);
return game;
} on Exception {
emit(GamesError(message: "Could not fetch the list, please try again later!"));
}
}
The widget that calls this function adds a game and then redirects to a new page and passes the game object to it.
This works but it doesn't feel like it is the right approach. Is it ok to do this or should I be emitting a new state and using the BlocListener to redirect to the new page?
Of course, it's not.
Bloc/Cubit is the single source of truth for the widget. All data that comes to the widget should be passed via state, one source. If you return values from Cubit methods, you are breaking the whole concept of the Bloc pattern.
Bloc data flow
It is ok, but not preferred.
Presently the function addGame returns a future, so you would have to use FutureBuilder to display it's value.
Instead emit state having containing the value,Now you can use BlocListener and BlocBuilder to display the value of game produced in the function addGame. So now the purpose of using bloc makes sense.
Use code like:
Future<Game?> addGame(List<String> players, int numOfRounds) async {
try {
Game game = await repository.addGame(DateTime.now(), players, numOfRounds);
emit(GameLoaded(game: game); // 👈 Use it this way
} on Exception {
emit(GamesError(message: "Could not fetch the list, please try again later!"));
}
}
I created a list and made it observable by using using getx obs
var msgChat = List.filled(1, UserMessagesModel(), growable: true).obs;
and if i append or add a new to it, it automatically updates in the UI but my problem is that if i using a single value inside the classes of the list it doesn't get updated until i do it for the second time before it updates
socket.on("msg_delivered", (del) {
OfflineDatabaseManager.instance.updateMsgReadData(del).then((value) {
uController.msgChat.forEach((el) {
if (el.msgId == del) {
print(el.msgId);
el.isRead = 'true';
}
});
});
});
my listview is wrap using an Obx widget which makes it work but it doesn't updated if a single value in the class of the list is changed please is there a way to like refresh the list or listview and also i can't use setState(() {}); because an not inside a Statefulbuilder but in a class which extends to getx
class SocketHandler extends GetxController {}
please how do i fix this issue. Thank. Also if you require more explanation then tell me to explain
you can use refresh method like this on your observable, after changing the value:
msgChat.refresh()
if this didn't work, it's probably because you didn't use Obs correctly
please share the Obs part if the problem remains so i can help
Hi I am using this code here to retrieve data from Firestore. The data is in a Map and here is the picture of my Firestore.
And here is my code when I retrieve from Firestore
Future <void> getRoute() async{
Firestore.instance.collection('routes').snapshots().listen((RouteData) {
if(RouteData.documents.isNotEmpty){
for (int i = 0; i < RouteData.documents.length; i++){
_getPolyline(RouteData.documents[i].data, RouteData.documents[i].documentID);
}
}
});
setState(() {
});
}
void _getPolyline(info, infoID) async{
print(info);}
When Im printing the info it will display the data many times.
here is the result. As you can see it starts off in test then test1 then test again and repeated for some time. Is there something wrong with my code? Thank you in advance.
Calling setState will trigger a rebuild of your widget. What i think is happening is that when getRoute() u are also triggering your build function which will trigger getRoute() to run again.
solution:
make sure your function is not being triggered during a build. you can do this by triggering your function only once in initState
Either that or because u are using snapshots().listen. this will listen to any changes that happen on that collection.
solution:
use .get() instead of .listen()
To read a collection or document once, call the Query.get or DocumentReference.get methods. In the below example a FutureBuilder is used to help manage the state of the request:
https://firebase.flutter.dev/docs/firestore/usage/#one-time-read
My scenario is like this:
a BottomNavigationBar where one page contains a list of items ("listPage") and another page is a single item ("itemPage").
both pages can navigate to "itemPage" related to a different items.
the "itemPage" show details of the product and have a "Favourite" toggle button.
in the "listPage" every item shown via ListView.builder, show and can handle the "Favourite" toggle button.
I can't understand how reflect the "Favourite" change in an "itemPage" to others "itemPage" (if opened multiple time for the same item, yes, it's possible) and also to the same item in the "pageList".
I created a NotifierBloc where a BehaviorSubject > Sink is called every time a "Favourite" toggle button change its state (putting the itemId and the boolean value of the Favourite). After a database update an output PublishSubject > Stream is filled with the additional information of the item.
In this way every time a "Favourite" is toggled, all the subscribers receive the info about the flag.
BehaviorSubject<Item> inController = new BehaviorSubject<Item>();
Sink<Item> get putUpdate => inController.sink;
final PublishSubject<Item> outController = PublishSubject<Item>();
Stream<Item> get getUpdates => outController.stream;
NotifierBloc() {
inController.listen(_handleToggle);
}
_handleToggle(Item item) {
...
outController.sink.add(newItemAfterDatabaseCall);
}
In ListPage and ItemPage (both StatefulWidget) I created a StreamSubscription in DidChangeDependencies method which listen to the NotifierBloc Stream.
The StreamSubscription will be cancelled in the Dispose method.
StreamSubscription _subscription;
void didChangeDependencies() {
super.didChangeDependencies();
_subscription = _notifierBloc.getUpdates.listen((item) => {
// Do things with the Item like setState or call bloc methods
...
});
}
void dispose() {
_subscription?.cancel();
super.dispose();
}
Problems are when a new itemPage is opened: the dispose method cancel the subscription in the previous shown page, so new events will not be get listened.
In addition when a page is shown due to a previous page close, the StreamSubscription is renewed and I get updates about the last one Favourite change, but I need a list of Favourite changes, because maybe the user opened several "itemPage"s.
How can I solve?
Maybe the Stream must be passed to the Page(not the PageState)? But how to handle onData function?
You need some sort of state management system. Look into Provider. Here is a simple example of how you could use it. https://github.com/m-Skolnick/provider_example_flutter