flutter
/

GetX Smart Management: Automatic Controller Lifecycle

Last Sync: Today

On this page

11
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

GetX Smart Management: Automatic Controller Lifecycle

What is GetX Smart Management?

One of GetX's most powerful features is its smart management system, which automatically handles the lifecycle of your controllers and dependencies. It tracks when a controller is being used and disposes of it when it's no longer needed, preventing memory leaks. This guide explains how this system works and how to control it with permanent, fenix, and proper registration patterns.

How Smart Management Works

GetX monitors whether a controller is actively being used. A controller is considered "in use" when:

  • It is injected via Get.put and the widget that called Get.put is still in the widget tree.
  • It is accessed via Get.find inside a widget that is currently mounted.
  • It is part of a route that is currently active.

When a controller is no longer referenced by any active widget or route, GetX automatically calls its onClose() method and disposes it. This is the default behavior and helps keep your app memory‑efficient.

Controller Lifecycle in Routes

When you use bindings with GetPage, GetX ties the controller lifecycle to the route. When the route is popped, the controller is automatically disposed (unless marked permanent).

DARTRead-only
1
class HomeBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut(() => HomeController()); // disposed when route is popped
  }
}

GetPage(
  name: '/home',
  page: () => HomePage(),
  binding: HomeBinding(),
);

If you register a controller inside a widget using Get.put (not recommended outside of simple cases), the controller will be disposed when that widget is removed from the tree.

Controlling Lifecycle with permanent

Sometimes you want a controller to live for the entire app duration. Use permanent: true to prevent automatic disposal.

DARTRead-only
1
// This controller will never be disposed
Get.put(AuthController(), permanent: true);

// Also works with lazyPut
Get.lazyPut(() => AuthService(), permanent: true);

fenix – Resurrection After Disposal

If you want a controller to be recreated after it has been disposed (e.g., when the user revisits a route), use fenix: true with lazyPut. When the route is popped, the controller is disposed, but the next Get.find will create a fresh instance.

DARTRead-only
1
Get.lazyPut(() => ProfileController(), fenix: true);
// After the controller is disposed, calling Get.find will create a new one.

Smart Management in Practice

  • Default (no flags) – The controller is disposed when the route is popped or when the widget that created it is removed.
  • permanent: true – The controller lives forever. Use for app‑wide services.
  • fenix: true – The controller is disposed when not used, but can be recreated automatically later.
  • Get.create – Always returns a new instance; does not cache.

Detecting Disposal

Override onClose() to perform cleanup and confirm that disposal is happening as expected.

DARTRead-only
1
class MyController extends GetxController {
  @override
  void onClose() {
    print('Controller disposed');
    super.onClose();
  }
}

Common Pitfalls

  • Registering controllers inside build – Creates a new controller on every rebuild, causing leaks and duplicate instances. ✅ Register in initState, bindings, or onInit of other controllers.
  • Assuming a controller is still alive – Always check Get.isRegistered<T>() before using Get.find if you're not certain.
  • Forgetting to set permanent for global services – They might be disposed unexpectedly when a route is popped. ✅ Use permanent: true for app‑wide services.
  • Not disposing resources in onClose – Even though the controller is disposed, streams and timers may still be active. ✅ Always clean up in onClose.

Best Practices

  • Use bindings – Let GetX manage the controller lifecycle automatically based on routes.
  • Avoid Get.put inside widgets – Prefer bindings or at least use Get.lazyPut if you must create a controller in a widget.
  • Use permanent only for truly global dependencies – Like authentication, theme, database services.
  • Use fenix for controllers that should be recreated when needed – Especially for routes that may be opened multiple times.
  • Test disposal – Use onClose logs during development to verify controllers are being disposed correctly.

FAQ

  • Q: How does GetX know when a controller is no longer used?
    A: GetX uses an internal reference counter. Each time Get.find is called from a widget that is mounted, it increments a count. When the widget is unmounted, it decrements. When the count reaches zero, the controller is disposed.
  • Q: What happens if I use Get.put in multiple places?
    A: The first call creates the instance; subsequent calls return the same instance (unless tags are used). The reference count is increased accordingly.
  • Q: Can I manually delete a controller?
    A: Yes, call Get.delete<MyController>(). It will call onClose and remove it from the registry.
  • Q: Why is my controller not being disposed?
    A: It might be marked permanent, or there may still be a reference to it in a widget that is still mounted. Check if you are using Get.put inside a widget that never gets removed.
  • Q: How to dispose a controller when using Get.lazyPut without fenix?
    A: It will be disposed when the route is popped or when the last reference is removed. To force disposal, use Get.delete.

Conclusion

GetX smart management eliminates the need to manually dispose controllers, reducing memory leaks and boilerplate. By understanding how it works and using permanent and fenix appropriately, you can build efficient, scalable Flutter apps.

Try it yourself

import 'package:flutter/material.dart';
import 'package:get/get.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      home: HomePage(),
    );
  }
}

class DemoController extends GetxController {
  @override
  void onInit() {
    super.onInit();
    print('Controller created');
  }

  @override
  void onClose() {
    print('Controller disposed');
    super.onClose();
  }
}

class HomePage extends StatelessWidget {
  final controller = Get.put(DemoController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Smart Management')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Check console for lifecycle logs'),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => Get.to(SecondPage()),
              child: Text('Go to Second Page'),
            ),
          ],
        ),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  // This controller will be disposed when this page is popped
  final controller = Get.put(DemoController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Second Page')),
      body: Center(
        child: ElevatedButton(
          onPressed: () => Get.back(),
          child: Text('Back'),
        ),
      ),
    );
  }
}

Test Your Knowledge

Q1
of 3

What is the default behavior of a controller injected with Get.lazyPut in a binding?

A
It stays forever
B
It is disposed when the route is popped
C
It is never created
D
It must be manually disposed
Q2
of 3

Which flag prevents a controller from being automatically disposed?

A
permanent
B
fenix
C
keepAlive
D
persist
Q3
of 3

What does `fenix: true` do?

A
Makes the controller permanent
B
Allows recreation after disposal
C
Creates the controller immediately
D
Prevents the controller from being created

Previous

getx state persistence

Next

getx permanent controller

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.