Describe the bug
The provided code is a simplified abstraction of a bigger snippet. I have a controller that performs actions on init and updates the view, if I make the GetBuilders global parameter for all views that use the controller false and switch views while one is updating I get:
E/flutter (23637): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: setState() called after dispose(): _GetBuilderState<TestViewModel>#0b190(lifecycle state: defunct, not mounted)
E/flutter (23637): This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback.
E/flutter (23637): The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
E/flutter (23637): This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().
E/flutter (23637): #0 State.setState.<anonymous closure> (package:flutter/src/widgets/framework.dart:1197:9)
E/flutter (23637): #1 State.setState (package:flutter/src/widgets/framework.dart:1232:6)
E/flutter (23637): #2 GetxController.update.<anonymous closure> (package:get/src/state_manager/simple/get_state.dart:15:25)
E/flutter (23637): #3 _SetBase.forEach (dart:collection/set.dart:438:30)
E/flutter (23637): #4 GetxController.update (package:get/src/state_manager/simple/get_state.dart:14:21)
E/flutter (23637): #5 TestViewModel.loading= (package:get_only_example/core/viewmodels/test_view_model.dart:11:5)
E/flutter (23637): #6 TestViewModel.onInit (package:get_only_example/core/viewmodels/test_view_model.dart:20:5)
E/flutter (23637): <asynchronous suspension>
E/flutter (23637): #7 DisposableInterface.onStart (package:get/src/state_manager/rx/rx_interface.dart:30:5)
E/flutter (23637): #8 _GetBuilderState.initState (package:get/src/state_manager/simple/get_state.dart:97:19)
E/flutter (23637): #9 StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:4640:58)
E/flutter (23637): #10 ComponentElement.mount (package:flutter/src/widgets/framework.dart:4476:5)
E/flutter (23637): #11 Element.inflateWidget (package:flutter/src/widgets/framework.dart:3446:14)
E/flutter (23637): #12 Element.updateChild (package:flutter/src/widgets/framework.dart:3214:18)
E/flutter (23637): #13 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4527:16)
E/flutter (23637): #14 Element.rebuild (package:flutter/src/widgets/framework.dart:4218:5)
E/flutter (23637): #15 ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4481:5)
E/flutter (23637): #16 ComponentElement.mount (package:flutter/src/widgets/framework.dart:4476:5)
E/flutter (23637): #17 Element.inflateWidget (package:flutter/src/widgets/framework.dart:3446:14)
E/flutter (23637): #18 Element.updateChild (package:flutter/src/widgets/framework.dart:3214:18)
E/flutter (23637): #19 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4527:16)
E/flutter (23637): #20 Element.rebuild (package:flutter/src/widgets/framework.dart:4218:5)
E/flutter (23637): #21 ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4481:5)
E/flutter (23637): #22 ComponentElement.mount (package:flutter/src/widgets/framework.dart:4476:5)
E/flutter (23637): #23 Element.inflateWidget (package:flutter/src/widgets/framework.dart:3446:14)
E/flutter (23637): #24 Element.updateChild (package:flutter/src/widgets/framework.dart:3214:18)
E/flutter (23637): #25 SingleChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:5830:14)
E/flutter (23637): #26 Element.inflateWidget (package:flutter/src/widgets/framework.dart:3446:14)
E/flutter (23637): #27 Element.updateChild (package:flutter/src/widgets/framework.dart:3214:18)
E/flutter (23637): #28 SingleChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:5830:14)
E/flutter (23637): #29 Element.inflateWidget (package:flutter/src/widgets/framework.dart:3446:14)
E/flutter (23637): #30 Element.updateChild (package:flutter/src/widgets/framework.dart:3214:18)
E/flutter (23637): #31 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4527:16)
E/flutter (23637): #32 Element.rebuild (package:flutter/src/widgets/framework.dart:4218:5)
E/flutter (23637): #33 ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4481:5)
E/flutter (23637): #34 ComponentElement.mount (package:flutter/src/widgets/framework.dart:4476:5)
E/flutter (23637): #35 Element.inflateWidget (package:fl
To Reproduce
Steps to reproduce the behavior:
Run the provided code snippet.
Expected behavior
Execute without error.
Flutter Version:
1.17.3
Get Version:
3.4.1
Minimal reproduce code
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../core/viewmodels/test_view_model.dart';
class TestView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetBuilder<TestViewModel>(
init: TestViewModel(),
global: false,
builder: (_) => DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text('This is a test'),
bottom: TabBar(
labelPadding: EdgeInsets.symmetric(vertical: 10),
tabs: [
Text('Tab 1'),
Text('Tab 2'),
],
),
),
body: TabBarView(
children: [
_Tab1(),
_Tab2(),
],
),
),
),
);
}
}
class _Tab2 extends StatelessWidget {
const _Tab2({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetBuilder<TestViewModel>(
init: TestViewModel(),
global: false,
builder: (_) => Center(
child: _.loading ? CircularProgressIndicator() : Text(_.text),
),
);
}
}
class _Tab1 extends StatelessWidget {
const _Tab1({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetBuilder<TestViewModel>(
init: TestViewModel(),
global: false,
builder: (_) => Center(
child: _.loading ? CircularProgressIndicator() : Text(_.text),
),
);
}
}
import 'package:get/get.dart';
class TestViewModel extends GetxController {
String text = '';
bool _loading = false;
bool get loading => _loading;
set loading(bool value) {
_loading = value;
update();
}
@override
Future<void> onInit() async {
super.onInit();
loading = true;
await Future.delayed(Duration(seconds: 2));
text = 'hello';
loading = false;
}
}
Hello! I see you are using global: false in your widgets. To be honest, i don't know how this works so i don't know if it is wrong or right.
But on TextView i noticed that you don't need a GetBuilder there, since each Tab Get it's own GetBuilder, maybe removing it from TestView will fix?
Hello! I see you are using global: false in your widgets. To be honest, i don't know how this works so i don't know if it is wrong or right.
I used this because I want the Tabs to reload every time I switch tabs.
But on TextView i noticed that you don't need a GetBuilder there, since each Tab Get it's own GetBuilder, maybe removing it from TestView will fix?
Taking it out does not fix the issue.
Hi, i could reproduce this as well when switching between the tabs quickly (when loading) it breaks. Dont know why doing it like the below helps but it "works" as expected (note i am not even using the Controller c so it is all magic :P but the controller in argument however)
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:gettabs/controllers/testviewmodel.dart';
class TestView extends StatelessWidget {
final TestViewModel c = Get.put(TestViewModel());
@override
Widget build(BuildContext context) {
return GetBuilder<TestViewModel>(
init: TestViewModel(),
global: false,
//autoRemove: false,
builder: (controller) => DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text('This is a test'),
bottom: TabBar(
labelPadding: EdgeInsets.symmetric(vertical: 10),
tabs: [
Text('Tab 1'),
Text('Tab 2'),
],
),
),
body: TabBarView(
children: [
_Tab1(),
_Tab2(),
],
),
),
),
);
}
}
class _Tab2 extends StatelessWidget {
const _Tab2({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
TestViewModel controller = Get.put(TestViewModel());
return GetBuilder<TestViewModel>(
init: TestViewModel(),
global: false,
builder: (controller) => Center(
child: controller.loading
? CircularProgressIndicator()
: Text(controller.text),
),
);
}
}
class _Tab1 extends StatelessWidget {
const _Tab1({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
TestViewModel controller = Get.put(TestViewModel());
return GetBuilder<TestViewModel>(
init: TestViewModel(),
global: false,
builder: (controller) => Center(
child: controller.loading
? CircularProgressIndicator()
: Text(controller.text),
),
);
}
}
Doing this it "works" as expected, from the log:
Restarted application in 1,174ms.
flutter: INIT
flutter: [GETX] TestViewModel has been initialized
flutter: INIT
flutter: INIT
(i.e three GetBuilders init)
when click the tab a new init creates and other dispose
flutter: INIT
flutter: [GETX] onClose of TestViewModel called
flutter: [GETX] TestViewModel deleted from memory
switching:
flutter: INIT
flutter: [GETX] onClose of TestViewModel called
flutter: [GETX] TestViewModel deleted from memory
flutter: INIT
flutter: [GETX] onClose of TestViewModel called
flutter: [GETX] TestViewModel deleted from memory
where
@override
Future<void> onInit() async {
super.onInit();
print("INIT");
// print(isLoading.value);
// ever(isLoading, onChange);
//isLoading.value = true;
loading = true;
await Future.delayed(Duration(seconds: 2));
text = 'hello';
loading = false;
}
final TestViewModel c = Get.put(TestViewModel());
Wow, this really is magic, I'll leave the issue open though incase someone can offer an explanation.
hey, i would like to ask why would you need two different instances of a same controller? I always had that doubt but the last person that tried to explain me i did not understand...
Why not use the same instance the tabs? If each one need different data then why use the same controller? shouldn't be one specific controller for each screen?
Also, this solution that lundin (i could not mention you) said, don't this means that you have at all times two different instances of the same class? so when one is disposed GetX will fallback to the one that were initialized with Get.put()?
@jonataslaw always say you should not have two instances of a controller, unless you using global: false like you're doing
So i don't know how this works, but it does not seems right
@Nipodemos That is what I resolved to do(use different controllers). But I still opened the issue anyway to gain more insight and opinions.
@Prn-Ice i edited my comment with more things, you answer fast! 😅
Also, this solution that lundin (i could not mention you) said, don't this means that you have at all times two different instances of the same class? so when one is disposed GetX will fallback to the one that were initialized with Get.put()?
@jonataslaw always say you should not have two instances of a controller, unless you using global: false like you're doing
So i don't know how this works, but it does not seems right
Yeah it doesn't, seems weird, an error waiting to happen. I does give a sense of what I was going for.
Say It was the same data but on one tab just the data on the other some stuff then the data, someone else might really have this requirement.
hey, i could not reproduce your issue, i just copied and pasted your code and it did not give any error @Prn-Ice

Flutter 1.20.0-7.3.pre • channel beta • https://github.com/flutter/flutter.git
Framework • revision e606910f28 (26 hours ago) • 2020-07-28 16:06:37 -0700
Engine • revision ac95267aef
Tools • Dart 2.9.0 (build 2.9.0-21.10.beta)
get: 3.4.1
and i used in flutter web in a fresh created flutter app
The error appears only in the console.
Sorry I did not noticed. I did not read the console, I will test again
something is wrong with my console, i'll be able to test only tomorrow, sorry for delay.
Even so i doubt that i would come up with a solution 😅
Tabs are disposed when you switch pages.
I strongly recommend using the fenix mode, or using a find at the top level.
@jonataslaw I changed to
class TestView extends StatelessWidget {
@override
Widget build(BuildContext context) {
Get.lazyPut<TestViewModel>(() => TestViewModel(), fenix: true);
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text('This is a test'),
bottom: TabBar(
labelPadding: EdgeInsets.symmetric(vertical: 10),
tabs: [
Text('Tab 1'),
Text('Tab 2'),
],
),
),
body: TabBarView(
children: [
_Tab1(),
_Tab2(),
],
),
),
);
}
}
class _Tab2 extends StatelessWidget {
const _Tab2({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final _ = TestViewModel.to;
return Center(
child: _.loading ? CircularProgressIndicator() : Text(_.text),
);
}
}
class _Tab1 extends StatelessWidget {
const _Tab1({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final _ = TestViewModel.to;
return Center(
child: _.loading ? CircularProgressIndicator() : Text(_.text),
);
}
}
It does not produce the desired result.
@Prn-Ice it is not recommend to use Get.lazyPut() inside build methods. @jonataslaw can tell you better the reason. What i did to fix your code is:
Put the lazyPut in the main method with the fenix: true.
Put GetBuilder around each widget that i wanted to be rebuilded after Future, but without the init argument, since the viewmodel is already initialized.
class TestViewModel extends GetxController {
static TestViewModel get to => Get.find();
String text = 'if this shows, onInit did not run'; // changed only this line
bool _loading = false;
bool get loading => _loading;
set loading(bool value) {
_loading = value;
update();
}
@override
Future<void> onInit() async {
print('passing on onInit'); // and put this print
super.onInit();
loading = true;
await Future.delayed(Duration(seconds: 1));
text = 'hello';
loading = false;
}
}
Changed the initial value of text to be something that i could read
However, i got an stranger behavior:

Each tab when were disposed lost their state, but when you go to the tab again, the onInit does not run
flutter ( 5927): [GETX] INITIALIZED: If you need help, join our community support channels: https://tinyurl.com/y3cp88l3
I/flutter ( 5927): [GETX] GetMaterialController has been initialized
I/flutter ( 5927): [GETX] GOING TO ROUTE /
I/flutter ( 5927): [GETX] TestViewModel instance was created at that time
I/flutter ( 5927): passing on onInit
I/flutter ( 5927): [GETX] TestViewModel has been initialized
I/flutter ( 5927): [GETX] onClose of TestViewModel called
I/flutter ( 5927): [GETX] TestViewModel deleted from memory
I/flutter ( 5927): [GETX] TestViewModel instance was created at that time
I/flutter ( 5927): [GETX] onClose of TestViewModel called
I/flutter ( 5927): [GETX] TestViewModel deleted from memory
I/flutter ( 5927): [GETX] TestViewModel instance was created at that time
I/flutter ( 5927): [GETX] onClose of TestViewModel called
I/flutter ( 5927): [GETX] TestViewModel deleted from memory
I/flutter ( 5927): [GETX] TestViewModel instance was created at that time
I/flutter ( 5927): [GETX] onClose of TestViewModel called
I/flutter ( 5927): [GETX] TestViewModel deleted from memory
@jonataslaw i have no ideia why this happened, could you explain this to us?
@Nipodemos can I see your code for the view.
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
Get.lazyPut<TestViewModel>(() => TestViewModel(), fenix: true);
runApp(
GetMaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: TestView(),
),
);
}
class TestView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text('This is a test'),
bottom: TabBar(
labelPadding: EdgeInsets.symmetric(vertical: 10),
tabs: [
Text('Tab 1'),
Text('Tab 2'),
],
),
),
body: TabBarView(
children: [
_Tab1(),
_Tab2(),
],
),
),
);
}
}
class _Tab2 extends StatelessWidget {
const _Tab2({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetBuilder<TestViewModel>(
builder: (viewModel) {
return Center(
child: viewModel.loading
? CircularProgressIndicator()
: Text(viewModel.text),
);
},
);
}
}
class _Tab1 extends StatelessWidget {
const _Tab1({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetBuilder<TestViewModel>(
builder: (viewModel) {
return Center(
child: viewModel.loading
? CircularProgressIndicator()
: Text(viewModel.text),
);
},
);
}
}
class TestViewModel extends GetxController {
static TestViewModel get to => Get.find();
String text = 'if this shows, onInit did not run';
bool _loading = false;
bool get loading => _loading;
set loading(bool value) {
_loading = value;
update();
}
@override
Future<void> onInit() async {
print('passing on onInit');
super.onInit();
loading = true;
await Future.delayed(Duration(seconds: 1));
text = 'hello';
loading = false;
}
}
Yeah, I can confirm the same behavior.
Wow, this issue is huge.
I will see this here after my working hours.
Could you summarize what is the expected behavior and the current behavior for me to place myself in the reproduction example?
I'll let @Nipodemos summarize this new behavior:
As for the initial issue -
Expected behavior: I want the tabs to reload whenever I switch. so I set global: false for all views that use the controller.
Current behavior: From a UI standpoint, this works as expected and tabs do reload but an Unhandled Exception: setState() called after dispose() error keeps getting thrown in the console when you switch tabs while one is reloading.
Current Behavior:
Since both tabs were using a single controller, they should not be disposed, since it is said that by default get will know which routes are using that controller and dispose only when the current route is not using it anymore.
but in that case, i initialized the controller in main, and set fenix: true, and every time i change tabs, the controller is disposed and another one is created. But I don't know why, init() is not called again, and we can check that by simply seeing that the tab is not shoing the CircularProgress when it is opened a second time, and the previous value the text had is lost.
Expected Behavior:
Since the same controller is being used on two tabs, to maintain the instance alive when a tab is disposed to show the other tab.
Thank you very much for your time on this issue. Analyzing the sample code, I saw that there was a problem with onInit, mainly in Tabs.
I fixed this problem in version 3.4.3, so I'm closing this.
If you encounter another problem with Getx, do not hesitate to open another issue. And thanks for providing reproduction codes for the problem, which helps a lot in identifying problems.