Skip to content
New issue

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

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

Already on GitHub? # to your account

How use : AutomaticKeepAliveClientMixin #11

Closed
vptcnt opened this issue Apr 28, 2020 · 6 comments
Closed

How use : AutomaticKeepAliveClientMixin #11

vptcnt opened this issue Apr 28, 2020 · 6 comments

Comments

@vptcnt
Copy link

vptcnt commented Apr 28, 2020

Hello,

I am new in Flutter and I was looking for an architecture model.
After having seen your tutorial, I have decided to try to implement the Stacked philosophy.

I use some training codes I made to implement Stacked. Everything is fine with the View and Viewmodel.
In one code I use, there is a Scaffold and a bottomNavigationBar with 4 pages.
Each page has a list, and to preserve the scroll position, they implement : AutomaticKeepAliveClientMixin

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage>
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;
  
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return ListView.builder(
      itemBuilder: (BuildContext context, int index) {
        return ListTile(
          title: Text("Home"),
          subtitle: Text("$index"),
        );
      },
    );
  }
}

The tutorial is : https://cantaspinar.com/persistent-bottom-navigation-bar-in-flutter/

How implement AutomaticKeepAliveClientMixin using Stacked? I have tried with
... extends ViewModelBuilderWidget with AutomaticKeepAliveClientMixin
but I have an error

Thanks for your help

@FilledStacks
Copy link
Contributor

Hi there,

What's the problem you're trying to solve?

@vptcnt
Copy link
Author

vptcnt commented Apr 29, 2020

I am trying to convert a classic StatefulWidget that uses AutomaticKeepAliveClientMixin (which allows to preserve the scroll position) , with the ViewModel structure.

For exemple : If we have Page1, Page2, Page3, Page4 based on the model (fetching data, and display a list) :

class Page1 extends StatefulWidget {
  @override
  _Page1State createState() => _Page1State();
}

class _Page1State extends State<Page1>
    with AutomaticKeepAliveClientMixin {.  // <!-- How to use it with Stacked ???

  List<dynamic> data = [];
  Future<List<dynamic>> _data;
  ScrollController _controller;

  @override
  bool get wantKeepAlive => true;

  @override
  void initState() {
    super.initState();
    _data = fetchData();
    _controller =
        ScrollController(initialScrollOffset: 0.0, keepScrollOffset: true);
    _controller.addListener(_scrollListener);
  }

  @override
  void dispose() {
    super.dispose();
    _controller.dispose();
  }

  Future<List<dynamic>> fetchData(int page) async {
    try {
      var response = await http.get('$URL');
        if (response.statusCode == 200) {
          setState(() {
            data(json.decode(response.body).map((m) => MyModel.fromJson(m)).toList());
          });
          return data;
        } else {  throw 'Data error'; }
    } on SocketException { throw 'No Internet connection'; }
    return data;
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return FutureBuilder<List<dynamic>>(
      future: data,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          if (snapshot.data.length == 0) return Container();
          return Column(
            children: <Widget>[
              Column(
                  children: snapshot.data.map((item) {
                final heroId = item.id.toString() + "-latest";
                return InkWell(
                  onTap: () {
                    Navigator.push(
                      context,
                      MaterialPageRoute(
                        builder: (context) => NewPage(item, heroId),
                      ),
                    );
                  },
                  child: displayMyDataItem(context, item, heroId),
                );
              }).toList())
            ],
          );
        } else if (articleSnapshot.hasError) {
          return Container();
        }
  }
}

and a MainPage with a bottom navigation bar :

class MainPage extends StatefulWidget {

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

class _MainPageState extends State<MainPage> {
  int _selectedIndex = 0;

  List<Widget> _pages = <Widget>[
    Page1(),
    Page2(),
    Page3(),
    Page4(),
  ];

  PageController pageController = PageController();

  void _onItemTapped(int index) {
    pageController.jumpToPage(index);
  }

  void _onPageChanged(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body:PageView(
            controller: pageController,
            onPageChanged: _onPageChanged,
            children: _pages,
            physics: NeverScrollableScrollPhysics(),
          ),
       bottomNavigationBar: BottomNavigationBar(
            items: <BottomNavigationBarItem>[
              BottomNavigationBarItem( title: Text('Page 1')),
              BottomNavigationBarItem( title: Text('Page 2')),
              BottomNavigationBarItem( title: Text('Page 3')),
              BottomNavigationBarItem( title: Text('Page 4')),
            ],
             currentIndex: _selectedIndex,
             onTap: _onItemTapped,
             type: BottomNavigationBarType.shifting),
      );
  }
}

