getx icon indicating copy to clipboard operation
getx copied to clipboard

Get.offAllNamed() removes destination's controller

Open FlorianHeuse opened this issue 3 years ago • 17 comments

Describe the bug Get.offAllNamed(destination) cleans up destination controller immediately, when going from HomePage to OtherPage and back to HomePage.

i.e currently at /home and call Get.toNamed('/other') will bring me to /other and initializes OtherController. On OtherPage Get.offAllNamed('/home'), brings me to Route /home, removes Route /other, removes Route /home, creates an instance of HomeController, immediately deletes HomeController and deletes OtherController. Setting the fenix flag to true in the bindings doesn't change behaviour.

  • [GETX] GOING TO ROUTE /home
  • [GETX] Instance "HomeController" has been created
  • [GETX] Instance "HomeController" has been initialized
  • [GETX] GOING TO ROUTE /other
  • [GETX] Instance "OtherController" has been created
  • [GETX] Instance "OtherController" has been initialized
  • [GETX] GOING TO ROUTE /home
  • [GETX] REMOVING ROUTE /other
  • [GETX] REMOVING ROUTE /home
  • [GETX] Instance "HomeController" has been created
  • [GETX] Instance "HomeController" has been initialized
  • [GETX] "HomeController" onDelete() called
  • [GETX] "HomeController" deleted from memory
  • [GETX] "OtherController" onDelete() called
  • [GETX] "OtherController" deleted from memory

**Reproduction code

example:

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'Flutter Demo',
      initialRoute: '/home',
      getPages: _getPages(),
    );
  }

  List<GetPage> _getPages() {
    return [
      GetPage(
        name: '/home',
        page: () => const HomePage(),
        binding: HomeBindings(),
      ),
      GetPage(
        name: '/other',
        page: () => const OtherPage(),
        binding: OtherBindings(),
      ),
    ];
  }
}

class HomePage extends GetView<HomeController> {
  const HomePage({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(controller.title),
            Obx(() => Text('HomeCounter: ${controller.count.value}')),
            ElevatedButton(
              onPressed: () => controller.count.value++,
              child: const Text('+'),
            ),
            ElevatedButton(
              onPressed: () {
                Get.toNamed('/other');
              },
              child: const Text('Go to Other page'),
            )
          ],
        ),
      ),
    );
  }
}

class OtherPage extends GetView<OtherController> {
  const OtherPage({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Obx(() => Text('OtherPageCounter: ${controller.count.value}')),
          ElevatedButton(
            onPressed: () => controller.count.value++,
            child: const Text('+'),
          ),
          ElevatedButton(
            onPressed: () => Get.offAllNamed('/home'),
            child: const Text('Go Back'),
          ),
        ],
      ),
    ));
  }
}

class HomeBindings implements Bindings {
  @override
  void dependencies() {
    Get.put(HomeController());
  }
}

class OtherBindings implements Bindings {
  @override
  void dependencies() {
    Get.put(OtherController());
  }
}

class HomeController extends RxController {
  final String title = "Home";
  RxInt count = 0.obs;
}

class OtherController extends RxController {
  RxInt count = 0.obs;
}

To Reproduce Steps to reproduce the behavior:

  1. Click 'Go to Other page'
  2. Click on 'Go back'

Expected behavior I expect that if I go back with Get.offAllNamed('/home') HomeController does not get deleted or old instance gets deleted and new instance is created with all lifecycle methodes (e.g. onInit, onReady).

Flutter Version: 2.5.0-5.2.pre

Get Version: 4.3.8

Describe on which device you found the bug: Pixel 4XL

FlorianHeuse avatar Nov 02 '21 23:11 FlorianHeuse

I have a same issue, and i'm still waiting for it fixed,I'm using setState instead

quocviet1996 avatar Nov 05 '21 06:11 quocviet1996

@FlorianHeuse Try to use the predicate parameter so the destination wouldn't be remove

Get.offAllNamed('/home', predicate: (route) => Get.currentRoute == '/home');

