How can you get setState.of(context) in Flutter? - 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.

Related

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

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

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();
}
}

How best to make my scroll controllers available throughout the app?

Context: I'll be having a couple of scrollable lists in my app and I always want to scroll them to the latest item whenever an item is added.
Problem: My ListView.builders and the places where items are added are going to be quite far apart in my widget tree. Passing around all those scroll controllers via constructors seems to be super awkward.
My Solution:As I'm practising with Provider at the moment, I came up with a working solution using Provider:
class ScrollControllerProvider with ChangeNotifier {
ScrollController _paneController = ScrollController();
//setting up all other controllers here later
get paneController {
return _paneController;
}
void scrollHistory() {
WidgetsBinding.instance?.addPostFrameCallback((_) {
if (_paneController.hasClients) {
_paneController.jumpTo(_paneController.position.maxScrollExtent);
}
});
}
}
I'll add all scroll controllers to that provider and grab what I need, where I need it. It already works with one, but someone on reddit told me it's not a good idea, as scroll controllers should be disposed. Im not super knowledgeable on the topic of life cycle yet and find it difficult to assess this.
Questions: Is it really a bad idea to use Provider here? Can you help me to understand why? If yes, what is the best approach to solve this issue?
Provider is not the problem, using a disposable item inside a provider is. ScrollController is a disposable item related to its main Widget, or better to say its State.
If you want to notify your widgets about newly added items, create a variable inside the provider and listen to that variable in your widgets, then use your ScrollController to change the position.
To find out more about your question take a look at ScrollController class and Disposable class
For posterity, Payam Asefi pointed me in the right direction.
How I'm doing it now.
tldr; Provider contains a value that can be toggled and a method to toggle it. I provide the value where I can also access the scroll controler. If it is toggled, the scroll conroler is used. I provide the method to toggle the value where I add new items to the list.
item is added > value in provider is triggered > listeners realized the value has changed calling the build method > scroll controller is used to go to maxscrollextend.
Long answer with code:
Provider with a) a bool that can be toggled b) a method to toggle the bool c) a getter for the bool
Code:
class ScrollControllerToggles with ChangeNotifier {
bool _historyPaneSwitch = true;
get getTogglePaneSwitch {
return _historyPaneSwitch;
}
void toggleHistoryPane() {
_historyPaneSwitch = !_historyPaneSwitch;
notifyListeners();
}
}
In the widget I'm using the Listview.builder: a) I define a scroll controller, b) I use a function dependent on the _historyPaneSwitch inside that Provider. That funtion also uses the scroll controller to scroll the list to the end.
void triggerScrollController() {
bool scrollHistoryPane =
Provider.of<ScrollControllerToggles>(context).getTogglePaneSwitch;
WidgetsBinding.instance?.addPostFrameCallback((_) {
if (paneController.hasClients) {
paneController.jumpTo(paneController.position.maxScrollExtent);
}
});
}
In the widget adding new items to the list, I access the Provider again and grab the method to toggle "_historyPaneSwitch".
Function scrollHistoryPane =
Provider.of<ScrollControllerToggles>(context).toggleHistoryPane;
void dayChange(Function scrollHistoryPane) {
mainElementList.insert(0, MainElement(false, DateTime.now().toString()));
scrollHistoryPane;
}

How can I listen to a String variable change and perform an action when there is a change?

Is it possible to execute a function, lets call it myFunction() if a variable _myString is changed, but have this happen on the fly?
What I have is a textfield with a controller
var _myString;
const TextField(
controller:_controller
)
Now elsewhere in my widget tree, I have a button that can change the value of _myString, in this case I'm changing '_myString' to 'Testing'
GestureDetector(
onTap:(){ _myString = 'Testing'; }
child: Text('Testing')
)
Now what I'm hoping to achieve is that when the value of _myString changes in any way, I can perform some action of my choosing. In this case, I want to edit the TextField using the _controller, but I don't only want to do that, but a few other things, so I think its better to listen to changes in that variable and then execute a function
void myFunction(){
///Do some stuff
}
I'm using riverpod for state management in my app, and I was thinking I could try to use it, but have no idea how to use it to watch a single variable, I'm more familiar with using it for entire widgets. Alternatively using riverpod for something like this might be overkill.
I just don't know how to approach this problem, so any guidance would be really appreciated!
I believe you could use a ValueNotifier for this, a value notifier is a class that holds some value and "notifies" its listeners when this value changes. It is a simpler version of ChangeNotifier:
ValueNotifier<String> _myString = ValueNotifier<String>('');
With the above, whenever you want to read or write the value, use the value getter/setter:
print(_myString.value);
_myString.value = 'some value';
Now, to listen to changes you should use the addListener method:
#override
initState() {
// update _controller with value whenever _myString changes
_myString.addListener(() => _controller.text = _myString.value);
// print value on change
_myString.addListener(() => print(_myString.value));
// do stuff
_myString.addListener(myFunction)
}

Riverpod - ref.onDispose called when object is created, and not when disposed

I have this ViewModel and a Riverpod provider for it:
final signInViewModelProvider = Provider.autoDispose<SignInViewModel>((ref) {
final vm = SignInViewModel();
ref.onDispose(() {
vm.cleanUp();
});
return vm;
});
class SignInViewModel extends VpViewModelNew {
FormGroup get form => _form;
String get emailKey => _emailKey;
String get passwordKey => _passwordKey;
final String _emailKey = UserSignInFieldKeys.email;
final String _passwordKey = UserSignInFieldKeys.password;
final FormGroup _form = FormGroup({
UserSignInFieldKeys.email:
FormControl<String>(validators: [Validators.required]),
UserSignInFieldKeys.password:
FormControl<String>(validators: [Validators.required])
});
void cleanUp() {
print('cleaning up');
}
void onSubmitPressed(BuildContext context) {
// _saveRegistrationLocallyUseCase.invoke(
// form.control(_self.emailKey).value as String ?? '',
// form.control(_self.passwordKey).value as String ?? '');
}
}
abstract class VpViewModelNew {
VpViewModelNew() {
if (onCreate != null) {
onCreate();
print('creating');
}
}
void onCreate() {}
}
When I navigate to the page that has the signInViewModelProvider, it prints to the console:
flutter: signInPage building
flutter: creating
flutter: cleaning up
Then popping the page from the stack with Navigator.pop() prints nothing.
Then navigating to the page again prints the same 3 lines in the same order.
I expected onDispose to be called after Navigator.pop(), and not when navigating to the page that reads the provider. Why is onDispose being called directly after creation, and not when using Navigator.pop() (when I expected the provider to be disposed of since no other views reference it)?
Edit: I access the provider with final viewModel = context.read<SignInViewModel>(signInViewModelProvider);
I don't need to listen since I don't need to rebuild the page on
change. Is consumer less performant for this?
No, the performance is meaningless, even if it's listening it's not really affecting the performance because as a Provider there is no way to notify (which is not the case with a state notifier or change notifier)
Also if you don't care to listen after the value has been read The auto dispose understand no one is watching it and it disposes, it's better to use context.read when using tap or gestures that modify something
(I realize this is late to the party but maybe it'll help somebody)
The Riverpod docs come out pretty strongly against using read for the reason you said, i.e. performance/rebuilding concerns.
Basically you should always use watch except:
If you want your custom callback function called when it updates (use listen)
If the actual reading is happening asynchronously or in response to user action (like in an onPressed): this is the only time to use read.
If you're having issues with your widgets rebuilding too often, Riverpod has some ways to deal with that that don't involve using read.