ShadyBoukhary / flutter_clean_architecture

Clean architecture flutter: A Flutter package that makes it easy and intuitive to implement Uncle Bob's Clean Architecture in Flutter. This package provides basic classes that are tuned to work with Flutter and are designed according to the Clean Architecture.

Home Page:https://pub.dartlang.org/packages/flutter_clean_architecture

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Problem with AutomaticKeepAliveClientMixin

tusaamf opened this issue · comments

Please tell me how to keep state of view in a tabbar or navigation bottom bar, thank you. I try to implement with AutomaticKeepAliveClientMixin but it show error:

'To return an empty space that causes the building widget to fill available room, return "Container()". To return an empty space that takes as little room as possible, return "Container(width: 0.0, height: 0.0)".'

This seems like an issue with your UI code rather than the library. Where are you using the clean architecture? Perhaps share some code.

Sure, this is my code, the page use on TabBarView

class TheFirstPage extends View {
  final dynamic data;

  TheFirstPage({Key key, @required this.data}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _TheFirstPageView(data);
}

// error
class _TheFirstPageView extends ViewState<TheFirstPage, TheFirstController>
with AutomaticKeepAliveClientMixin<TheFirstPage> {
  _TheFirstPageView(dynamic data)
      : super(TheFirstController(SomeRepository(), data));

  @override
  bool get wantKeepAlive => true;

  @override
  void initState() {
    super.initState();
    print('_TheFirstPageView initState');
  }

  @override
  Widget build(BuildContext context) {
    print('_TheFirstPageView build');
    super.build(context);
    return super.build(context);
  }

  @override
  Widget buildPage() {
    print('_TheFirstPageView buildPage');
    return Scaffold(
        body: Container(...)
    );
  }
}

// normally work
class _TheFirstPageState extends State<ChallengeDetailsPage>
    with AutomaticKeepAliveClientMixin<ChallengeDetailsPage> {

  @override
  void initState() {
    super.initState();
    print('_TheFirstPageState initState');
  }

  @override
  Widget build(BuildContext context) {
    print('_TheFirstPageState build');
    super.build(context);
    return Scaffold(
        body: Container()
    );
  }
  @override
  bool get wantKeepAlive => true;
}

You're actually supposed to be using buildPage() instead of build() inside your ViewState as stated in the documentation. So just replace it and it should work. The library marks the build() function as nonvirtual, that means you should not be overriding it because it is used internally.

Do not override build() and use buildPage() for your UI such as

@override
  Widget buildPage() {
    print('_TheFirstPageState build');
    return Scaffold(
        body: Container()
    );

If you still have problem after this please let me know. Try this

class _TheFirstPageView extends ViewState<TheFirstPage, TheFirstController>
with AutomaticKeepAliveClientMixin<TheFirstPage> {
  _TheFirstPageView(dynamic data)
      : super(TheFirstController(SomeRepository(), data));

  @override
  bool get wantKeepAlive => true;

  @override
  void initState() {
    super.initState();
    print('_TheFirstPageView initState');
  }

  @override
  Widget buildPage() {
    print('_TheFirstPageView buildPage');
    return Scaffold(
        body: Container()
    );
  }
}

i try it but it show the same

image

i check on view.dart it actually wont call the super.build(context) like this sample https://github.com/diegoveloper/flutter-samples/blob/aa43501a8ee8799024a3422e39842329d20482c5/lib/persistent_tabbar/page2.dart

@override
  @nonVirtual
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Con>.value(
        value: _controller,
        child: Consumer<Con>(builder: (ctx, con, _) {
          _controller = con;
          return buildPage();
        }));
  }

What happens if you add

@override
  Widget build(BuildContext context) {
    return super.build(context);
  }

Can you check if either build or buildPage gets called? If that doesn't help, I can try to recreate the issue in a small application and see what is causing the issue.

I try to call super.build(context) on the build function of the Class that extends ViewState but it not work.

Okay, I will make a small app and test this on my end.

Any news, are you getting an error like me?

I am looking into it as we speak. Will get back to you shortly.

@tunt-04098,

I was able to replicate the issue on my end. I am reluctant to call this a bug, as this is an unfortunate side effect of how Dart works.

Mixins create interfaces on top of the base class. That means, if you were to extend a class with a method called foo() and mixin with a class that also has a method called foo(), only the Mixin's foo() method will be recognized, as the mixin becomes the proper super class of the child class.

Since both ViewState and AutomaticKeepAliveClientMixin override build(), only AutomaticKeepAliveClientMixin's build method is called, which means the library method won't be called and won't end up building your UI. That is the cause of the issue.

What that means for me, is that I will probably have to change the method name and replace it with a widget that users can use to wrap their widgets in their build method. Either that, or people would have to use the workaround below. While I eventually decide which route I am going to take, that is, either leave it for the user to work around it or modify the library in a less user-friendly way, you can go ahead and use the workaround below:

class TheFirstPage extends View {
  final dynamic data;

  TheFirstPage({Key key, @required this.data}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _TheFirstPageView(data);
}


class _TheFirstPageViewRename extends ViewState<TheFirstPage, TheFirstController> {
  _TheFirstPageViewRename(controller) : super(controller);
  Widget buildRename(context) => super.build(context);
  @override
  Widget buildPage() => null;
}
class _TheFirstPageView extends _TheFirstPageViewRename
with AutomaticKeepAliveClientMixin<TheFirstPage> {
  _TheFirstPageView(dynamic data)
      : super(TheFirstController(SomeRepository(), data));

  @override
  bool get wantKeepAlive => true;

  @override
  void initState() {
    super.initState();
    print('_TheFirstPageView initState');
    
  }

  @override
  Widget build(BuildContext context) {
    print('_TheFirstPageView build');
    super.build(context);
    return super.buildRename(context);
  }

  @override
  Widget buildPage() {
    print('_TheFirstPageView buildPage');
    return Scaffold(
        body: Container()
    );
  }
}

This simply renames the method so Flutter can differentiate between the 2. Specifically here:

class _TheFirstPageViewRename extends ViewState<TheFirstPage, TheFirstController> {
  _TheFirstPageViewRename(controller) : super(controller);
  Widget buildRename(context) => super.build(context);
  @override
  Widget buildPage() => null;
}

It's only a few added lines of code, yet it is still sub-optimal.

Let me know if you have any questions.
Thank you and happy coding!

Thank you for your help wish you many good things!