Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Mixin equivalent to AutomaticKeepAliveClientMixin #822

Open
lpylpyleo opened this issue Nov 26, 2020 · 30 comments
Open

[Feature Request] Mixin equivalent to AutomaticKeepAliveClientMixin #822

lpylpyleo opened this issue Nov 26, 2020 · 30 comments
Labels
Feature Request Issues that suggest a new functionality or to edit an existing one to better improve GetX

Comments

@lpylpyleo
Copy link

lpylpyleo commented Nov 26, 2020

AutomaticKeepAliveClientMixin is useful it comes to TabView or PageView.

Tried to use TextEditingController and PageStorageKey to keep user input and ListView's scroll position. But it will be more convinient if we have AutomaticKeepAliveClientMixin.

I have noticed that there is a SingleGetTickerProviderMixin equivalent to SingleTickerProviderMixin in Getx.
So is it possible to make a mixin like AutomaticKeepAliveClientMixin as well or I just don't know the correct usage?

Not sure if it is appropriate to use Feature request. Thank you advance.

@eduardoflorence
Copy link
Collaborator

Hy @lpylpyleo,

  • AutomaticKeepAliveClientMixin is used for the initState not to run more than once. In Getx you can use a controller for each view and you will not need to use StatefullWidget. The onInit method of the controller will be called only once. See an example:
import 'package:flutter/material.dart';
import 'package:get/get.dart';

void main() {
  runApp(GetMaterialApp(home: Home()));
}

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final controller = Get.put(HomeController());

    return Scaffold(
      appBar: AppBar(
        bottom: TabBar(
          controller: controller.tabController,
          tabs: [
            Tab(icon: Icon(Icons.directions_car)),
            Tab(icon: Icon(Icons.directions_bike)),
          ],
        ),
        title: Text('Tabs Demo'),
      ),
      body: TabBarView(
        controller: controller.tabController,
        children: [
          CarPage(),
          BikePage(),
        ],
      ),
    );
  }
}

class HomeController extends GetxController with SingleGetTickerProviderMixin {
  TabController tabController;

  @override
  void onInit() {
    tabController = TabController(vsync: this, length: 2);
    super.onInit();
  }
}

class CarPage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    final controller = Get.put(CarPageController());
    return Center(
      child: Obx(() => Text(controller.car.value)),
    );
  }
}

class CarPageController extends GetxController {
  final car = ''.obs;

  @override
  void onInit() {
    print('Call API Car');  // called only once
    car.value = 'Ferrari';
    super.onInit();
  }
}

class BikePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final controller = Get.put(BikePageController());
    return Center(
      child: Obx(() => Text(controller.bike.value)),
    );
  }
}

class BikePageController extends GetxController {
  final bike = ''.obs;

  @override
  void onInit() {
    print('Call API Bike');  // called only once
    bike.value = 'BMC';
    super.onInit();
  }
}

@lpylpyleo
Copy link
Author

lpylpyleo commented Nov 27, 2020

Thanks for you sample code. @eduardoflorence

I edited your second TabView's using a ListView. When I switched to this tab, scroll the list, then switched to first tab, then back. The ListView's position is reset to 0.0. Also, the build is called every time I switched to this tab.

class BikePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('BikePage build'); // called every time
    final controller = Get.put(BikePageController());
    return ListView.builder(
      itemBuilder: (BuildContext context, int index) {
        return Text(index.toString());
      },
    );
  }
}

class BikePageController extends GetxController {
  final bike = ''.obs;

  @override
  void onInit() {
    print('Call API Bike'); // called only once
    bike.value = 'BMC';
    super.onInit();
  }
}

As a comparison, the AutomaticKeepAliveClientMixin version will automatically store the ListView's position, as well as
other state like TextField. Also, the build method only fired once. So should I just use StatefulWidget + AutomaticKeepAliveClientMixin instead of GetView + GetxController in cases like this?

class BikePage1 extends StatefulWidget {
  @override
  _BikePage1State createState() => _BikePage1State();
}

class _BikePage1State extends State<BikePage1>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    print('BikePage1 build'); //called only once
    return ListView.builder(
      itemBuilder: (BuildContext context, int index) {
        return Text(index.toString());
      },
    );
  }

  @override
  bool get wantKeepAlive => true;
}

