Is it ok to return an variable from a cubit state function? - flutter

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!"));
}
}

Related

Change a dropdown's items when another dropdown's value is chosen in flutter (UI wont update)

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)

Which is the correct way to await for an Event in Flutter Bloc

my application is growing in complexity and I can't figure out how to handle the await of an Event, when I need to execute code after that Event. Now I'm just putting the code that I need after an event inside the Bloc and I now that this is not the way to do it, making my app a mess. This is how I am managing my app:
For example, if I need to add a user in my backend and after that, execute an action I do this in my view/screen:
BlocProvider.of<UserBloc>(context).add(AddUserEvent())
As events are async, I can't put the code after that line so Inside the UserBloc I am making:
on<HomeNavigationEvent>((event, emit) {
#Call backend api to create user
#Do my needed action
});
And some times this is even worst because I need to call another Bloc, so I have to pass the context to that Event, like this:
BlocProvider.of<UserBloc>(context).add(AddUserEvent(context))
on<HomeNavigationEvent>((event, emit) {
#Call backend api to create user
BlocProvider.of<OtherBloc>(event.context).add(MyNeededActionEvent())
});
So I think the answer is related with Bloc listener, but I don't know how to check for an event instead of a state I mean I can't do this because I am receiving the state but not the Event:
return BlocListener<UserBloc, UserState>(
listener: (context, state) { #I would like to have (context, state, event))
if (event is AddUserEvent) {
#DO my needed action
}
})
[EDITED]
Real case of my app:
VehiculoBloc() : super(const VehiculoState(vehicle: null)) {
on<GetCurrentVehicle>((event, emit) async {
vehicle = await api.getCurrentVehicle();
final bool showVehicleButton = vehicle != null;
BlocProvider.of<HomeBloc>(event.context).add(ShowVehicleButtonEvent(showVehicleButton ));
emit(state.copyWith(vehicle: vehicle));
});
}
The main purpose of using BLoC is to separate the UI from the state. And have a global, and immutable, state that you can access from wherever you wish. The purpose of the UI is the fire events and let the BLoC generate the Appropriante state. If you try to listen to event, why would you even use BLoC?
In situations like yours, you want to create a dedicated state for the AddUserEvent. For example AddUserStateSuccess Whenever the BLoC receives that event it will emit the corresponding state. All you have to do is listen for that state ;)
In the listener of that state you can fire other events from other BLoC as well.

How to attend best practice for not using UI code in the Controller with GetX flutter when I need to show a Dialog if my task complete.?

For a simple Email login with OTP code I have a structure as follows.
View
await _signUpCntrl.signUp(email, password);
Controller
_showOtpDialog(email);
_showOtpDialog func
return Get.dialog(
AlertDialog(
So the thing is _showOtpDialog function is inside a controller file. ie. /Controllers/controller_file.dart
I want do something like a blocListener, call the _showOtpDialog from a screen(view) file on signup success. (also relocate the _showOtpDialog to a view file)
Using GetX I have to use one of the builders either obs or getbuilder. Which is I think not a good approach to show a dialog box.
On internet it says Workers are the alternative to BlocListener. However Workers function resides on Controller file and with that the dialog is still being called on the controller file.
As OTP dialog will have its own state and a controller I wanted to put it inside a /view/viewfile.dart
How do I obtain this?
I tried using StateMixin but when I call Get.dialog() it throw an error.
visitChildElements() called during build
Unlike BLoC there's no BlocListener or BlocConsumer in GetX.
Instead GetX has RxWorkers. You can store your response object in a Rx variable:
class SomeController extends GetxController{
final response= Rxn<SomeResponse>();
Future<void> someMethod()async{
response.value = await someApiCall();
}
}
And then right before the return of your widget's build method:
class SomeWidget extends StatelessWidget{
final controller = Get.put(SomeController());
#override
Widget build(BuildContext context){
ever(controller.response, (SomeResponse res){
if(res.success){
return Get.dialog(SuccessDialog()); //Or snackbar, or navigate to another page
}
....
});
return UI();
}
First thing, you will need to enhance the quality of your question by making things more clearly. Add the code block and the number list, highlight those and making emphasize texts are bold. Use the code block instead of quote.
Seconds things, Depends on the state management you are using, we will have different approaches:
Bloc (As you already added to the question tag). By using this state management, you controller ( business logic handler) will act like the view model in the MVVM architecture. In terms of that, You will need to emit a state (e.g: Sent success event). Afterward, the UI will listen to the changes and update it value according to the event you have emitted. See this Bloc example
GetX (As your code and question pointed out): GetX will acts a little bit different. you have multiple ways to implement this:
Using callbacks (passed at the start when calling the send otp function)
Declare a general dialog for your application ( this is the most used when it comes to realization) and calling show Dialog from Bloc
Using Rx. You will define a Reactive Variable for e.g final success = RxBool(true). Then the view will listen and update whenever the success changes.
controller.dart
class MyController extends GetxController {
final success = RxBool(false);
void sendOtp() async {
final result = await repository.sendOTP();
success.update((val) => {true});
}
}
view.dart
class MyUI extends GetView<MyController> {
#override
Widget build(BuildContext context) {
ever(controller.success, (bool success) {
// This will update things whenever success is updated
if (success) {
Get.dialog(AlertDialog());
}
});
return Container();
}
}

flutter rxdart Observable - how to unsubscribe

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.

How can you get setState.of(context) in Flutter?

I have an app with a ModalProgresHUD on most pages. Usually I can pass a fucntion to, for example, onTap, to any widget in this tree, to turn this spinner on/off.
But sometimes this seems difficult and I'd like to access the fields and/or setState on a State somewhere else, up the WidgetTree.
One option seems to be to move all the logic into the top Widget, and pass handlers down to access these methods, but that feels cludgy.
class StatefullPage ..... {
String _someImportantField;
set someImportantField(String newValue) {
_someImportantField = newValue;
if(mounted) setState((){});
}
...
}
class StateOfSomethingElse ... {
Future doSomeWorkThatAffectsTheParent() async {
await something.then((String newResult) {
State.of(context).someImportantField = newResult;// HOW TO DO THIS
}
...
}
What kind of state management do you use? It looks that you are simply using setState and there's no problem with that. But to do the sort of think you want i think it's better to use something like provider or redux to handle the state of your app.
Using Provider for example, you can provide a value on the top of your tree and can get this value anywhere on your widget tree to do your logic.