11

I have a minimlaist sample app running on Android with GetX as State Management lib only. There are two screens LandingPage and MainScreen. On going back from MainScreen to LandingPage screen, the controller is not autodisposing as expected. I am using Flutter's Navigation only without wrapping with GetMaterialApp.

My expectation is that the value exposed by the controller should be reset to its initial value when the Controller is instantiated. However, the Widget continues to show the last value from the controller.

I am using the latest version of Flutter and GetX as avail of now : 2.2.3 and 4.3.8 respectively

Your help is appreciated.

Code:

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
   
    primarySwatch: Colors.purple,
  ),
  home: LandingScreen(),
  );
 }
} 

class LandingScreen extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
  return Container(
   color: Colors.blue[800],
   child: Center(
     child: ElevatedButton(
       onPressed: () => {
         Get.to(MainScreen())
       },
       child: const Text('Navigate to Second Screen'),
     ),
    ),
  );
 }
}

class MainScreen extends StatelessWidget {
 final MyController controller = Get.put(MyController());

 @override
 Widget build(BuildContext context) {
  return Scaffold(
  body: SafeArea(
    child: Container(
      color: Colors.blueAccent,
      child: Center(
        child: Column(
          children: [
            Obx(() => Text('Clicked ${controller.count}')),
            FloatingActionButton(
              onPressed: controller.increment,
              child: Text('+'),
            ),
            ElevatedButton(
              onPressed: ()=>{Navigator.of(context).pop()},
              child: Text('Go Back'),
            )
          ],
          ),
         ),
        ),
       ),
      );
     }
    }

  class MyController extends GetxController {

   var count = 0.obs;
   void increment() => count++;

  }
Akash Gorai
  • 580
  • 1
  • 8
  • 17

7 Answers7

13

You need to use GetX navigation first. However, at the time of writing this answer, there is a bug causing the controllers not to get disposed off automatically. So, it is recommended for now to use bindings or to manually dispose of them from a StatefulWidget till the bug is fixed.

@override
  void dispose() {
    Get.delete<Controller>();
    super.dispose();
  }

Update 22 Nov. 2021 You can still use the above solution, or for a more elegant approach you can use Bindings along with extending GetView<YourController> instead of Stateful/ Stateless Widgets. GetXController will provide most of the functions that you would need from a StatefulWidget. You also don't have to use GetX Navigation.

This approach also works when using nested navigation.

To use Bindings, there are multiple methods explained here.

Mena
  • 3,019
  • 1
  • 25
  • 54
13

Automatic Disposal

To have our GetxController disposed when the widget is removed, use Get.put inside the build() method, not as a field. This is the correct usage as per GetX's maintainers.

The Controller can then be disposed when MainScreen is popped.

Incorrect Usage

Controller won't get disposed:

class MainScreen extends StatelessWidget {
 final MyController controller = Get.put(MyController());

