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

Skipping a tab while switching among tabs in TabBarView #72984

Closed
AkbarBakhshi opened this issue Dec 27, 2020 · 3 comments
Closed

Skipping a tab while switching among tabs in TabBarView #72984

AkbarBakhshi opened this issue Dec 27, 2020 · 3 comments

Comments

@AkbarBakhshi
Copy link

AkbarBakhshi commented Dec 27, 2020

I'm using a simple tabBar in my flutter app. I borrowed the code from the flutter website and slightly updated to make sure that I can keep the state of each tab using AutomaticKeepAliveClientMixin.

Basically, I have 3 tabs and each tab is fetching a list of data (why I need to use AutomaticKeepAliveClientMixin) from my backend API. The problem is that when I switch between first and 3rd tabs (Page1 and Page3), the middle tab keeps rebuilding over and over again until I switch to that tab (Page2) and only at that point it doesn't get rebuilt anymore. Every rebuild results in fetching data from API and that's not desirable. Below, i have included a simplified code to reproduce this issue. You can see in the debug console once switching between 1st and 3rd tab (without switching to 2nd tab) that it keeps printing "p2" (in my real app, it keeps fetching data for the 2nd tab).

Is there a way to switch between tabs without other tabs in between being built/rebuilt?

Steps to Reproduce

Just copy and paste the code below, keep switching between the 1st and 3rd tab and keep an eye on debug console. You should see "p2" being printed every time.

import 'package:flutter/material.dart';

void main() {
  runApp(TabBarDemo());
}

class TabBarDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DefaultTabController(
        length: 3,
        child: Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              tabs: [
                Tab(icon: Icon(Icons.directions_car)),
                Tab(icon: Icon(Icons.directions_transit)),
                Tab(icon: Icon(Icons.directions_bike)),
              ],
            ),
            title: Text('Tabs Demo'),
          ),
          body: TabBarView(
            children: [
              Page1(),
              Page2(),
              Page3(),
            ],
          ),
        ),
      ),
    );
  }
}

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

class _Page1State extends State<Page1>
    with AutomaticKeepAliveClientMixin<Page1> {
  @override
  bool get wantKeepAlive => true;
  @override
  Widget build(BuildContext context) {
    super.build(context);
    print('p1');
    return Container(
      child: Center(
        child: Icon(Icons.directions_car),
      ),
    );
  }
}

class Page2 extends StatefulWidget {
  @override
  _Page2State createState() => _Page2State();
}

class _Page2State extends State<Page2>
    with AutomaticKeepAliveClientMixin<Page2> {
  @override
  bool get wantKeepAlive => true;
  @override
  Widget build(BuildContext context) {
    super.build(context);
    print('p2');
    return Container(
      child: Center(
        child: Icon(Icons.directions_transit),
      ),
    );
  }
}

class Page3 extends StatefulWidget {
  @override
  _Page3State createState() => _Page3State();
}

class _Page3State extends State<Page3>
    with AutomaticKeepAliveClientMixin<Page3> {
  @override
  bool get wantKeepAlive => true;
  @override
  Widget build(BuildContext context) {
    super.build(context);
    print('p3');
    return Container(
      child: Center(
        child: Icon(Icons.directions_bike),
      ),
    );
  }
}
@iapicca
Copy link
Contributor

iapicca commented Dec 27, 2020

@AkbarBakhshi
I believe this isn't a bug with flutter, but ultimately comes down to your implementation
please take a look at the sample I wrote for you,
maybe isn't the most elegant, but gives you an option

dartpad example

code sample
import 'package:flutter/material.dart';
import 'dart:async';

class FakeApi {
  Future<List<int>> call() async {
    print('calling api');
    await Future.delayed(const Duration(seconds: 3));
    return <int>[for (var i = 0; i < 100; ++i) i];
  }
}

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp() : super(key: const Key('MyApp'));

  @override
  Widget build(BuildContext context) => const MaterialApp(home: MyHomePage());
}

class MyHomePage extends StatelessWidget {
  const MyHomePage() : super(key: const Key('MyHomePage'));
  static const _icons = [
    Icon(Icons.directions_car),
    Icon(Icons.directions_transit),
    Icon(Icons.directions_bike),
  ];
  @override
  Widget build(BuildContext context) => DefaultTabController(
        length: _icons.length,
        child: Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              tabs: [for (final icon in _icons) Tab(icon: icon)],
            ),
            title: Text('Tabs Demo'),
          ),
          body: TabBarView(
            children: [
              Center(child: _icons[0]),
              StaggeredWidget(_icons[1]),
              Center(child: _icons[2]),
            ],
          ),
        ),
      );
}

class StaggeredWidget extends StatefulWidget {
  const StaggeredWidget(this.icon)
      : super(key: const ValueKey('StaggeredWidget'));
  final Icon icon;

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

class _StaggeredWidgetState extends State<StaggeredWidget> {
  Widget _child;

  Timer _timer;

  @override
  void initState() {
    super.initState();
    _timer = Timer(const Duration(milliseconds: 150), () {
      if (mounted) {
        setState(() => _child = MyApiWidget(widget.icon));
      }
    });
  }

  @override
  void dispose() {
    _timer.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => _child ?? widget.icon;
}

class MyApiWidget extends StatefulWidget {
  const MyApiWidget(this.icon, [Key key]) : super(key: key);
  final Icon icon;
  @override
  _MyApiWidgetState createState() => _MyApiWidgetState();
}

class _MyApiWidgetState extends State<MyApiWidget>
    with AutomaticKeepAliveClientMixin {
  final _api = FakeApi();
  @override
  Widget build(BuildContext context) {
    print('building `MyApiWidget`');
    super.build(context);
    return FutureBuilder<List<int>>(
      future: _api(),
      builder: (context, snapshot) => !snapshot.hasData
          ? const Center(child: CircularProgressIndicator())
          : snapshot.hasError
              ? const Center(child: Icon(Icons.error))
              : ListView.builder(
                  itemBuilder: (context, index) => ListTile(
                    title: Text('item $index'),
                  ),
                ),
    );
  }

  @override
  bool get wantKeepAlive => true;
}

if this approach doesn't fit your need please follow the link at I want help writing my application
in the new issue choice

@AkbarBakhshi
Copy link
Author

@iapicca
Your solution seems to be working just as I wanted. Thanks a lot for the help.

@github-actions
Copy link

github-actions bot commented Aug 8, 2021

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants