What is a GetX Controller?
A GetX controller is a class that extends GetxController and holds your business logic, state, and side effects. It is the heart of GetX's architecture, providing lifecycle management, reactive state, and easy dependency injection. Controllers are automatically disposed when not needed, preventing memory leaks.
Creating a Controller
Controller Lifecycle
GetX controllers have three main lifecycle methods that you can override to execute code at specific moments.
onInit()– Called immediately after the controller is created. Perfect for initializing data, setting up workers, or calling APIs.onReady()– Called after the widget tree is fully rendered. Ideal for tasks that require the UI to be ready (e.g., showing dialogs, starting animations).onClose()– Called when the controller is about to be destroyed. Clean up resources like streams, timers, or dispose subscriptions.
| Method | Timing | Use Cases |
|---|---|---|
| `onInit()` | Immediately after creation | Initialize data, setup workers, call APIs |
| `onReady()` | After UI is fully rendered | Show dialogs, start animations, focus fields |
| `onClose()` | Before disposal | Close streams, cancel timers, dispose subscriptions |
Dependency Injection with Controllers
Controllers are typically managed by GetX's dependency injection system. You can inject them in several ways.
Accessing Controllers
Once registered, you can access a controller anywhere in your app using Get.find() or by using GetView.
State Management in Controllers
Controllers can hold both reactive and simple state, and decide how to notify the UI.
Communication Between Controllers
Controllers can communicate using GetX's DI system. One controller can find another and call its methods or listen to its state.
Using Workers in Controllers
Workers are typically set up in onInit() to react to state changes.
Permanent vs Non-Permanent Controllers
By default, GetX disposes controllers when the route that uses them is removed. You can make a controller permanent to keep it alive for the entire app lifecycle.
Comparison: GetxController vs GetxService
| Feature | GetxController | GetxService |
|---|---|---|
| Disposal | Auto-disposed when route is removed (unless permanent) | Never disposed automatically |
| Best for | Screen/feature-specific logic | App-wide services (API client, storage, auth) |
| Usage | One per screen or feature | Singleton across app |
| Lifecycle methods | onInit, onReady, onClose | Same, but never called onClose unless manually deleted |
Use GetxService for services that should live the entire app lifetime. Use GetxController for disposable logic tied to a screen or module.
Best Practices
- Single Responsibility – Each controller should handle one feature or screen. Avoid giant controllers with unrelated logic.
- Use
onInitfor initialization – Never put async code directly in the constructor. - Use
onClosefor cleanup – Dispose streams, timers, and any listeners. - Prefer bindings for injection – Keeps the UI clean and avoids scattered
Get.putcalls. - Use
GetViewfor cleaner UI code – Eliminates repetitiveGet.findcalls. - Avoid accessing controllers in UI before they're registered – Ensure
Get.putor bindings are called first. - Use
Get.isRegistered<T>()– Check if a controller exists before accessing.
Common Mistakes
- ❌ Creating controllers inside build method – Causes multiple instances on rebuilds.
✅ Use
Get.putor bindings outside build. - ❌ Forgetting to call
super.onInit()– Breaks internal GetX setup. ✅ Always callsuper.onInit()in overridden methods. - ❌ Using
onReadyfor heavy work that blocks UI – It's called after UI is ready but can still block if not async. ✅ Use async methods or delay heavy operations. - ❌ Not disposing resources – Leads to memory leaks.
✅ Clear streams, timers, and dispose listeners in
onClose. - ❌ Hardcoding
Get.findin many places – Makes refactoring harder. ✅ UseGetViewor pass dependencies via constructor.
Conclusion
GetX controllers provide a robust foundation for managing state, logic, and lifecycle in Flutter apps. By understanding their lifecycle, injection options, and best practices, you can build maintainable, scalable applications with minimal boilerplate.