RuizeMT avatar Nov 16 '21 16:11 RuizeMT

Just to explain what is happening:

Get.offAllNamed will take all pages off the Stack, and open the named route next.

You see the following in the logs:

[GETX] Instance "HomeController" has been created [GETX] Instance "HomeController" has been initialized [GETX] "HomeController" onDelete() called [GETX] "HomeController" deleted from memory [GETX] "OtherController" onDelete() called [GETX] "OtherController" deleted from memory

Another instance of HomeController was created, and the old one received onDelete and was deleted from memory.

That's what Get.offAllNamed does. However, what you are looking for is the "until" method. That would solve your problem:

 Get.until((route) => Get.currentRoute == '/home');

jonataslaw avatar Dec 12 '21 23:12 jonataslaw

Just to explain what is happening:

Get.offAllNamed will take all pages off the Stack, and open the named route next.

You see the following in the logs:

[GETX] Instance "HomeController" has been created [GETX] Instance "HomeController" has been initialized [GETX] "HomeController" onDelete() called [GETX] "HomeController" deleted from memory [GETX] "OtherController" onDelete() called [GETX] "OtherController" deleted from memory

Another instance of HomeController was created, and the old one received onDelete and was deleted from memory.

That's what Get.offAllNamed does. However, what you are looking for is the "until" method. That would solve your problem:

 Get.until((route) => Get.currentRoute == '/home');

So we still don't have a solution for this right? i'm using until instead for my project, but because the controller not refresh or created again, so when i'm comeback to first page with offAll or offAllName(from login->signup->login and remove all page previous), i need manually refresh variable of my login controller because this controller not deleted and created again.

And i have to face a issue when i comeback to login with offAllName or offAll, the login controller as you say is a new controller still being deleted with old login controller, and when i'm click on my page to run function in controller, a new controller was created, but my page create more problem relate to controller like click login but nothing is active, and the form not display error message,...

quocviet1996 avatar Dec 15 '21 01:12 quocviet1996

Just to explain what is happening: Get.offAllNamed will take all pages off the Stack, and open the named route next. You see the following in the logs: [GETX] Instance "HomeController" has been created [GETX] Instance "HomeController" has been initialized [GETX] "HomeController" onDelete() called [GETX] "HomeController" deleted from memory [GETX] "OtherController" onDelete() called [GETX] "OtherController" deleted from memory Another instance of HomeController was created, and the old one received onDelete and was deleted from memory. That's what Get.offAllNamed does. However, what you are looking for is the "until" method. That would solve your problem:

 Get.until((route) => Get.currentRoute == '/home');

So we still don't have a solution for this right? i'm using until instead for my project, but because the controller not refresh or created again, so when i'm comeback to first page with offAll or offAllName(from login->signup->login and remove all page previous), i need manually refresh variable of my login controller because this controller not deleted and created again.

And i have to face a issue when i comeback to login with offAllName or offAll, the login controller as you say is a new controller still being deleted with old login controller, and when i'm click on my page to run function in controller, a new controller was created, but my page create more problem relate to controller like click login but nothing is active, and the form not display error message,...

There's actually no problem with that. until -> back until the predicate is true, then you can use a page selector inside the function offAllNamed -> All pages will be deleted (all non-permanent controllers too), and the new page will enter the Stack, with a new controller (unless the controller is permanent)

jonataslaw avatar Dec 15 '21 12:12 jonataslaw

I also thought that this was an issue, because the state of a controller wasn't updating while using offAllNamed. I also made a small project with minimal code. You can try it out and you'll see, that my CounterController will be a new instance with the default value of 0, which means that offAllNamed works correctly even if the logs are a bit misleading.

import 'package:flutter/material.dart';
import 'package:get/get.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'Flutter Demo',
      initialRoute: '/counter',
      getPages: [
        GetPage(
          name: '/counter',
          page: () => const CounterPage(),
          binding: CounterBinding(),
          children: [
            GetPage(
              name: '/profile',
              page: () => const ProfilePage(),
            ),
          ],
        ),
      ],
    );
  }
}