If Page1, Page2, Page3, Page4 with the Stacked architecture :

class Page1Screen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ViewModelBuilder<Page1ViewModel>.reactive(.   // <-- Stacked !!! But no AutomaticKeepAliveClientMixin because it became a StatelessWidget

      viewModelBuilder: () => Page1ViewModel(),
      onModelReady: (model) => model.initialise(),
      builder: (context, model, child) => Scaffold(     
        body: Container(
          decoration: BoxDecoration(color: Colors.white70),
          child: SingleChildScrollView(
            controller: model.controller,
            scrollDirection: Axis.vertical,
            child: Column(
              children: <Widget>[
                displayData(model, model.data)
              ],
            ),
          ),
        ),
      ),
    );
  }

  Widget displayData(Future<List<dynamic>> data) {
    return SingleChildScrollView(
      scrollDirection: Axis.horizontal,
      child: FutureBuilder<List<dynamic>>(
        future: data,
        builder: (context, articleSnapshot) {
          if (articleSnapshot.hasData) {
            if (articleSnapshot.data.length == 0) return Container();
            return Row(
                children: articleSnapshot.data.map((item) {
              final heroId = item.id.toString() + "-featured";
              return InkWell(
                  onTap: () {
                    Navigator.push(
                      context,
                      MaterialPageRoute(
                          builder: (context) => NewPage(item, heroId),
                          ),
                    );
                  },
                  child: displayMyDataItem(context, item, heroId));
            }).toList());
          } else if (articleSnapshot.hasError) {
            return Container(
              alignment: Alignment.center,
              margin: EdgeInsets.fromLTRB(0, 60, 0, 0),
              width: MediaQuery.of(context).size.width,
              height: MediaQuery.of(context).size.height,
              child: Column(
                children: <Widget>[
                  Image.asset(
                    "assets/no-internet.png",
                    width: 250,
                  ),
                  Text("No Internet Connection."),
                  FlatButton.icon(
                    icon: Icon(Icons.refresh),
                    label: Text("Reload"),
                    onPressed: () {
                    },
                  )
                ],
              ),
            );
          }
          return Container(
              alignment: Alignment.center,
              width: MediaQuery.of(context).size.width,
              height: 280,
              child: Loading( indicator: BallBeatIndicator(),size: 60.0, color: Theme.of(context).accentColor));
        },
      ),
    );
  }

}

and same for MainPage...

It works, list are displayed .... but each time I click on a BottomNavigationBarItem, the list is refreshed, because I can't success to put the within AutomaticKeepAliveClientMixin which allows to keep the widget's state..

@FilledStacks
Copy link
Contributor

You can just make Page1Screen stateful. Stacked doesn't exempt you from using Stateful widgets. Because almost all state is manged in the viewmodel you just don't see it often but you can still use stateful widgets if you want to.

@vptcnt
Copy link
Author

vptcnt commented Apr 29, 2020

I will try it ;-)

@vptcnt
Copy link
Author

vptcnt commented Apr 29, 2020

Great ! It works !

class Page1Screen extends StatefulWidget {
  Page1Screen({Key key}) : super(key: key);

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

class _Page1ScreenState extends State<Page1Screen>
    with AutomaticKeepAliveClientMixin {

  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return ViewModelBuilder<ArticlesViewModel>.reactive(
      viewModelBuilder: () => ArticlesViewModel(),
      onModelReady: (model) => model.initialise(),
      builder: (context, model, child) => Scaffold(
      ...
      )
    );
  }
}

Thanks for your help!
Maybe an note or an exemple that it's still possible to use the StatefulWidget with Stacked.... There is no reference about it. So it's why I was a little bit lost.

@FilledStacks
Copy link
Contributor

Awesome! Happy it works.

I don't think that would be required since there's nothing saying you can ONLY use a stateless widget. Like everything else the ViewModelBuilder is a widget, so it can be used anywhere in flutter like every other widget. There's no limitation.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants