Say Goodbye to Annoying Rebuilds in Flutter’s PageView and TabView

Aakash Pamnani
4 min readMay 24, 2024

--

Have you noticed when using TabViewor PageView widgets in Flutter that move from one page to another causes the entire UI of the page to rebuild? This can be very annoying for users, especially if the page fetches data from the internet. Every time the user swipes, a loading indicator might appear, disrupting the user experience.

For example, consider a screen with TabView, where each tab loads data from the internet every time it is opened. This constant reloading can make the app feel sluggish and unresponsive.

import 'package:flutter/material.dart';

void main() async {
runApp(
const MaterialApp(
home: HomePage(),
),
);
}

class HomePage extends StatelessWidget {
const HomePage({super.key});

@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: const Text("Home Page"),
bottom: const TabBar(
tabs: [
Tab(text: "Tab 1"),
Tab(text: "Tab 2"),
Tab(text: "Tab 3"),
],
),
),
body: const TabBarView(
children: [
TabPage(),
TabPage(),
TabPage(),
],
),
),
);
}
}

class TabPage extends StatefulWidget {
const TabPage({super.key});

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

class _TabPageState extends State<TabPage> {
@override
Widget build(BuildContext context) {
return FutureBuilder<List<String>>(
future: fetchDataFromInternet(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapshot.data![index]),
);
},
);
}
},
);
}

Future<List<String>> fetchDataFromInternet() async {
// Simulate a network call
await Future.delayed(const Duration(seconds: 2));
return List.generate(10, (index) => 'Item $index');
}
}

To solve this issue, we have a hero class: AutomaticKeepAliveClientMixin.

What is AutomaticKeepAliveClientMixin?

AutomaticKeepAliveClientMixin is a mixin provided by Flutter that helps maintain the state of a widget, preventing it from being disposed of when it goes off-screen. By using this mixin, you can ensure that your TabView or PageView pages retain their state and do not rebuild unnecessarily.

In the official Flutter docs it is defined as

A mixin with convenience methods for clients of [AutomaticKeepAlive]. Used with [State] subclasses.
Subclasses must implement [wantKeepAlive], and their [build] methods must call super.build (though the return value should be ignored).
Then, whenever [wantKeepAlive]’s value changes (or might change), the subclass should call [updateKeepAlive].
The type argument T is the type of the [StatefulWidget] subclass of the [State] into which this class is being mixed.

How to Use AutomaticKeepAliveClientMixin

Here’s a step-by-step guide to using AutomaticKeepAliveClientMixin to keep your pages alive:

  1. Extend State with AutomaticKeepAliveClientMixin:
    Extend the state class of your widget with AutomaticKeepAliveClientMixin.
class _TabPageState extends State<TabPage>
with AutomaticKeepAliveClientMixin<TabPage>

2. Override wantKeepAlive:
Override the wantKeepAlive getter to return true.

@override
bool get wantKeepAlive => true;

3. Call super.build(context):
Ensure you call super.build(context) in the build method.

Here’s an example of how to implement it:

class TabPage extends StatefulWidget {
const TabPage({super.key});

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

class _TabPageState extends State<TabPage>
with AutomaticKeepAliveClientMixin<TabPage> {
@override
bool get wantKeepAlive => true;

@override
Widget build(BuildContext context) {
super.build(context); // Call super.build
return FutureBuilder<List<String>>(
future: fetchDataFromInternet(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapshot.data![index]),
);
},
);
}
},
);
}

Future<List<String>> fetchDataFromInternet() async {
// Simulate a network call
await Future.delayed(const Duration(seconds: 2));
return List.generate(10, (index) => 'Item $index');
}

In this example:

  • The TabPage widget fetches data from the internet.
  • The AutomaticKeepAliveClientMixin ensures that the state of TabPage is preserved when it goes off-screen.
  • The wantKeepAlive getter is overridden to return true, indicating that the widget should be kept alive.
  • super.build(context) is called within the build method to ensure the proper functioning of the mixin.

By using AutomaticKeepAliveClientMixin, you can significantly improve the user experience in your Flutter applications, especially when dealing with TabView and PageView widgets that fetch data from the internet. No more unnecessary loading indicators or sluggish transitions — just a smooth and responsive interface.

Conclusion

To make it easier and reduce code repetition you can create a custom widget KeepAlivePage and just wrap your pages inside this widget.

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

/// Widget to keep alive pages used in tab bar view or page view
class KeepAlivePage extends StatefulWidget {
const KeepAlivePage({
super.key,
required this.child,
this.keepAlive = false,
});
final Widget child;
final bool keepAlive;
@override
State<KeepAlivePage> createState() => _KeepAlivePageState();
}

class _KeepAlivePageState extends State<KeepAlivePage>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
return widget.child;
}

@override
bool get wantKeepAlive => widget.keepAlive || !kDebugMode;
}

I hope you found this article enjoyable! If you appreciate the information provided, you have the option to support me by Buying Me A Coffee! Your gesture would be greatly appreciated!

Follow Me

https://www.linkedin.com/in/aakashpamnani/

Thank you for taking the time to read this article. If you enjoyed it, feel free to explore more of my articles and consider following me for future updates. Your support is greatly appreciated!

--

--

Aakash Pamnani

Flutter, Java, developer. | Trying to explain concepts in the most straightforward way possible.