flutter
/

GetX Lifecycle: Complete Guide to Controllers, Routes & Widgets

Last Sync: Today

On this page

10
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

GetX Lifecycle: Complete Guide to Controllers, Routes & Widgets

Introduction to GetX Lifecycle

GetX provides a powerful lifecycle system that automatically manages the creation, state, and disposal of controllers and dependencies. Understanding this lifecycle is essential for building efficient, memory-safe Flutter applications. This guide covers the full lifecycle of GetX controllers, widgets, routes, and navigation.

  1. Controller Lifecycle

Every controller that extends GetxController follows a predictable lifecycle. You can hook into it using the following methods:

  • onInit() – Called immediately after the controller is created. Used for initializing data, setting up workers, or starting API calls.
  • onReady() – Called after the widget tree has been rendered. Ideal for UI-dependent actions (e.g., showing dialogs, starting animations).
  • onClose() – Called when the controller is about to be destroyed. Clean up streams, timers, or any resources.
DARTRead-only
1
class LifecycleController extends GetxController {
  @override
  void onInit() {
    super.onInit();
    print('1. onInit');
  }

  @override
  void onReady() {
    super.onReady();
    print('2. onReady (UI is ready)');
  }

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

  1. Widget Lifecycle & Controllers

GetX widgets like GetView, GetWidget, Obx, and GetBuilder integrate seamlessly with controller lifecycle.

DARTRead-only
1
class MyView extends GetView<MyController> {
  @override
  Widget build(BuildContext context) {
    // controller is automatically found
    // The controller is registered when the view is first built
    return Text('${controller.count}');
  }
}

When GetView is used, the controller is created when the view is first built (if not already registered). The controller is then disposed when the view is removed from the widget tree (unless permanent: true).

These widgets listen to controllers but do not affect their lifecycle. The controller's lifecycle is independent; the widgets simply rebuild when notified.

  1. Route & Navigation Lifecycle

GetX's navigation system (using Get.to, Get.off, etc.) automatically manages controllers associated with routes, especially when bindings are used.

DARTRead-only
1
class HomeBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut(() => HomeController());
  }
}

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

When the route is pushed, the binding's dependencies() is called, registering the controller. When the route is popped, the controller is automatically disposed (unless marked permanent: true or fenix: true).

DARTRead-only
1
Get.to(NextPage());
// Inside NextPage:
final controller = Get.put(MyController());

The controller is created when Get.put is called and disposed when the route is removed (because the controller is tied to the route's lifecycle).

  1. Full Lifecycle Sequence

Here's the typical order when navigating to a new route with a binding and controller:

    1. Navigation triggered (Get.toNamed('/details')).
    1. Binding's dependencies() runs, registering controllers (lazy).
    1. Route page widget is created (but not yet built).
    1. Controller's constructor runs (if lazy is used, it runs only when first accessed).
    1. Controller's onInit() runs (if the controller is accessed in the view).
    1. Widget is built – the view uses GetView<MyController> or Get.find() to access the controller.
    1. After the first frame, controller's onReady() runs.
    1. While route is active, controllers can be accessed and modified.
    1. When route is popped, GetX checks if the controller is permanent; if not, it calls onClose() and disposes the controller.

  1. Managing Controller Lifecycle

You can control how long a controller lives using injection options.

DARTRead-only
1
// Permanent: never disposed automatically
Get.put(MyController(), permanent: true);

// Lazy, but can be recreated after disposal
Get.lazyPut(() => MyController(), fenix: true);

// Manually delete
Get.delete<MyController>();

  1. Best Practices

  • Use onInit for initialization – Never put async code in the constructor; use onInit instead.
  • Use onReady for UI-related tasks – Safe to show dialogs or start animations after the UI is ready.
  • Clean up in onClose – Dispose streams, cancel timers, and remove listeners to avoid memory leaks.
  • Use bindings – Automatically tie controllers to routes, ensuring proper disposal.
  • Avoid permanent: true unless necessary – Let GetX manage lifecycle for most controllers.
  • Use Get.isRegistered<T>() – Check before accessing a controller that might not exist yet.

  1. Common Mistakes

  • ❌ Calling Get.put inside build – Creates a new controller on each rebuild. ✅ Place in initState, bindings, or use Get.lazyPut.
  • ❌ Not calling super.onInit() – Breaks internal setup. ✅ Always call super.onInit() when overriding.
  • ❌ Forgetting to dispose resources in onClose – Causes memory leaks. ✅ Clean up streams, timers, and controllers manually if needed.
  • ❌ Using Get.find before registration – Throws an error. ✅ Ensure registration with Get.put or bindings first.

FAQ

  • Q: When is onReady called?
    A: After the widget tree is rendered for the first time, which is typically after the first frame. It's called once per controller.
  • Q: Are controllers disposed when using Get.off?
    A: Yes, if the controller was tied to that route (e.g., via binding), it will be disposed. If it was marked permanent, it stays.
  • Q: What's the difference between onClose and onDelete?
    A: onClose is the standard lifecycle method; onDelete is not part of GetX. Use onClose.
  • Q: How do I check if a controller is still alive?
    A: Use Get.isRegistered<MyController>().
  • Q: Can I call Get.put in onInit of another controller?
    A: Yes, but be mindful of circular dependencies. Use Get.lazyPut or pass through constructors when possible.
  • Q: Does GetX dispose controllers when the app goes to background?
    A: No, controllers persist. They are only disposed when the associated route is removed or you manually call Get.delete.

Conclusion

Mastering the GetX lifecycle allows you to build efficient, leak-free Flutter apps. By leveraging onInit, onReady, and onClose, and using bindings to manage dependencies, you can ensure your controllers are created and disposed exactly when needed.

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

class DemoController extends GetxController {
  DemoController() {
    print('Constructor');
  }

  @override
  void onInit() {
    super.onInit();
    print('onInit');
  }

  @override
  void onReady() {
    super.onReady();
    print('onReady');
    Get.snackbar('Ready', 'Controller is ready!');
  }

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

  void disposeController() {
    Get.delete<DemoController>();
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Lifecycle Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Check console for lifecycle logs'),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Get.to(SecondPage());
              },
              child: Text('Navigate to new page'),
            ),
            SizedBox(height: 10),
            ElevatedButton(
              onPressed: controller.disposeController,
              child: Text('Manually delete controller'),
            ),
          ],
        ),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  final controller = Get.put(DemoController()); // new instance

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Second Page')),
      body: Center(
        child: Text('This page has its own controller.\nCheck console when you pop this page.'),
      ),
    );
  }
}

Test Your Knowledge

Q1
of 3

Which method is called after the UI is fully rendered?

A
onInit
B
onReady
C
onClose
D
initState
Q2
of 3

What should you do in onClose?

A
Initialize data
B
Start animations
C
Dispose streams and timers
D
Register new controllers
Q3
of 3

When is a controller disposed automatically?

A
When the app closes
B
When the route it's tied to is popped
C
Never
D
After 10 seconds of inactivity

Previous

getx controller

Next

getx bindings

Related Content

Need help?

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