Say Goodbye to Annoying Rebuilds in Flutter’s PageView and TabView
Have you noticed when using TabView
or 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 callsuper.build
(though the return value should be ignored).
Then, whenever [wantKeepAlive]’s value changes (or might change), the subclass should call [updateKeepAlive].
The type argumentT
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:
- Extend State with
AutomaticKeepAliveClientMixin
:
Extend the state class of your widget withAutomaticKeepAliveClientMixin
.
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 ofTabPage
is preserved when it goes off-screen. - The
wantKeepAlive
getter is overridden to returntrue
, indicating that the widget should be kept alive. super.build(context)
is called within thebuild
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!