class CounterBinding implements Bindings {
  @override
  void dependencies() {
    Get.put(CounterController());
  }
}

class CounterController extends GetxController {
  final counter = 0.obs;
}

class CounterPage extends GetView<CounterController> {
  const CounterPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Obx(() => Text(
                  '${controller.counter.value}',
                  style: Theme.of(context).textTheme.headline4,
                )),
          ],
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () => Get.toNamed('/counter/profile'),
            heroTag: 'Profile',
            child: const Icon(Icons.person),
          ),
          const SizedBox(height: 8.0),
          FloatingActionButton(
            onPressed: () => controller.counter.value++,
            child: const Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

class ProfilePage extends StatelessWidget {
  const ProfilePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Profile')),
      floatingActionButton: FloatingActionButton(
        onPressed: () => Get.offAllNamed('/counter'),
        child: const Icon(Icons.clear),
      ),
    );
  }
}

jonaspoxleitner avatar Feb 21 '22 18:02 jonaspoxleitner

I also thought that this was an issue, because the state of a controller wasn't updating while using offAllNamed. I also made a small project with minimal code. You can try it out and you'll see, that my CounterController will be a new instance with the default value of 0, which means that offAllNamed works correctly even if the logs are a bit misleading.

import 'package:flutter/material.dart';
import 'package:get/get.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'Flutter Demo',
      initialRoute: '/counter',
      getPages: [
        GetPage(
          name: '/counter',
          page: () => const CounterPage(),
          binding: CounterBinding(),
          children: [
            GetPage(
              name: '/profile',
              page: () => const ProfilePage(),
            ),
          ],
        ),
      ],
    );
  }
}

class CounterBinding implements Bindings {
  @override
  void dependencies() {
    Get.put(CounterController());
  }
}

class CounterController extends GetxController {
  final counter = 0.obs;
}

class CounterPage extends GetView<CounterController> {
  const CounterPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Obx(() => Text(
                  '${controller.counter.value}',
                  style: Theme.of(context).textTheme.headline4,
                )),
          ],
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () => Get.toNamed('/counter/profile'),
            heroTag: 'Profile',
            child: const Icon(Icons.person),
          ),
          const SizedBox(height: 8.0),
          FloatingActionButton(
            onPressed: () => controller.counter.value++,
            child: const Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

class ProfilePage extends StatelessWidget {
  const ProfilePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Profile')),
      floatingActionButton: FloatingActionButton(
        onPressed: () => Get.offAllNamed('/counter'),
        child: const Icon(Icons.clear),
      ),
    );
  }
}

So I have checked your code, turn out this issue because i'm using lazyPut with fenix true, if i change to get.put, everything is working fine, you can try it out, first change to lazyput with fenix true, then click increase the number, go to profile page and click floating button to go back counter page, then click again the floating in counter page, you will see the counter controller recreate again, so the controller recreate twice, and the number is not increase.

quocviet1996 avatar Mar 01 '22 01:03 quocviet1996

Just to explain what is happening: Get.offAllNamed will take all pages off the Stack, and open the named route next. You see the following in the logs: [GETX] Instance "HomeController" has been created [GETX] Instance "HomeController" has been initialized [GETX] "HomeController" onDelete() called [GETX] "HomeController" deleted from memory [GETX] "OtherController" onDelete() called [GETX] "OtherController" deleted from memory Another instance of HomeController was created, and the old one received onDelete and was deleted from memory. That's what Get.offAllNamed does. However, what you are looking for is the "until" method. That would solve your problem:

 Get.until((route) => Get.currentRoute == '/home');

So we still don't have a solution for this right? i'm using until instead for my project, but because the controller not refresh or created again, so when i'm comeback to first page with offAll or offAllName(from login->signup->login and remove all page previous), i need manually refresh variable of my login controller because this controller not deleted and created again. And i have to face a issue when i comeback to login with offAllName or offAll, the login controller as you say is a new controller still being deleted with old login controller, and when i'm click on my page to run function in controller, a new controller was created, but my page create more problem relate to controller like click login but nothing is active, and the form not display error message,...

