How to put Flutter GetXController permanently in memory? - flutter

I am using get package.
Here's what my code looks like,
class MyController extends GetXController{
//code to fetch data from firebase
}
class SecondScreen extends GetView<MyController>{
#override
Widget build(BuildContext context) {
return GetBuilder(
init: MyController(),
builder: (controller) {
return Scaffold(
//code...
);
},
);
}
}
Doubt:
I have a button, using which I am navigating to the secondScreen from homePage, and everytime I tap on the button the controller MyController is initialized again and so the data is fetched again. But I want to do something that will keep that controller that is initizlized the first time in memory permanently. How can I do that?
I know that, we can do something like this,
Get.put(Controller(), permanent: true);
But, in my code, I haven't used Get.put method anywhere as when the class extending GetView is called the controller is initialized automatically.

Well, actually you are putting/initializing MyController. Just not inside the GetX dependency container.
Because you are doing:
GetBuilder(
init: MyController(),
....
)
What you should do instead is:
GetBuilders(
init: Get.put(MyController()),
....
)
That way you are letting GetX dependency manager to manage your dependencies. And it's smart enough to know that that route is on the backstack so doesn't remove from memory.

Adding "permanent" to where you use "get.put" will fix the problem
Get.put(Controller(), permanent: true);
my widgets were not staying permanently, this is how I solved it

Related

How can you access GoRoute state.params outside of the routing process?