 @override
 Widget build(BuildContext context) {
  return Scaffold(

Instantiation & registration (Get.put(...)) should not be done as a field.

Otherwise, the registration of controller is attached to the widget above (i.e. LandingScreen in question's example), not MainScreen. And MyController will only get disposed when LandingScreen is disposed. (In the question's code LandingScreen is the "home" Widget and its disposal only happens upon app exit.)

Why?

If instantiating a controller as a field, GetX will record that controller's creation with the context currently available, the parent widget's context, not the instantiating widget's context. The instantiating widget context isn't available until its build() method. A widget class' instantiation and the running of its build() method, are two distinct things. The build() method is not a factory constructor of the widget itself, but a lifecycle method used by the Flutter framework after the widget's instantiation, to inflate the widget and insert/mount it as an Element in the element tree of rendered objects. (That's my current understanding.)

In the question's example, MainScreen widget is instantiated in the context of LandingScreen. MainScreen and its fields are in the context of LandingScreen. MainScreen's build() method is run after its instantiation. MainScreen's context is available for use when its build() method is run, but not during the Widget's instantiation.

For MyController to be deleted/disposed when MainScreen is disposed, we must instantiate MyController inside of MainScreen context, which is available during the call of its build(BuildContext context) method. That's the context with which we should associate our GetxController if we want it automatically disposed of by GetX when the current/instantiating widget is removed from the widget tree.

Correct Usage

class MainScreen extends StatelessWidget {

 @override
 Widget build(BuildContext context) {
  // ↓ instantiate/register here inside build ↓
  final MyController controller = Get.put(MyController());
  return Scaffold(

Now when MainScreen is popped off the route stack, MyController will be cleaned up / disposed by GetX (which I assume observes the route history to know when to do its cleanup).

Baker
  • 24,730
  • 11
  • 100
  • 106
  • What if I am using a StatefulWidget for any reason? Putting it in the build might not be a good idea? – Mena Nov 22 '21 at 07:43
  • @Mena Hmm..., at first glance, I can't think of a reason why it would be bad to put it into the `build()` method of a `StatefulWidget`. But, it may make more sense to register a controller in a `StatefulWidget's State` object. When the `State` object is disposed, so will the controller. – Baker Nov 23 '21 at 21:04
  • I'm not observing this behaviour, even though I'm calling `Get.put` inside the `build` method. Note that instead of `Navigator.pop(context)` I'm calling `Navigator.of(context).pushNamedAndRemoveUntil`. – ASAD HAMEED Nov 16 '22 at 05:57
  • Thank you so much. I was initialising it outside build method which was preventing it to dispose after screen pop. – Usama Javed Jan 30 '23 at 23:22
  • This is incorrect! Putting controllers inside build method is not needed for disposal of the controller. – Akash Gorai May 18 '23 at 16:16
  • It does not make sense at all. Whenever a build method is called, the widget loose its state. – Cícero Moura May 25 '23 at 21:20
  • @CíceroMoura The original question is asking for exactly that: *losing* state **not** *keeping* state. The problem in original question is: a GetxController, when instantiated (improperly) as a field, its state is *not* lost when the current (stateless) widget is disposed. Reason: created as a field, GetxController is attached to the *parent* context, *not* the context of the current `build()` method. – Baker May 28 '23 at 11:03
5

You can late initialize your controller like bellow:

late final YourController controller;

and inside your initState function:

@override
void initState() {
    super.initState();

    Get.delete<YourController>();
    controller = Get.put(YourController());
}

this will fix your problem

Mohammad Khair
  • 436
  • 6
  • 15
0

If you add the routes on the getMaterialApp you don't need to initiate the controller inside the build:

Add the 'initialRoute' and 'getPages' properties and remove the home one:

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
    primarySwatch: Colors.purple,
  ),
  initialRoute: '/landing',
  getPages: [
    GetPage(name: '/landing', page: () => LandingScreen()),
    GetPage(name: '/main', page: () => MainScreen()),
  ],
  );
 }
}

On LandingPage() change the Get.to() to Get.toNamed():

class LandingScreen extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
  return Container(
   color: Colors.blue[800],
   child: Center(
     child: ElevatedButton(
       onPressed: () => Get.toNamed('/main'),
       child: const Text('Navigate to Second Screen'),
     ),
    ),
  );
 }
}

And on MainScreen change the navigator pop to Get.back():

class MainScreen extends StatelessWidget {
 final MyController controller = Get.put(MyController());

 @override
 Widget build(BuildContext context) {
  return Scaffold(
  body: SafeArea(
    child: Container(
      color: Colors.blueAccent,
      child: Center(
        child: Column(
          children: [
            Obx(() => Text('Clicked ${controller.count}')),
            FloatingActionButton(
              onPressed: controller.increment,
              child: Text('+'),
            ),
            ElevatedButton(
              onPressed: ()=>Get.back(),
              child: Text('Go Back'),
            )
          ],
          ),
         ),
        ),
       ),
      );
     }
    }
Gianluca Bettega
  • 326
  • 4
  • 12
0

this kinda worked for me when i defined the following method in the controller.dart file:

late Readcontroller controller;

void dispose(){ Get.delete(); controller = Get.put(ReadController()); }

and then in the main file for me which was reading.dart:

widget build(Build context){ final ReadController controller = Get.put(ReadController()); }

the above piece of code just works fine for me

give it a try .......

}

TuGordoBello
  • 4,350
  • 9
  • 52
  • 78
Ayan Paul
  • 1
  • 1
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – mohammad mobasher Feb 12 '22 at 06:40
0

You need to use getPages with navigation getx way

 Get.to(() => const NotificationsScreen())

routes:

GetMaterialApp(
          getPages: NavigatorService.onGenerateRoute(),
Mahmoud Salah Eldin
  • 1,739
  • 16
  • 21
0

define binding class in diffrent files.not use common file for define binding