There's actually no problem with that. until -> back until the predicate is true, then you can use a page selector inside the function offAllNamed -> All pages will be deleted (all non-permanent controllers too), and the new page will enter the Stack, with a new controller (unless the controller is permanent)

offAllNamed pushes a new page to the history stack and removes the others. In case a page that has a same name as the new(pushed) page in the history stack, it does not delete the older controller instance but a new one. For example,

PageA --(Get.toNamed(PageB))--> PageB --(Get.offAllNamed(PageA))--> PageA

when Get.offAllNamed(PageA) is called, onClose() of new PageAController is called.

I/flutter (26062): ### onInit() of PageAController(hashCode: 575403058) is called // initial controller is initialized I/flutter (26062): ### onInit() of PageAController(hashCode: 443901899) is called // new controller is initialized I/flutter (26062): ### onClose() of PageAController(hashCode: 443901899) is called // new controller is closed(NOT an initial one)

When I use Navigator.pushNamedAndRemoveUntil() instead, it works correctly.

nicejhkim avatar May 27 '22 02:05 nicejhkim

Same error here... any solutions?

josesilocomo avatar Aug 12 '22 16:08 josesilocomo

+1

sglim avatar Oct 11 '22 02:10 sglim

i found the solution If you want to keep a specific controller in memory even after calling Get.offAll(), you can use the permanent parameter when binding the controller to a route. This will prevent the controller from being removed when the route is popped or replaced.

uttkarshrastogi27 avatar Mar 27 '23 08:03 uttkarshrastogi27

sir, please release a fix for this @jonataslaw

ramees-rowbest avatar Apr 05 '23 08:04 ramees-rowbest

Just to explain what is happening:

Get.offAllNamed will take all pages off the Stack, and open the named route next.

You see the following in the logs:

[GETX] Instance "HomeController" has been created [GETX] Instance "HomeController" has been initialized [GETX] "HomeController" onDelete() called [GETX] "HomeController" deleted from memory [GETX] "OtherController" onDelete() called [GETX] "OtherController" deleted from memory

Another instance of HomeController was created, and the old one received onDelete and was deleted from memory.

That's what Get.offAllNamed does. However, what you are looking for is the "until" method. That would solve your problem:

 Get.until((route) => Get.currentRoute == '/home');

Until is not safe. If there are a Flutter-web app, some user start from an URL that direct to PageA, and PageA hope remove all routes and to HomePage. In this case, if we want adapt all case, we have to use .offAllNamed.

huanghui1998hhh avatar May 23 '23 02:05 huanghui1998hhh

I have the same issue, anyone have a solution ?

jgrandchavin avatar Jun 25 '23 12:06 jgrandchavin

if(Get.isRegistered<LoginEmailController>()){
  Get.until((route) => route.settings.name == RouteNames.systemLoginEmail);
}else{
  Get.offAllNamed(RouteNames.systemLoginEmail);
}

完满解决!!!

345166018 avatar Jun 28 '23 07:06 345166018

if(Get.isRegistered<LoginEmailController>()){
  Get.until((route) => route.settings.name == RouteNames.systemLoginEmail);
}else{
  Get.offAllNamed(RouteNames.systemLoginEmail);
}

完满解决!!!

I'm leaving a comment for anyone who might see this issue in the future.

Unfortunately, my problem was not solved when i use this solution. And i had the same problem when i use another routing method(like Get.offToNamed). When i use my own dependency manage code, the problem was solved.

(sorry for jonataslaw, i suggest who see this comment please to use another di package...)

Flutter Version: 3.13.4

Get Version: 4.6.5

Describe on which device you found the bug: iPhone XS Max, iOS 16.6

gdaegeun539 avatar Jan 31 '24 11:01 gdaegeun539

If you want the controller not to be deleted and to work permanently, use permanent:true: when first instantiating example: Home_Controller home_controller = Get.put(Home_Controller(), permanent: true);

sabitzhambay avatar Apr 03 '24 09:04 sabitzhambay