jonataslaw / getx

Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with Get.
MIT License
10.23k stars 1.61k forks source link

GetMaterialApp's onInit put controllers get deleted on offAll #188

Closed duck-dev-go closed 4 years ago

duck-dev-go commented 4 years ago

So I tried to put the authentication controller after a delay which works. But the strange thing is when I execute Get.offAll() it closes the controllers that I put() in the onInit() of my GetMaterial app

class App extends StatelessWidget {
  final String deviceId;
  final PreferencesController preferencesController;

  App(this.deviceId, this.preferencesController);

  @override
  Widget build(BuildContext context) {
    SystemChrome.setEnabledSystemUIOverlays([]);

    return GetMaterialApp(
      onInit: () => initialize(),
      debugShowCheckedModeBanner: false,
      supportedLocales: [Locale('en', 'US'), Locale('nl', 'NL')],
      defaultTransition: Transition.fade,
      transitionDuration: Duration(microseconds: 0),
      localizationsDelegates: [
        LocalizationService.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      localeResolutionCallback: (locale, supportedLocales) {
        Locale changedTo = supportedLocales.first;
        for (var supportedLocale in supportedLocales) {
          if (locale != null &&
              supportedLocale.languageCode == locale.languageCode &&
              supportedLocale.countryCode == locale.countryCode) changedTo = supportedLocale;
        }

        final DeviceInfoModel deviceInfo = Get.find<DeviceInfoModel>();
        deviceInfo.updateLanguageCode(changedTo.languageCode);

        if (Get.isRegistred<DatabaseService>()) Get.find<DatabaseService>().setDeviceInfo(deviceInfo);

        return changedTo;
      },
      darkTheme: LightTheme.themeData,
      theme: DarkTheme.themeData,
      namedRoutes: {
        '/': GetRoute(page: TheSplashView()),
        '/disconnected': GetRoute(page: TheDisconnectedView()),
        '/signUp': GetRoute(page: TheSignUpView()),
        '/home': GetRoute(page: TheHomeView(), binding: AuthenticatedServiceBinding()),
        '/group/create': GetRoute(page: TheGroupCreateView(), binding: AuthenticatedServiceBinding()),
        '/group/invitations': GetRoute(page: TheGroupInvitationsView(), binding: AuthenticatedServiceBinding()),
        '/group/:groupId': GetRoute(page: TheGroupView(), binding: AuthenticatedServiceBinding()),
        '/group/members/invite': GetRoute(page: TheGroupInviteView(), binding: AuthenticatedServiceBinding()),
        '/settings': GetRoute(page: TheSettingsView(), binding: AuthenticatedServiceBinding()),
        '/account': GetRoute(page: TheAccountView(), binding: AuthenticatedServiceBinding()),
        '/imagepicker': GetRoute(page: TheImagePickerView()),
      },
    );
  }

  void initialize() {
    Get.changeThemeMode(preferencesController.isDarkMode ? ThemeMode.light : ThemeMode.dark);
    Get.put<DeviceInfoModel>(DeviceInfoModel(deviceId, Platform.operatingSystem));
    Get.put<PreferencesController>(preferencesController);
    Get.put(ContactsService());
    Get.put<OverlayController>(OverlayController());
    Future<void>.delayed(Duration(seconds: 2)).then((value) => onSplashLoadDone());
  }

  void onSplashLoadDone() {
    print('on splash done');
    Get.put<ConnectionController>(
      ConnectionController(
        onConnected: (result) {
          if (Get.find<AuthenticationController>().state == AuthenticationState.authenticated)
            Get.offAllNamed('/home');
          else
            Get.offAllNamed('/signUp');
        },
        onDisconnected: () {
          Get.offAll(TheDisconnectedView());
        },
      ),
    );
    Get.put<AuthenticationController>(
      FirebaseAuthenticationController(onAuthenticated: (state) {
        Get.offAllNamed('/home');
      }, onUnauthenticated: (state) {
        Get.offAllNamed('/signUp');
      }),
    );
  }
}

So the excution order is GetMaterialApp(onInit: () => initialize()) > initialize() > onSplashLoadDone() > onAuthenticated() > Get.offAllNamed('/home');

Once Get.offAllNamed('/home'); is executed the AutenticationController and ConnectionController are deleted that just got put() in the onSplashLoadDone()? Those controllers aren't bound to any widget so how is this possible?

/flutter (27459): [GOING TO ROUTE] /
I/flutter (27459): on splash done
I/flutter (27459): [GOING TO ROUTE] /home
I/flutter (27459): [REMOVING ROUTE] /
I/flutter (27459): authentication controller closed
I/flutter (27459): [GET] onClose of ConnectionController called
I/flutter (27459): [GET] ConnectionController deleted from memory
I/flutter (27459): [GET] onClose of AuthenticationController called
I/flutter (27459): [GET] AuthenticationController deleted from memory

What would make sense to me is if put controllers will stay in memory until you remove them. And bound controllers get removed when the widget is disposed.

So in short I render my splash page and I want to delay my put of the AuthenticationController for a certain amount of time(which would essentially be the amount of time my splash page is active). When my Autentication controller is put it should take care of everything from that point. From all the thing's i've tried this feels most elegant. But right now this isn't possible because when I put a controller after a future completes that I instantiate in the onInit() method of the GetMaterial it somehow get's deleted when using ofAll()

jonataslaw commented 4 years ago

In your GetMaterialApp you have the smartManagement option. Try to set it to SmartManagement.onlyBuilders.

Nothing you use with Get.put() will be discarded. Everything you use with Get.lazyPut will be retained.

duck-dev-go commented 4 years ago

Is there an option inbetween? Where put is not discarded but lazyput isn't retained?

jonataslaw commented 4 years ago

LazyPut is created at the exact moment that you use something like: Get.find() or Controller.to.value.

The widget can be linked to a Builder (when you type your builder you do this) or to a route (when there is a problem, Get uses the route as a subsidiary to guarantee availability). If you mark as "onlyBuilders" you are deactivating the security device (of the route), then only the widgets that were placed in memory during the term of a widget builder will be removed. Get.put is put into memory immediately, it was made to be highly synchronous. The moment you call it, it is put into memory immediately, with a delay of 0.0. So he has no way to associate himself with a Builder widget. That is why SmartManagement.full was created, which removes anything that was created after GetMaterialApp from memory. including Get.put.

An alternative solution is to initialize your permanent controllers on the main.

duck-dev-go commented 4 years ago

It appears I closed it to early, if I call a method from the main in a .then() the get.offAll() will still close the put controller. Is there anything I can do to do this?

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final String id = await DeviceInfoService().fetchId();
  final PreferencesController preferencesController = await PreferencesController.initializeAsync();