I hope some simple pseudocode is enough so that both me and you can understand the question and answer.
The problem I'm facing is especially hard when using flutter-web, where the refresh restarts the whole program.
I want to use the path parameters to build objects in a child widgets build method.
GoRouter(routes:...,GoRoute(path="/categories/:category/",
builder:(context,state){
category = state.params['category];
return ParentWidget(category);}
ParentWidget extends StatelessWidget {
build(context){
return ChildWidget();}}
ChildWidget extends StatlessWidget {
build(context){
return "do something with category";}}
Now one way which I can think of and should technically work without any errors would be to pass the params first into the ParentWidget and then pass it along to the next child and so on. But if there's a long chain of child widgets it gets quite tedious and I'm guessing error prone as well. The other thing I was thinking was to use providers: pass the param once again to the parent widget and then make the parent widget send it to a provider. But then the question becomes, where do I do it? Apparently I shouldn't update a provider on build(), but if I do it on initState() it only does it bugs out if I change into a route that include the same widget tree but different path e.g. /categories/apples -> /categories/bananas.
Ps. For some reason I don't remember what the problem with refreshing was. (It has something to do with resetting the providers). But I'll update it when I remember.
go_router has it's params in its state.
Hence pass the state to the page
Router
GoRoute(
name: "test",
path: "/test/:id",
builder: (context, state) {
return SampleWidget(
goRouterState: state, πŸ‘ˆ Pass state here
);
},
),
Usage
context.goNamed("test", params: {"id": "123"}),
Accesing in the page
class SampleWidget extends StatelessWidget {
GoRouterState? goRouterState;
SampleWidget({super.key, this.goRouterState});
#override
Widget build(BuildContext context) {
print(goRouterState?.params.toString()); πŸ‘ˆ access anywhere like so
return const Scaffold(
body: ...
);
}
}

Flutter GetX: Where Does Get.put Go in a Widget?

I am new to GetX and am trying to learn how to use it. I have read different tutorials that inject the controller outside of the widget's build method, and others that put it inside it.
class MyWidget extends StatelessWidget{
const MyWidget({Key? key}) : super(key:key);
//Outside...
final controller = Get.put(Controller()); //<---
#override
Widget build(BuildContext context) {
//Inside...
final controller = Get.put(Controller()); //<---
return Obx(
() => Text(controller.name)
);
}
}
Is there a difference between those two locations? If so, why?
Also, where should it go in a StatefulWidget? It seems it should not go inside the build method because it causes a stack overflow error for me.
Does the location of Get.put() matter inside a widget?
The normal way is to put the controller outside the widget, so it will be created once. if you but it inside the widget a new instance of the controller will be created each time you refresh (update) the widget.
Also, with GetX there is no need to use StatefulWidget.
When you put it inside the build method, it will create a new instance every time the widget is rebuilt, so it is better to but it outside and in most cases I think you do not need to use StatefulWidgetwith GetX even in animation.
The answers shared are correct. Thank you! I wanted to post another answer that I consider to be an even better way to do it as referenced in the documentation.
This method allows for controller access without ever putting Get.put in any of your widgets. This is also really helpful if you have a lot of controllers and need to reference them in widgets as well as from other controllers.
Here is an example:
//ControllerA
class ControllerA extends GetxController {
static ControllerA get to => Get.find();
final name = 'Bob'.obs;
someMethod(){
ControllerB.to.anotherMethod(); //'I am inside ControllerB!'
}
}
//ControllerB
class ControllerB extends GetxController {
//+++
static ControllerB get to => Get.find();
anotherMethod(){
print('I am inside ControllerB!');
}
}
And then inside Widgets:
class MyWidget extends StatelessWidget{
const MyWidget({Key? key}) : super(key:key);
#override
Widget build(BuildContext context) {
return Obx(
() => Text(ControllerA.to.name) //Bob
);
}
}
This requires that you add your Get.put declarations in main.dart so that you make sure they are all ready:
void main() {
Get.put(ControllerA());
Get.put(ControllerB());
runApp(
GetMaterialApp(...)
);
}
In my opinion, it's really clean like this--and very convenient!
If we want to keep a GetX controller in memory forever, then we should indeed instantiate it outside of a Widget's build() function (such as within main() as Clifton shows).
(Note that we can also use GetXService for persistent controllers, which allows manual disposal.)
Placing controllers inside build() functions, is the correct place when we want GetX to free memory & dispose the controller when the widget goes "out of scope". (e.g. when the user has "popped" the route from the stack.) There's also no danger in "recreating" the controller when its instantiated inside build(): Get checks for existence of a controller when Get.put() is called and skips it if the controller is already instantiated.
See this answer (and link to an explanation by one of Get's maintainers) for more info on why we should Get.put() inside build().

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

is is ok if show progress indicator inside the viewmodel pattern?( Flutter question)

I'm using MVVM pattern in my project and i show the spinner inside the viewmodel by passing the build context to the view model method
for example :
void getSomeData(BuildContext context,int someDataID){
showSpinner(context);
}
the mentioned method getSomeData(....) is called from screen file
it works fine but i just wanna know will that make any problem?
Thanks
I think it is an ok pattern to use but it may cause problems in the future when your app is much bigger. You may either go to debug your viewmodel and view or you want to extend some functionality based on or closely related to the spinner and find it hard to navigate through your code to find what you want as the viewmodel and view are very intertwined.
Strictly speaking the viewmodel should direct the view via state variables held within the viewmodel, rather than allowing the viewmodel to make direct changes to the view via context. This helps keep all the build code in the view and all the logic that controls that in the viewmodel. Below is an example of what I mean using a ChangeNotifier as a viewmodel (using the provider package) e.g.
class ViewModel extends ChangeNotifier {
bool _showSpinner = false;
bool get shouldShowSpinner => _showSpinner;
void showSpinner() {
_showSpinner = true;
notifyListeners() // (Method for ChangeNotifiers) Or equivalent call to rebuild the view
}
void hideSpinner() {
_showSpinner = false;
notifyListeners() // (Method for ChangeNotifiers) Or equivalent call to rebuild the view
}
}
class View extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<ViewModel>(
create: (context) => ViewModel(),
child: Consumer<ViewModel>(
build: (context, model, child) {
return Stack(
children: [
SomePageContents(),
if (model.shouldShowSpinner) Spinner(),
],
);
}
)
);
}
}
Just to reiterate, I think this helps maintainability in the long run but if your method works for you, i.e. the scope of your project is relatively small and your original way doesn't cause too many dependency issues then I'd say do it your original way as the MVVM method should help not hinder development.

How to call into a flutter widget's state from the widget

I'm new to dart/flutter and having a little trouble getting my head around communication patterns.
One reoccurring problem is that I keep looking to expose a public method on a widget so it can be called by other widgets.
The problem is with stateful widgets. In these cases, I need to call down to the widgets state to do the actual work.
The problem is that the widget doesn't have a copy of the state.
I have been saving a copy of the state in the widget but of course this throws a warning as it makes the widget mutable.
Let me give a specific example:
I have a specialised menu which can have a set of menu items.
Each are stateful.
When the menu is closing it needs to iterate over the list of menu items that it owns and tell each one to hide (the menu items are not visually contained within the menu so hiding the menu doesn't work).
So the menu has the following code:
class Menu{
closeMenu() {
for (var menuItem in menuItems) {
menuItem.close();
}
}
So that works fine, but of course in the MenuItem class I need to:
class MenuItem {
MenuItemState state;
close()
{
state.close();
}
But of course having the state object stored In the MenuItem is a problem given that MenuItem is meant to be immutable. (It is only a warning so the code works, but its clearly not the intended design pattern).
I could do with seeing more of your code to get a better idea of how to solve your specific issue but it appears that the Flutter documentation will help you in some regard, specifically the section on Lifting state up:
In Flutter, it makes sense to keep the state above the widgets that use it.
Why? In declarative frameworks like Flutter, if you want to change the UI, you have to rebuild it.
…it’s hard to imperatively change a widget from outside, by calling a method on it. And even if you could make this work, you would be fighting the framework instead of letting it help you.
It appears you're trying to fight the framework in your example and that you were correct to be apprehensive about adding public methods to your Widgets. What you need to do is something closer to what's detailed in the documentation (which details all of the new classes etc you'll see below). I've put a quick example together based on this and the use of Provider which makes this approach to state management easy. Here's a Google I/O talk from this year encouraging its use.
void main() {
runApp(
ChangeNotifierProvider(
builder: (context) => MenuModel(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
…
// call this when the menu is closed
void onMyMenuClosed(BuildContext context) {
var menuModel = getMyMenuModel(context);
menuModel.hideMenuItems();
}
}
class MenuModel extends ChangeNotifier {
bool _displayItems = false;
void hideMenuItems() {
_displayItems = false;
notifyListeners();
}
void showMenuItems() {
_displayItems = true;
notifyListeners();
}
}
Calling hideMenuItems() makes a call to notifyListeners() that'll do just that; notify any listeners of a change which in turn prompts a rebuild of the Widget/s you wrap in a Consumer<MenuModel> Now, when the Widget that displays the menu is rebuilt, it just grabs the appropriate detail from the MenuModel class - the one source of truth for the state. This reduces the number of code paths you'd otherwise have to deal with to one and makes it far easier to see what's happening when you make further changes.
#override
Widget build(BuildContext context) {
return Consumer<MenuModel>(
builder: (context, menuModel, child) {
return menuModel._displayItems() ? MenuItemsWidget() : Container();
},
);
}
I recommend you read the entire page on state management.