'GetX Controller not disposing off automatically

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++;

  }


Solution 1:[1]

Move Get.put from being a field of MainScreen to inside its build() method.

The Controller can then be disposed when MainScreen is popped.

Current Situation

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 LandingScreen, not MainScreen. And MyController will only get disposed when LandingScreen is disposed. Since that's the home Widget in the code above, disposal only happens upon app exit.

Fix: Move Get.put to the build() method.

Inside Build()

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 route stack, MyController will be cleaned up / disposed.

This is a quirk of Flutter framework's navigation + Get.

See this issue for an explanation / example by Eduardo, one Get's maintainers.

Solution 2:[2]

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.

Solution 3:[3]

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

Solution 4:[4]

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'),
            )
          ],
          ),
         ),
        ),
       ),
      );
     }
    }

Solution 5:[5]

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

}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Baker
Solution 2
Solution 3 Mohammad Khair
Solution 4 MacacoAzul
Solution 5 Ayan Paul