getx
getx copied to clipboard
Get.offAllNamed() removes destination's controller
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:
- Click 'Go to Other page'
- 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
I have a same issue, and i'm still waiting for it fixed,I'm using setState instead
@FlorianHeuse Try to use the predicate parameter so the destination wouldn't be remove
Get.offAllNamed('/home', predicate: (route) => Get.currentRoute == '/home');
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');
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,...
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)
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),
),
);
}
}
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 myCounterController
will be a new instance with the default value of 0, which means thatoffAllNamed
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.
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 functionoffAllNamed
-> 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.
Same error here... any solutions?
+1
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.
sir, please release a fix for this @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');
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.
I have the same issue, anyone have a solution ?
if(Get.isRegistered<LoginEmailController>()){
Get.until((route) => route.settings.name == RouteNames.systemLoginEmail);
}else{
Get.offAllNamed(RouteNames.systemLoginEmail);
}
完满解决!!!
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
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);