Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter Switching to Tab Reloads Widgets and runs FutureBuilder

Tags:

flutter

dart

The issue:

I have 2 tabs using Default Tabs Controller, like so:

Widget build(BuildContext context) {     return DefaultTabController(       length: 2,       child: Scaffold(         drawer: Menu(),         appBar: AppBar(           title: Container(             child: Text('Dashboard'),           ),           bottom: TabBar(             tabs: <Widget>[               Container(                 padding: EdgeInsets.all(8.0),                 child: Text('Deals'),               ),               Container(                 padding: EdgeInsets.all(8.0),                 child: Text('Viewer'),               ),             ],           ),         ),         body: TabBarView(           children: <Widget>[             DealList(),             ViewersPage(),           ],         ),       ),     );   } } 

The DealList() is a StatefulWidget which is built like this:

Widget build(BuildContext context) {     return FutureBuilder(       future: this.loadDeals(),       builder: (BuildContext context, AsyncSnapshot snapshot) {         print('Has error: ${snapshot.hasError}');         print('Has data: ${snapshot.hasData}');         print('Snapshot data: ${snapshot.data}');         return snapshot.connectionState == ConnectionState.done             ? RefreshIndicator(                 onRefresh: showSomething,                 child: ListView.builder(                   physics: const AlwaysScrollableScrollPhysics(),                   itemCount: snapshot.data['deals'].length,                   itemBuilder: (context, index) {                     final Map deal = snapshot.data['deals'][index];                     print('A Deal: ${deal}');                     return _getDealItem(deal, context);                   },                 ),               )             : Center(                 child: CircularProgressIndicator(),               );       },     );   } } 

With the above, here's what happens whenever I switch back to the DealList() tab: It reloads.

Flutter

Is there a way to prevent re-run of the FutureBuilder when done once? (the plan is for user to use the RefreshIndicator to reload. So changing tabs should not trigger anything, unless explicitly done so by user.)

like image 407
KhoPhi Avatar asked Jul 07 '18 15:07

KhoPhi


People also ask

Why is FutureBuilder in flutter?

In Flutter, the FutureBuilder Widget is used to create widgets based on the latest snapshot of interaction with a Future. It is necessary for Future to be obtained earlier either through a change of state or change in dependencies.

How do you refresh a tab in flutter?

the code is something like this. class ListWidget extends StatefulWidget { final catID; ListWidget(this. catID); _ListWidgetState createState() => new _ListWidgetState(catID); } class _ListWidgetState extends State<ListWidget> { var catID; void initState() { super.

What is default tab controller in flutter?

DefaultTabController is an inherited widget that is used to share a TabController with a TabBar or a TabBarView. It's used when sharing an explicitly created TabController isn't convenient because the tab bar widgets are created by a stateless parent widget or by different parent widgets.


1 Answers

There are two issues here, the first:

When the TabController switches tabs, it unloads the old widget tree to save memory. If you want to change this behavior, you need to mixin AutomaticKeepAliveClientMixin to your tab widget's state.

class _DealListState extends State<DealList> with AutomaticKeepAliveClientMixin<DealList> {   @override   bool get wantKeepAlive => true;    @override   Widget build(BuildContext context) {     super.build(context); // need to call super method.     return /* ... */   } } 

The second issue is in your use of the FutureBuilder - If you provide a new Future to a FutureBuilder, it can't tell that the results would be the same as the last time, so it has to rebuild. (Remember that Flutter may call your build method up to once a frame).

return FutureBuilder(   future: this.loadDeals(), // Creates a new future on every build invocation.   /* ... */ ); 

Instead, you want to assign the future to a member on your State class in initState, and then pass this value to the FutureBuilder. The ensures that the future is the same on subsequent rebuilds. If you want to force the State to reload the deals, you can always create a method which reassigns the _loadingDeals member and calls setState.

Future<...> _loadingDeals;  @override void initState() {   _loadingDeals = loadDeals(); // only create the future once.   super.initState(); }  @override Widget build(BuildContext context) {   super.build(context); // because we use the keep alive mixin.   return new FutureBuilder(future: _loadingDeals, /* ... */); } 
like image 84
Jonah Williams Avatar answered Sep 18 '22 09:09

Jonah Williams