  Get.put<DeviceInfoModel>(DeviceInfoModel(id, Platform.operatingSystem));
  Get.put<PreferencesController>(preferencesController);
  Future<void>.delayed(Duration(seconds: 2)).then((value) => onSplashDalayDone());
  Get.put<OverlayController>(OverlayController());

  runApp(App());
}

void onSplashDalayDone() {
  print('on splash done');
  Get.put<ConnectionController>(
    ConnectionController(
      onConnected: (result) {
        if (Get.find<AuthenticationController>().state == AuthenticationState.authenticated)
          Get.offAllNamed('/home');
        else
          Get.offAllNamed('/signUp');
      },
      onDisconnected: () {
        Get.offAll(TheDisconnectedView());
      },
    ),
  );
  Get.put<AuthenticationController>(
    FirebaseAuthenticationController(onAuthenticated: (state) {
      Get.offAllNamed('/home');
    }, onUnauthenticated: (state) {
      Get.offAllNamed('/signUp');
    }),
  );
}

class App extends StatelessWidget {

  App();

  @override
  Widget build(BuildContext context) {
    SystemChrome.setEnabledSystemUIOverlays([]);

    return GetMaterialApp(
      onInit: () => initialize(),
      debugShowCheckedModeBanner: false,
      supportedLocales: [Locale('en', 'US'), Locale('nl', 'NL')],
      defaultTransition: Transition.fade,
      transitionDuration: Duration(microseconds: 0),
      localizationsDelegates: [
        LocalizationService.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      localeResolutionCallback: (locale, supportedLocales) {
        Locale changedTo = supportedLocales.first;
        for (var supportedLocale in supportedLocales) {
          if (locale != null &&
              supportedLocale.languageCode == locale.languageCode &&
              supportedLocale.countryCode == locale.countryCode) changedTo = supportedLocale;
        }

        final DeviceInfoModel deviceInfo = Get.find<DeviceInfoModel>();
        deviceInfo.updateLanguageCode(changedTo.languageCode);

        if (Get.isRegistred<DatabaseService>()) Get.find<DatabaseService>().setDeviceInfo(deviceInfo);

        return changedTo;
      },
      darkTheme: LightTheme.themeData,
      theme: DarkTheme.themeData,
      namedRoutes: {
        '/': GetRoute(page: TheSplashView()),
        '/disconnected': GetRoute(page: TheDisconnectedView()),
        '/signUp': GetRoute(page: TheSignUpView()),
        '/home': GetRoute(page: TheHomeView(), binding: AuthenticatedServiceBinding()),
        '/group/create': GetRoute(page: TheGroupCreateView(), bindings: [AuthenticatedServiceBinding(), ContactServiceBinding()]),
        '/group/invitations': GetRoute(page: TheGroupInvitationsView(), binding: AuthenticatedServiceBinding()),
        '/group/:groupId': GetRoute(page: TheGroupView(), bindings: [AuthenticatedServiceBinding(), ContactServiceBinding()]),
        '/group/members/invite': GetRoute(page: TheGroupInviteView(), binding: AuthenticatedServiceBinding()),
        '/settings': GetRoute(page: TheSettingsView(), binding: AuthenticatedServiceBinding()),
        '/account': GetRoute(page: TheAccountView(), binding: AuthenticatedServiceBinding()),
        '/imagepicker': GetRoute(page: TheImagePickerView()),
      },
    );
  }

  void initialize() {
    Get.changeThemeMode(Get.find<PreferencesController>().isDarkMode ? ThemeMode.light : ThemeMode.dark);
  }
}

This is the log

I/flutter (27657): [GOING TO ROUTE] /home
I/flutter (27657): [REMOVING ROUTE] /
I/flutter (27657): authentication controller closed
I/flutter (27657): [GET] onClose of ConnectionController called
I/flutter (27657): [GET] ConnectionController deleted from memory
I/flutter (27657): [GET] onClose of AuthenticationController called
I/flutter (27657): [GET] AuthenticationController deleted from memory
I/flutter (27657): [GET] DatabaseService instance was created at that time

Id like to keep the security of the SmartManagement. But I also want to put a peristent controller after a certain amount of time.

jonataslaw commented 4 years ago

In the last update you can remove permissions from SmartManagement to remove certain dependencies. This is easy, just insert: Get.put(Controller(), permanent: true);

Note: only Get.put can do this Get.lazyPut does not and never will, as this can bring other problems.

RodBr commented 4 years ago

Just to clarify here, I seeSmartManagement.onlyBuilder is now giving an error. If I'm instantiating services at the beginning of my app, they should now be Get.put with the permanentflag set, not Get.lazyPut, and therefore they will be instantiated immediately? .

jonataslaw commented 4 years ago

Just to clarify here, I seeSmartManagement.onlyBuilder is now giving an error. If I'm instantiating services at the beginning of my app, they should now be Get.put with the permanentflag set, not Get.lazyPut, and therefore they will be instantiated immediately?

Things have changed a bit in the last update. OnlyBuilder may have suffered from some changes in SmartManagement. What error are you facing?

Basically it doesn't really matter where you start your services, with Get.put using permanent = true it will never be removed from memory.

RodBr commented 4 years ago

This 'breaks' the ability to set up controllers/services in main if they have dependencies on each other. For instance, if a Firestore service relies on a Firebase auth service, the Get.findwill fail. I'll look for a workaround. Thanks

RodBr commented 4 years ago

Sorry, I was trying to respond from my phone on the road. Here's the error I get: smartManagement:SmartManagement.keepOnly, gives error Undefined name 'SmartManagement'.

Addin this fixes that issue: import 'package:get/src/root/smart_management.dart';

duck-dev-go commented 4 years ago

I have a big problem with the latest update. My permanent controller keep initializing over an over agian. All the code is the same except that I put permanent to true.

I/flutter (20527): [GOING TO ROUTE] /
I/flutter (20527): on splash done
I/flutter (20527): [GET] ConnectionController has been initialized
I/flutter (20527): init auth
I/flutter (20527): [GET] AuthenticationController has been initialized
I/flutter (20527): [GOING TO ROUTE] /home
I/flutter (20527): [REMOVING ROUTE] /
I/flutter (20527): [GET] [ConnectionController] has been marked as permanent, SmartManagement is not authorized to delete it.
I/flutter (20527): [GET] [AuthenticationController] has been marked as permanent, SmartManagement is not authorized to delete it.
I/flutter (20527): init auth
I/flutter (20527): [GET] AuthenticationController has been initialized
I/flutter (20527): [GOING TO ROUTE] /home
I/flutter (20527): [REMOVING ROUTE] /home
I/flutter (20527): [GET] [AuthenticationController] has been marked as permanent, SmartManagement is not authorized to delete it.
I/flutter (20527): [GET] DatabaseService instance was created at that time
I/flutter (20527): [GET] GetStreamController<List<GroupInvitationModel>> has been initialized
I/flutter (20527): init auth
I/flutter (20527): [GET] AuthenticationController has been initialized
I/flutter (20527): [GOING TO ROUTE] /home
I/flutter (20527): [REMOVING ROUTE] /home
I/flutter (20527): [GET] DatabaseService deleted from memory
I/flutter (20527): [GET] [AuthenticationController] has been marked as permanent, SmartManagement is not authorized to delete it.
I/flutter (20527): [GET] onClose of GetStreamController<List<GroupInvitationModel>> called
I/flutter (20527): [GET] GetStreamController<List<GroupInvitationModel>> deleted from memory
I/flutter (20527): [GET] DatabaseService instance was created at that time
I/flutter (20527): [GET] GetStreamController<List<GroupInvitationModel>> has been initialized
I/flutter (20527): [GET] onClose of GetStreamController<List<GroupInvitationModel>> called
I/flutter (20527): [GET] GetStreamController<List<GroupInvitationModel>> deleted from memory
I/flutter (20527): init auth
I/flutter (20527): [GET] AuthenticationController has been initialized
I/flutter (20527): [GOING TO ROUTE] /home
I/flutter (20527): [REMOVING ROUTE] /home
I/flutter (20527): [GET] DatabaseService deleted from memory
I/flutter (20527): Instance GetStreamController<List<GroupInvitationModel>> not found

Which causes my onAuthenticated to be called everytime and that causes my pages to switch in a infinite loop! So on every offAll() the put controller reinitializes. This break my entire app.