jonataslaw / getx

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

[Feature Request] Mixin equivalent to AutomaticKeepAliveClientMixin #822

Open lpylpyleo opened 4 years ago

lpylpyleo commented 4 years ago

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 commented 4 years ago

Hy @lpylpyleo,

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 commented 4 years ago

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;
}
eduardoflorence commented 4 years ago

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

jonataslaw commented 4 years ago

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)

phamquoctrongdev commented 4 years ago

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

eduardoflorence commented 3 years ago

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

phamquoctrongdev commented 3 years ago

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

jy6c9w08 commented 3 years ago

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

phamquoctrongdev commented 3 years ago

@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 commented 3 years ago

@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.

phamquoctrongdev commented 3 years ago

Any update, please? @eduardoflorence

ataknakbulut commented 3 years ago

Any update, please? @eduardoflorence

????

phamquoctrongdev commented 3 years ago

Any update, please? @eduardoflorence

????

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

maares commented 3 years ago

@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 commented 3 years ago

@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 commented 3 years ago

Anyone found a solution to keep the ScrollController level please ?

eduardoflorence commented 3 years ago

Anyone found a solution to keep the ScrollController level please ?

See above (Stateful)

Zohenn commented 3 years ago

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 commented 3 years ago

Any updates?

syssam commented 3 years ago

Any updates?

qq326646683 commented 3 years ago

Any updates?

YeFei572 commented 2 years ago

Any updates?

pacifio commented 2 years ago

Update on this ?

3xscola commented 2 years ago

I think this should be taken into consideration.

DSPerson commented 2 years ago

Nice!!!

Update

const KeepAliveWrapper({Key? key, required this.child}) : super(key: key);
Asif-shah786 commented 2 years ago

Any updates on AutomaticKeepAliveClientMixin in getx ??

wildsurfer commented 2 years ago

+1 for this feature :)

leggod commented 1 year ago

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 commented 1 year ago

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 commented 6 months ago

Any updates ?