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

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().

Related

Avoid no_logic_in_create_state warning when saving reference to State of StatefulWidget in Flutter

Using Flutter, I display a list of elements in an app.
I have a StatefulWidget (ObjectList) that holds a list of items in its State (ObjectListState).
The state has a method (_populateList) to update the list of items.
I want the list to be updated when a method (updateList) is called on the widget.
To achieve this, I save a reference (_state) to the state in the widget. The value is set in createState. Then the method on the state can be called from the widget itself.
class ObjectList extends StatefulWidget {
const ObjectList({super.key});
static late ObjectListState _state;
#override
State<ObjectList> createState() {
_state = ObjectListState();
return _state;
}
void updateList() {
_state._populateList();
}
}
class ObjectListState extends State<ObjectList> {
List<Object>? objects;
void _populateList() {
setState(() {
// objects = API().getObjects();
});
}
// ... return ListView in build
}
The problem is that this raises a no_logic_in_create_state warning. Since I'm not "passing data to State objects" I assume this is fine, but I would still like to avoid the warning.
Is there a way to do any of these?
Saving the reference to the state without violating no_logic_in_create_state.
Accessing the state from the widget, without saving the reference.
Calling the method of the state from the outside without going through the widget.
It make no sense to put the updateList() method in the widget. You will not be able to call it anyway. Widgets are part of a widget tree, and you do not store or use a reference to them (unlike in other frameworks, such as Qt).
To update information in a widget, use a StreamBuilder widget and create the widget to be updated in the build function, passing the updated list to as a parameter to the widget.
Then, you store the list inside the widget. Possibly this may then be implemented as a stateless widget.

How to get the State<> instance inside of its StatefulWidget?

I have an unusual use case where I'd like to add a getter to a StatefulWidget class that accesses its State instance. Something like this:
class Foo extends StatefulWidget {
Foo({super.key});
int get bar => SomethingThatGetsFooState.bar;
#override
State<Foo> createState() => _FooState();
}
class _FooState extends State<Foo> {
int bar = 42;
#override
Widget build(BuildContext context) {
return Container();
}
}
Does SomethingThatGetsFooState exist?
I wonder, if your approach is the right way.
Flutter's way isn't 'Ask something about its state'
Flutter is much more like this: 'The consumer of a Widget passes something to another Widget, which the other Widget e.g. calls in case of certain situations (e.g. value change).'
Approach 1
You map pass a Callback Function to Foo and pass that along to _FooState.
If something special happens inside _FooState, you may call the callback and thus pass some value back to the provider of the Callback.
Approach 2
Probably use a state management solution like Flutter Redux. Using Flutter Redux, you establish a state store somewhere at the top of the widget tree, e.g. in MaterialApp.
Then you subscribe to the store at certain other locations, where dependent widgets exist, which need to update accordingly.
In one project I created certain Action classes, which I send to certain so called reducers of those states, to perform a change on the state:
StoreProvider.of<EModel>(context).dispatch( SaveToFileAction())
This call finds the relevant EModel state and asks it to perform the SaveToFileAction().
This way, a calling Widget not even needs to know, who is responsible for the Action.
The responsible Widget can even be moved around the widget tree - and the application still works. The initiator of SaveToFileAction() and the receiver are decoupled. The receiver you told a coordination 'Tell me, if someone tried to ask me for something.'
Could your provide some further details? Describe the usage pattern?
#SteAp is correct for suggesting there's a code smell in my OP. Typically there's no need to access State thru its StatefulWidget. But as I responded to his post, I'm fleshing out the first pass at a state management package, so it's an unusual case. Once I get it working, I'll revisit.
Below worked without Flutter complaining.
class _IntHolder {
int value;
}
class Foo extends StatefulWidget {
Foo({super.key});
final _intHolder = _IntHolder();
int get bar => _intHolder.value;
#override
State<Foo> createState() => _FooState();
}
class _FooState extends State<Foo> {
int value = 42;
#override
Widget build(BuildContext context) {
widget._intHolder.value = value;
return Container();
}
}

How to put Flutter GetXController permanently in memory?

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

Safe usage for useScrollController? (Flutter Hooks)

Would the following code be considered safe?
class SomeWidget extends HookWidget {
#override
Widget build(BuildContext context) {
final controller = useScrollController();
controller.addListener(_someCallback);
return ...;
}
}
I'm specifically referring to the addListener. In this ResoCoder hooks tutorial he adds the listener inside the initHook function of a custom hook.
I know that ResoCoder wrote the custom hook to dispose of the scrollController...I'm more curious as to how the controller listener behaves (I have no idea what is allowed and not allowed for listeners). Any resources on where I can learn about them would be great.
Thanks :)
Side-effects such as adding listeners should not be done directly inside build. If the widget rebuilt, that would cause the listener to be added again
Instead, you can use useEffect:
final controller = useScrollController();
useEffect(() {
controller.addListener(_someCallback);
return () => controller.removeListener(_someCallback);
}, [controller]);

Purpose of StatefulWidgets in Flutter?

Here is an example of a StatefulWidget. But isn't it just boiler plate code? Why do we need two classes? Normally you copy&paste the extends StatefulWidget part but where is the purpose? Is there some hidden functionality? Or is it some kind of abstraction level? Why was this design chosen by the Flutter team?
class Counter extends StatefulWidget {
int someVar;
Counter(this.someVar);
#override
_CounterState createState() => new _CounterState();
}
And here is the State class. Wouldn't be this class sufficient? Why do we need two classes for a StatefulWidget?
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
++_counter;
});
}
#override
Widget build(BuildContext context) {
return new Row(children: <Widget>[
new CounterIncrementor(onPressed: _increment),
new CounterDisplay(count: _counter),
]);
}
}
Because all Widgets's are immutable. This means that your Counter class in this case is immutable and thus also all variables should be final. The State, however, is not mutable.
You can check out the documentation about the Widget class to read more about its purpose.
A widget is an immutable description of part of a user interface. [...] Widgets themselves have no mutable state (all their fields must be final). If you wish to associate mutable state with a widget, consider using a StatefulWidget, which creates a State object (via StatefulWidget.createState) whenever it is inflated into an element and incorporated into the tree.
Dividing a stateful widget in two is a requirement of some of flutter patterns. Widgets being the representation of a pure function you'd find in functional programming.
In fact React, a library from which flutter takes inspiration use two separate objects too. It's fairly obvious when using the following syntax :
class MyComponent extends React.Component<Props, State> {
state: State = {
foo: 42,
}
...
}
Okey, but why is it that State and the Stateful subclass are deeply linked ? Why can't I use any class as State ?
Ah, that's an interesting question ! For those who've used react before, this is something you can do out there. React can use any object as it's state. Flutter being so similar, why can't we too ?
After all, with a bit of tweaks there's nothing preventing the following syntax, not even the #immutable flag :
class MyStateful extends StatefulWidget<Video> {
#override
void dispose() {}
#override
build(BuildContext context) {
return new Text(state.title);
}
}
So why ?
A quote from flutter documentation :
You might wonder why StatefulWidget and State are separate objects. In Flutter, these two types of objects have different life cycles. Widgets are temporary objects, used to construct a presentation of the application in its current state. State objects on the other hand are persistent between calls to build(), allowing them to remember information.
This would mean that we'd have all the lifecycle methods override you can have on State move to the Stateful subclass. Such as initState, but also dispose or didUpdateWidget. Which doesn't make sense considering widgets are just throwable objets that never update.