Introduction
Memory management is crucial for building smooth, long‑running Flutter apps. GetX helps by automatically disposing controllers when they are no longer needed, but you must still clean up custom resources like streams, timers, and listeners. This guide explains how GetX handles memory, how to properly implement onClose, and best practices to prevent memory leaks.
- How GetX Manages Controllers
GetX tracks controller usage through a reference count. When you call Get.put or Get.lazyPut, the controller is registered. Each time Get.find is called from a mounted widget, the reference count increases. When the widget is unmounted, the count decreases. When the count reaches zero, GetX calls onClose() and disposes the controller. This automatic disposal prevents most memory leaks, but you still need to clean up any resources you created (e.g., streams, timers, animations) inside onClose.
- The onClose Method
Override onClose to release any resources that are not automatically cleaned up by GetX. Common resources to dispose:
- Stream subscriptions (e.g., from Firebase,
StreamController, orStreamSubscription). - Timers (
Timer,Ticker). - Controllers (
TextEditingController,AnimationController). - Listeners (e.g.,
ScrollController,PageController). - Dio clients or other network connections.
- Avoiding Memory Leaks in Common Scenarios
Always cancel subscriptions in onClose. For broadcast streams, you must cancel each subscription individually.
- Permanent Controllers & Memory
Controllers marked permanent: true or extending GetxService are never disposed automatically. Use them only for truly global services (e.g., authentication, API client). For such controllers, you should still implement onClose to clean up resources when you eventually delete them (if ever). If you never delete them, resources like streams will stay alive – ensure they are either not used or are properly managed (e.g., using a single subscription that lives as long as the app).
- Detecting Memory Leaks
To check if controllers are being disposed correctly, add print statements in onClose. Use Flutter DevTools to inspect the widget tree and memory usage. Look for controllers that are not being disposed when you pop routes.
If you suspect a leak, use the DevTools memory tab and force garbage collection. If a controller stays in memory after its route is popped, you may have a lingering reference.
- Common Pitfalls
- Creating controllers inside build method – New controller on every rebuild, never disposed. ✅ Use bindings or inject once.
- Not canceling stream subscriptions – Subscriptions keep the controller alive.
✅ Cancel in
onClose. - Using
Get.putin a widget that rebuilds often – Creates duplicate instances. ✅ UseGet.lazyPutor bindings. - Keeping permanent controllers that hold large data – Unnecessary memory usage. ✅ Only make essential controllers permanent.
- Forgetting to call
super.onClose()– GetX internal cleanup may be skipped. ✅ Always callsuper.onClose()at the end of your override.
- Testing Memory Management
In unit tests, you can verify that onClose is called by creating a controller, disposing it via Get.delete, and checking that the flag was set. For widget tests, use tester.pumpAndSettle and verify that onClose logs appear.
Best Practices
- Always dispose custom resources in
onClose– Streams, timers, controllers. - Avoid storing large data in controllers unnecessarily – Offload to services with caching.
- Use bindings – Let GetX manage lifecycle automatically.
- Prefer
Get.lazyPut– Defer creation until needed. - Limit permanent controllers – Only for app‑wide services.
- Test disposal – Use logs and DevTools to ensure cleanup.
Common Mistakes
- ❌ Not canceling subscriptions – Memory leak.
✅ Cancel in
onClose. - ❌ Storing
BuildContextin a controller – Can cause leaks if context outlives the widget. ✅ Avoid storing contexts; useGet.contextwhen needed. - ❌ Using
Get.putinside a widget'sbuild– Creates new instances on each rebuild. ✅ Register ininitStateor bindings. - ❌ Forgetting to call
super.onClose()– Breaks internal cleanup. ✅ Always callsuper.onClose().
FAQ
- Q: Does GetX automatically dispose controllers when using bindings?
A: Yes, if the controller is registered inside a binding and the route is popped, GetX will dispose it (unless marked permanent). - Q: How do I dispose a controller manually?
A: CallGet.delete<MyController>(). This triggersonCloseand removes it from the registry. - Q: What happens if I don't call
super.onClose()?
A: GetX's internal cleanup may not run, causing memory leaks. Always call it. - Q: Can I use
onCloseto save data before the controller is destroyed?
A: Yes, it's a great place to save user input or state toGetStorage. - Q: Why is my controller not being disposed?
A: It might be permanent, or there may be a lingering reference (e.g., a stream subscription that wasn't cancelled). CheckisRegisteredand logs.
Conclusion
GetX automates most controller lifecycle management, but you still need to clean up custom resources. By properly implementing onClose and following the best practices in this guide, you can ensure your Flutter app remains memory‑efficient and leak‑free.