@lpylpyleo lpylpyleo changed the title Mixin which is equivalent to AutomaticKeepAliveClientMixin for GetView Mixin which is equivalent to AutomaticKeepAliveClientMixin for GetxController Nov 27, 2020
@eduardoflorence eduardoflorence added the Feature Request Issues that suggest a new functionality or to edit an existing one to better improve GetX label Nov 27, 2020
@eduardoflorence
Copy link
Collaborator

You're right, AutomaticKeepAliveClientMixin does not execute the build method again on StatefullWidget. I will mark this issue as a feature request

@jonataslaw jonataslaw changed the title Mixin which is equivalent to AutomaticKeepAliveClientMixin for GetxController [Feature Request] Mixin equivalent to AutomaticKeepAliveClientMixin Nov 27, 2020
@jonataslaw
Copy link
Owner

I think this should not be on the controller side, since it changes how the build method behaves. The view is responsible for this.
I think it is possible to create a class of its own for this, maybe it is even better than a mixin, as it does not need to overwrite the super.build, and because it is possible to add the getter to the controller.
Let's implement this, I just don't know if it would be better to create it inside a GetX/Obx/GetBuilder (as a widget wrapper), or as an abstract class (like GetView/GetWiget)

@dev-phamquoctrong
Copy link

Any update? I wanna keep state between two tab. How to?

@eduardoflorence
Copy link
Collaborator

@pqtrong17, use the example I provided above, but do what @lpylpyleo informed, replacing the StatelessWidget of each page with a Statefulwidget with AutomaticKeepAliveClientMixin

@dev-phamquoctrong
Copy link

@eduardoflorence I use StatefulWidget and work for me but I wanna use GetView or GetWidget, any solution?

@jy6c9w08
Copy link

jy6c9w08 commented Jan 3, 2021

Hello, I think AutomaticKeepAliveClientMixin is useful. If you add it on GetxController,please notice me.Thanks

@dev-phamquoctrong
Copy link

dev-phamquoctrong commented Jan 4, 2021

@jy6c9w08 If you made it, you will get the error:

'AutomaticKeepAliveClientMixin<StatefulWidget>' can't be mixed onto 'GetxController' because 'GetxController' doesn't implement 'State<StatefulWidget>'.

@jy6c9w08
Copy link

jy6c9w08 commented Jan 4, 2021

@pqtrong17 Thanks for your reply, I know your mean. So,how can I useing getx to keep page state?I want avoid to use StatefulWidget. I see you mark this issue as a feature request.If you realize this feature,Please notice me.Thsnks.

@dev-phamquoctrong
Copy link

dev-phamquoctrong commented Jan 8, 2021

Any update, please? @eduardoflorence

@ataknakbulut
Copy link

Any update, please? @eduardoflorence

????

@dev-phamquoctrong
Copy link

Any update, please? @eduardoflorence

????

I use StatefulWidget and work for me but I wanna use GetView or GetWidget, any solution?

@maares
Copy link

maares commented Feb 9, 2021

@lpylpyleo an alternative to keep the Lisview off set position would be to use PageStorageKey() for every tabBarView or PageView. You can follow this example here.

This feature would be more than appreciated, I'm experiencing this issue myself as my App is nested with TabBars.
Thanks again

@lpylpyleo
Copy link
Author

lpylpyleo commented Feb 25, 2021

@maares Thanks. Now I use a simple KeepAliveWrapper as a work around.

TabBarView(
        controller: controller.tabController,
        children: const [
          KeepAliveWrapper(child: CarPage()),
          KeepAliveWrapper(child: BikePage()),
        ],
)

My KeepAliveWrapper:

class KeepAliveWrapper extends StatefulWidget {
  final Widget child;

  const KeepAliveWrapper({Key key, this.child}) : super(key: key);

  @override
  _KeepAliveWrapperState createState() => _KeepAliveWrapperState();
}

class _KeepAliveWrapperState extends State<KeepAliveWrapper>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return widget.child;
  }

  @override
  bool get wantKeepAlive => true;
}

@loic-hamdi
Copy link

