I have two pages: HomePage and DetailsPage and associated GetxControllers.
HomePage:
class HomePage extends GetView<HomeController> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('HomePage')),
body: Container(
child: Obx(
() => ListView.builder(
itemCount: controller.task.length,
itemBuilder: (context, index) {
return ListTile(
leading: Text('${index + 1}'),
title: Text(controller.task[index]["name"]),
onTap: () {
Get.to(
DetailsPage(),
arguments: controller.task[index]["name"],
);
},
);
},
),
),
),
);
}
}
HomeController:
class HomeController extends GetxController {
final TaskRepository repository;
HomeController({@required this.repository}) : assert(repository != null);
final _task = [].obs;
set task(value) => this._task.assignAll(value);
get task => this._task;
onInit() {
super.onInit();
getAllTask();
}
getAllTask() {
repository.getAll().then((value) => task = value);
}
}
As you can see the HomeController depends on a TaskRepository which is a mock repo.
And my DetailsPage:
class DetailsPage extends GetView<DetailsController> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
GestureDetector(
onTap: () {
Get.back();
},
child: Row(
children: [
Icon(Icons.arrow_back),
Text('Go Back'),
],
),
),
Expanded(
child: Center(
child: Obx(
() => Text(controller.taskDetail.value),
),
),
),
],
),
);
}
}
DetailsController:
class DetailsController extends GetxController {
final taskDetail = ''.obs;
@override
void onInit() {
super.onInit();
taskDetail.value = Get.arguments;
}
}
I have created an AppDependencies class to initialize the dependencies (controllers, repositories, API clients, etc.):
class AppDependencies {
static Future<void> init() async {
Get.lazyPut(() => HomeController(repository: Get.find()));
Get.lazyPut(() => DetailsController());
Get.lazyPut(() => TaskRepository(apiClient: Get.find()));
Get.lazyPut(() => TaskClient());
}
}
I am initializing all the dependencies by calling AppDependencies.init()
on main()
:
void main() async {
await AppDependencies.init();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
HomePage
DetailsPage first time
Going back to HomePage and then Going again to DetailsPage
As you can see on the third image, going back from DetailsPage to HomePage and going back to DetailsPage causes an exception saying:
"DetailsController" not found. You need to call "Get.put(DetailsController())" or "Get.lazyPut(()=>DetailsController())"
But I already did that on main()
. I also tried with Get.put()
instead of Get.lazyPut()
but I found that for Get.put()
any dependencies of any other dependency must be registered before the dependent one. For example, HomeController depends on TaskRepository so TaskRepository must be before HomeController if using Get.put()
like:
Get.put(TaskRepository());
Get.put(HomeController());
And this is not what I want because I don't wanna track what comes before what manually. And I found that this causes if there's a back button (which almost every page has).
What I am doing wrong here?
Move Get. put from being a field of MainScreen to inside its build() method. The Controller can then be disposed when MainScreen is popped.
GetX has some great features out of the box, making it even easier to develop mobile applications in Flutter without any boilerplate code: Internationalization: translations with key-value maps, various language support, using translations with singulars, plurals, and parameters.
However, if you want to write less code and speed up your development process, you can use GetX (also called Get). Furthermore, you can use routes, snack bars, dialogs, bottom sheets without context. The two code snippets below do the same thing: Navigating to a route named SomeScreen. Using Flutter’s Navigator:
You facing this issue because of when you use back at that time it remove or delete controller like : [GETX] "LoginController" onDelete () called For prevent this issue you need to create InitialBinding.
a "basic" Controller and a GetView widget with specific tag taken from Get.parameters. both initialized from Binding class and GetPage definition. each list item will fire the same route with different tag as parameter. name: test_getx description: A new Flutter project.
By programmatically, I mean by calling Navigator.pop (context); When you go back using any of the above method then it will do Navigator.pop (context) under the hood.
If you don't want to use fenix = true
, you can use something like this for example in your click method:
try {
///find the controller and
///crush here if it's not initialized
final authController = Get.find<AuthController>();
if(authController.initialized)
Get.toNamed('/auth');
else {
Get.lazyPut(() => AuthController());
Get.toNamed('/auth');
}
} catch(e) {
Get.lazyPut(() => AuthController());
Get.toNamed('/auth');
}
About memory, important to consider of fenix
param:
The internal register of [builder()] will remain in memory to recreate the Instance if the Instance has been removed with [Get.delete()]. Therefore, future calls to [Get.find()] will return the same Instance.
You need to bind all controller and the add in GetMaterialApp.
You facing this issue because of when you use back at that time it remove or delete controller like : [GETX] "LoginController" onDelete() called
For prevent this issue you need to create InitialBinding.
InitialBinding
class InitialBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut(() => LoginController(LoginRepo()), fenix: true);
Get.lazyPut(() => HomeController(HomeRepo()), fenix: true);
}
}
In Main method :
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Get.put(AppController());
return GetMaterialApp(
title: StringConst.APP_NAME,
debugShowCheckedModeBanner: false,
defaultTransition: Transition.rightToLeft,
initialBinding: InitialBinding(),
theme: ThemeData(
primarySwatch: ColorConst.COLOR,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
initialRoute: RoutersConst.initialRoute,
getPages: routes(),
);
}
}
Thanks
Updated answer with Bindings:
You can achieve greater control of how how and when you controllers initialize with bindings and smart management. So if you need the onInit to fire every time you go the page you can do so with bindings. Setup a dedicated bindings class for your details page.
class DetailsPageBinding extends Bindings {
@override
void dependencies() {
// any controllers you need for this page you can lazy init here without setting fenix to true
}
}
If you're not already using GetMaterialApp instead of MaterialApp you'll need to do so. I suggest throwing static const id = 'details_page';
on your page(s) so you don't have to mess with raw strings for routing.
A basic example of your GetMaterialApp would look like this.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
initialRoute: HomePage.id,
title: 'Material App',
getPages: [
GetPage(name: HomePage.id, page: () => HomePage()),
// adding the new bindings class in the binding field below will link those controllers to the page and fire the dependancies override when you route to the page
GetPage(name: DetailsPage.id, page: () => DetailsPage(), binding: DetailsPageBinding()),
],
);
}
}
Then you'll need to do your routing via
Get.toNamed(DetailsPage.id)
Original Answer:
Add fenix: true
to your lazy init; Check the docs on lazyPut.
Get.lazyPut(() => HomeController(repository: Get.find()), fenix: true);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With