I am using the following package https://pub.dev/packages/get. Do I need to close my .obs in the onClose of a GetxController? I can't find anything about this in the docs. And looking at my memory it appears that the are being destroyed automatically.
In my understanding of GetX + Flutter so far...
No, you shouldn't have to remove .obs in the close()
method of GetxControllers. Disposal of observables from a Controller are done automatically when the Controller is removed from memory.
GetX disposes/removes GetxControllers (and their observables) when the widget in which they are contained are popped off the widget stack / removed from the widget tree (by default, but can be overridden).
You can see this in the override of dispose()
methods of various Get widgets.
Here's a snippet of dispose()
that's run when GetX
widgets are popped/removed:
@override
void dispose() {
if (widget.dispose != null) widget.dispose(this);
if (isCreator || widget.assignId) {
if (widget.autoRemove && GetInstance().isRegistered<T>(tag: widget.tag)) {
GetInstance().delete<T>(tag: widget.tag);
}
}
subs.cancel();
_observer.close();
controller = null;
isCreator = null;
super.dispose();
}
When you use Bindings or Get.to()
you're using GetPageRoute
's which do cleanup by Route names:
@override
void dispose() {
if (Get.smartManagement != SmartManagement.onlyBuilder) {
WidgetsBinding.instance.addPostFrameCallback((_) => GetInstance()
.removeDependencyByRoute("${settings?.name ?? routeName}"));
}
super.dispose();
}
Below is a test App you can copy/paste into Android Studio / VSCode and run to watch the debug or run window output for GETX lifecycle events.
GetX will log the creation & disposal of Controllers in and out of memory.
The app has a HomePage and 3 ChildPages using Get Controllers in 3 ways, all which remove itself from memory:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
// MyCounterBinding().dependencies(); // usually where Bindings happen
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'GetX Dispose Ex',
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GetX Dispose Test'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RaisedButton(
child: Text('GetX/Builder Child'),
onPressed: () => Get.to(ChildPage()),
),
RaisedButton(
child: Text('Get.put Child'),
onPressed: () => Get.to(ChildPutPage()),
),
RaisedButton(
child: Text('Binding Child'),
onPressed: () => Get.to(ChildBindPage()),
),
],
),
),
);
}
}
/// GETX / GETBUILDER
/// Creates Controller within the Get widgets
class ChildPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GetX Dispose Test Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('This is the Child Page'),
GetX<ChildX>(
init: ChildX(),
builder: (cx) => Text('Counter: ${cx.counter}', style: TextStyle(fontSize: 20),),
),
GetBuilder<ChildX>(
init: ChildX(),
builder: (cx) => RaisedButton(
child: Text('Increment'),
onPressed: cx.inc,
),
),
],
),
),
);
}
}
/// GET.PUT
/// Creates Controller instance upon Build, usable anywhere within the widget build context
class ChildPutPage extends StatelessWidget {
//final ChildX cx = Get.put(ChildX()); // wrong place to put
// see https://github.com/jonataslaw/getx/issues/818#issuecomment-733652172
@override
Widget build(BuildContext context) {
final ChildX cx = Get.put(ChildX());
return Scaffold(
appBar: AppBar(
title: Text('GetX Dispose Test Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('This is the Child Page'),
Obx(
() => Text('Counter: ${cx.counter}', style: TextStyle(fontSize: 20),),
),
RaisedButton(
child: Text('Increment'),
onPressed: cx.inc,
)
],
),
),
);
}
}
class MyCounterBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => ChildX(), fenix: true);
}
}
/// GET BINDINGS
/// Normally the MyCounterBinding().dependencies() call is done in main(),
/// making it available throughout the entire app.
/// A lazyPut Controller /w [fenix:true] will be created/removed/recreated as needed or
/// as specified by SmartManagement settings.
/// But to keep the Bindings from polluting the other examples, it's done within this
/// widget's build context (you wouldn't normally do this.)
class ChildBindPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
MyCounterBinding().dependencies(); // just for illustration/example
return Scaffold(
appBar: AppBar(
title: Text('GetX Dispose Test Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('This is the Child Page'),
Obx(
() => Text('Counter: ${ChildX.i.counter}', style: TextStyle(fontSize: 20),),
),
RaisedButton(
child: Text('Increment'),
onPressed: ChildX.i.inc,
)
],
),
),
);
}
}
class ChildX extends GetxController {
static ChildX get i => Get.find();
RxInt counter = 0.obs;
void inc() => counter.value++;
}
When using Get.put()
in a child widget be sure you're using Get.to()
to navigate to that child rather than Flutter's built-in Navigator.push
.
GetX wraps the destination widget in a GetPageRoute
when using Get.to
. This Route class will dispose of Controllers in this route when navigating away / popping the widget off the stack. If you use Navigator.push
, GetX isn't involved and you won't get this automatic cleanup.
Navigator.push
onPressed: () => Navigator.push(context, MaterialPageRoute(
builder: (context) => ChildPutPage())),
Get.to
onPressed: () => Get.to(ChildPutPage()),
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With