Anyone found a solution to keep the ScrollController level please ?

@eduardoflorence
Copy link
Collaborator

Anyone found a solution to keep the ScrollController level please ?

See above (Stateful)

@Zohenn
Copy link

Zohenn commented Mar 2, 2021

I've been using a similar approach to the one provided by @lpylpyleo for a while now, but you should consider passing a closure that returns a Widget instead of doing it directly. That way your widget won't be built until you go to this tab for the first time, which for me was what I wanted, because it was fetching some data from database. No need to fetch if user might not go there at all.

@AzizMarashly
Copy link

AzizMarashly commented Oct 6, 2021

Any updates?

3 similar comments
@syssam
Copy link

syssam commented Oct 17, 2021

Any updates?

@qq326646683
Copy link

Any updates?

@YeFei572
Copy link

YeFei572 commented Dec 9, 2021

Any updates?

@pacifio
Copy link

pacifio commented Feb 17, 2022

Update on this ?

@3xscola
Copy link

3xscola commented May 22, 2022

I think this should be taken into consideration.

@DSPerson
Copy link

DSPerson commented Jul 5, 2022

Nice!!!

Update

const KeepAliveWrapper({Key? key, required this.child}) : super(key: key);

@Asif-shah786
Copy link

Any updates on AutomaticKeepAliveClientMixin in getx ??

@wildsurfer
Copy link

+1 for this feature :)

@leggod
Copy link

leggod commented Jan 11, 2023

Thanks for you sample code. @eduardoflorence

I edited your second TabView's using a ListView. When I switched to this tab, scroll the list, then switched to first tab, then back. The ListView's position is reset to 0.0. Also, the build is called every time I switched to this tab.

class BikePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('BikePage build'); // called every time
    final controller = Get.put(BikePageController());
    return ListView.builder(
      itemBuilder: (BuildContext context, int index) {
        return Text(index.toString());
      },
    );
  }
}

class BikePageController extends GetxController {
  final bike = ''.obs;

  @override
  void onInit() {
    print('Call API Bike'); // called only once
    bike.value = 'BMC';
    super.onInit();
  }
}

As a comparison, the AutomaticKeepAliveClientMixin version will automatically store the ListView's position, as well as other state like TextField. Also, the build method only fired once. So should I just use StatefulWidget + AutomaticKeepAliveClientMixin instead of GetView + GetxController in cases like this?

class BikePage1 extends StatefulWidget {
  @override
  _BikePage1State createState() => _BikePage1State();
}

class _BikePage1State extends State<BikePage1>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    print('BikePage1 build'); //called only once
    return ListView.builder(
      itemBuilder: (BuildContext context, int index) {
        return Text(index.toString());
      },
    );
  }

  @override
  bool get wantKeepAlive => true;
}

Update

Support for null security

class KeepAliveWrapper extends StatefulWidget {
  final Widget child;
  const KeepAliveWrapper(this.child, {super.key});

  @override
  State<KeepAliveWrapper> createState() => _KeepAliveWrapperState();
}

class _KeepAliveWrapperState extends State<KeepAliveWrapper>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return widget.child;
  }

  @override
  bool get wantKeepAlive => true;
}

@liemfs
Copy link

liemfs commented Apr 18, 2023

Try this

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

abstract class GetViewKeepAlive<T> extends StatefulWidget {
  const GetViewKeepAlive({super.key, this.tag});

  @override
  State<GetViewKeepAlive<T>> createState() => _GetViewKeepAliveState<T>();

  @protected
  Widget build(BuildContext context);

  @protected
  final String? tag;

  @protected
  T get controller => GetInstance().find<T>(tag: tag)!;
}

class _GetViewKeepAliveState<T> extends State<GetViewKeepAlive<T>> with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return widget.build(context);
  }

  @override
  bool get wantKeepAlive => true;
}

Change your existed view from
class RickRollView extends GetView<RickRollController>
to
class RickRollView extends GetViewKeepAlive<RickRollController>

@LeonardoBratti
Copy link

Any updates ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature Request Issues that suggest a new functionality or to edit an existing one to better improve GetX
Projects
None yet
Development

No branches or pull requests