Getx: Controller's onInit() is not being called after removed from memory

Created on 4 Aug 2020  路  5Comments  路  Source: jonataslaw/getx

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:

  1. Set the init method of GetBuilder to create a controller (the controller's onInit will be called as expected);
  2. Load the widget (screen) that depends on that builder (step 1);
  3. Dispose of the aforementioned widget so that Getx can automatically remove the controller from memory;
  4. Load the same widget from step 2 and it will be possible to check that onInit() is not being called again.

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'),
            )
          ],
        ),
      ),
    );
  }
}

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.

All 5 comments

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

aztecrabbit picture aztecrabbit  路  3Comments

Denilson-source picture Denilson-source  路  3Comments

Nipodemos picture Nipodemos  路  4Comments

manojeeva picture manojeeva  路  3Comments

ad-on-is picture ad-on-is  路  3Comments