Describe the bug
After the controller is removed from memory, the onInit() method is not being called if it (the controller) is initialized again (from GetBuilder's init). The onInit() is only called on the controller's first initialization.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
The controller's onInit() method being called after it was removed from memory.
Flutter Version:
1.17.5
Get Version:
3.4.2
Describe on which device you found the bug:
Pixel 3 API 29 Emulator
Minimal reproduce code
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext mainContext) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
home: GetBuilder<AuthController>(
init: AuthController(),
builder: (authController) =>
authController.loggedIn ? HomePage.create() : AuthPage.create(),
),
);
}
}
class AuthController extends GetxController {
bool loggedIn = false;
signIn() {
loggedIn = true;
update();
}
signOut() {
loggedIn = false;
update();
}
}
class AuthPage extends StatelessWidget {
AuthPage._(this.controller);
static const routeName = '/';
final AuthController controller;
static Widget create() {
return AuthPage._(Get.find<AuthController>());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Authentication'), centerTitle: true),
body: Column(
children: <Widget>[
Expanded(
child: Center(
child: Column(
children: <Widget>[
Text('logged in: ${controller.loggedIn}'),
],
),
),
),
RaisedButton(
onPressed: () => controller.signIn(),
child: Text('Sign in'),
)
],
),
);
}
}
class HomePageController extends GetxController {
bool _initialized = false;
@override
void onInit() {
_initialized = true;
print('HomePageController initialized');
}
bool get initialized => _initialized;
}
class HomePage extends StatelessWidget {
HomePage._(this.controller);
static const routeName = '/home';
final HomePageController controller;
static Widget create() {
return GetBuilder<HomePageController>(
init: HomePageController(),
initState: (state) async {
print('HomePageController state initialized');
},
builder: (controller) => HomePage._(controller),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home'), centerTitle: true),
body: GetBuilder<AuthController>(
builder: (authController) => Column(
children: <Widget>[
Expanded(
child: Center(
child: Column(
children: <Widget>[
Text('logged in: ${authController.loggedIn}'),
Text(
'controller initialized: ${controller.initialized}',
style: TextStyle(
color:
controller.initialized ? Colors.green : Colors.red,
),
)
],
),
),
),
RaisedButton(
onPressed: () => authController.signOut(),
child: Text('Sign out'),
)
],
),
),
);
}
}
After debuging the Getx code, I figured it out that the controller remains on GetConfig.routesKey even if it is removed, so Get.find does not call initController again. Is it a bug?
Thank you for providing sample code.
I will look into this later.
Added an improved example to better illustrate the issue.
Ok, maybe I fixed it.
The test:
import 'package:flutter_test/flutter_test.dart';
import 'package:matcher/matcher.dart';
import 'package:get/get.dart';
class DisposableController extends DisposableInterface {
bool initialized = false;
void onInit() async {
initialized = true;
}
}
void main() {
group('test put, delete and onInit execution', () {
tearDownAll(() {
Get.reset();
});
test('Get.put test with init check', () async {
final instance = Get.put<DisposableController>(DisposableController());
expect(instance, Get.find<DisposableController>());
expect(instance.initialized, true);
});
test('Get.delete test with disposable controller', () async {
expect(await Get.delete<DisposableController>(), true);
expect(() => Get.find<DisposableController>(),
throwsA(TypeMatcher<String>()));
});
test('Get.put test after delete with disposable controller', () async {
final instance = Get.put<DisposableController>(DisposableController());
expect(instance, Get.find<DisposableController>());
expect(instance.initialized, true);
});
});
}
The fix that was made on get_instance.dart :
/// Delete class instance on [S] and clean memory
Future<bool> delete<S>({String tag, String key, bool force = false}) async {
String newKey;
if (key == null) {
newKey = _getKey(S, tag);
} else {
newKey = key;
}
if (!GetConfig._singl.containsKey(newKey)) {
print('Instance $newKey not found');
return false;
}
FcBuilder builder = GetConfig._singl[newKey] as FcBuilder;
if (builder.permanent && !force) {
print(
'[GETX] [$newKey] has been marked as permanent, SmartManagement is not authorized to delete it.');
return false;
}
final i = builder.dependency;
if (i is GetxService && !force) {
return false;
}
if (i is DisposableInterface) {
await i.onClose();
if (GetConfig.isLogEnable) print('[GETX] onClose of $newKey called');
}
GetConfig._singl.removeWhere((oldkey, value) => (oldkey == newKey));
if (GetConfig._singl.containsKey(newKey)) {
print('[GETX] error on remove object $newKey');
} else {
// THIS IS THE FIX ----------------------------------
if (isDependencyInit<S>() &&
GetConfig.smartManagement != SmartManagement.onlyBuilder) {
GetConfig.routesKey.removeWhere((oldkey, value) => (oldkey == newKey));
}
// FIX END ------------------------------------------
if (GetConfig.isLogEnable) print('[GETX] $newKey deleted from memory');
}
// GetConfig.routesKey?.remove(key);
return true;
}
Could you check if is it ok ?
Thank you very much for pointing this out.
Your solution would work, and even save time, but by dawn I had already made a hotfix for this, changing the logic in which the controllers are initialized.
Thank you very much for opening this issue with a clear reproduction case, thanks to him it was possible to identify the problem.
If you encounter problems, do not hesitate to open another issue, and if you arrive at a solution as noted, feel free to open a PR and join the collaborators of this package.
Thank you, and since this issue was resolved in 3.4.3, I am closing this.
Most helpful comment
Thank you very much for pointing this out.
Your solution would work, and even save time, but by dawn I had already made a hotfix for this, changing the logic in which the controllers are initialized.
Thank you very much for opening this issue with a clear reproduction case, thanks to him it was possible to identify the problem.
If you encounter problems, do not hesitate to open another issue, and if you arrive at a solution as noted, feel free to open a PR and join the collaborators of this package.
Thank you, and since this issue was resolved in 3.4.3, I am closing this.