Building an Expandable List Widget with Provider in Flutter
Expandable list widgets are a common and useful UI pattern, allowing users to reveal or hide detailed content without navigating to a new screen. They are excellent for FAQs, menus, or displaying categorized information efficiently. In Flutter, building such a widget with proper state management is crucial for performance and maintainability. This article will guide you through creating an expandable list using the Provider package, a simple yet powerful solution for state management.
Prerequisites
Before you begin, ensure you have Flutter installed and set up correctly. We will be using the provider package, so add it to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
provider: ^6.0.5 # Use the latest version
Run flutter pub get to fetch the new dependency.
1. Define the Data Model
First, let's create a simple data model for our list items. Each item will have an ID, a title, and content.
class Item {
final int id;
final String title;
final String content;
Item({required this.id, required this.title, required this.content});
}
2. Create the Provider for State Management
Next, we'll implement a ChangeNotifier that will manage the expansion state of our items. This provider will hold a list of all items and a Set of IDs for currently expanded items. When an item's expansion state changes, it will notify its listeners.
import 'package:flutter/foundation.dart';
class ExpandableListProvider extends ChangeNotifier {
// Our initial list of items
final List- _items = [
Item(id: 1, title: 'What is Flutter?', content: 'Flutter is an open-source UI software development kit created by Google. It is used to develop cross-platform applications from a single codebase.'),
Item(id: 2, title: 'Why use Provider?', content: 'Provider is a simple yet powerful state management solution in Flutter. It simplifies accessing and managing state across the widget tree.'),
Item(id: 3, title: 'How to install Flutter?', content: 'You can install Flutter by downloading the SDK from the official Flutter website and setting up your environment variables.'),
Item(id: 4, title: 'What is a Widget?', content: 'In Flutter, everything is a widget. Widgets are the basic building blocks of the UI. They describe how your app\'s view should look like with its current configuration and state.'),
];
// A set to keep track of item IDs that are currently expanded
final Set
_expandedItemIds = {};
// Getter to access the list of items
List- get items => _items;
// Method to check if an item is expanded
bool isExpanded(int itemId) => _expandedItemIds.contains(itemId);
// Method to toggle the expansion state of an item
void toggleExpansion(int itemId) {
if (_expandedItemIds.contains(itemId)) {
_expandedItemIds.remove(itemId);
} else {
_expandedItemIds.add(itemId);
}
notifyListeners(); // Notify all listening widgets to rebuild
}
}
3. Build the Expandable List Item Widget
Now, let's create a widget for a single expandable item. This widget will display the item's title and, when expanded, its content. It will interact with our ExpandableListProvider to toggle its state.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// Ensure your Item class and ExpandableListProvider are imported or defined in scope
// e.g., import '../models/item.dart';
// import '../providers/expandable_list_provider.dart';
class ExpandableListItem extends StatelessWidget {
final Item item;
const ExpandableListItem({Key? key, required this.item}) : super(key: key);
@override
Widget build(BuildContext context) {
// We use context.watch to listen to changes in ExpandableListProvider
// specifically to check if THIS item is expanded.
final bool isItemExpanded = context.watch().isExpanded(item.id);
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Column(
children: [
ListTile(
title: Text(item.title, style: const TextStyle(fontWeight: FontWeight.bold)),
trailing: Icon(
isItemExpanded ? Icons.expand_less : Icons.expand_more,
),
onTap: () {
// Use context.read to call methods that modify the state
// without causing the widget that calls it to rebuild.
context.read().toggleExpansion(item.id);
},
),
// Only show content if the item is expanded
if (isItemExpanded)
Padding(
padding: const EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 16.0),
child: Text(item.content),
),
],
),
);
}
}
4. Create the Main Expandable List Screen
Finally, we'll assemble our main screen. This screen will set up the ChangeNotifierProvider and display a ListView of our ExpandableListItem widgets.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// Ensure your Item class, ExpandableListProvider, and ExpandableListItem are imported
// e.g., import '../models/item.dart';
// import '../providers/expandable_list_provider.dart';
// import '../widgets/expandable_list_item.dart';
class ExpandableListScreen extends StatelessWidget {
const ExpandableListScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// context.watch is used here to get the list of items from the provider.
// If the _items list inside the provider were to change (e.g., dynamic loading),
// this widget would rebuild to reflect those changes.
final items = context.watch().items;
return Scaffold(
appBar: AppBar(
title: const Text('FAQ with Expandable List'),
),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ExpandableListItem(item: item);
},
),
);
}
}
5. Integrate with main.dart
To run the application, wrap your ExpandableListScreen with a ChangeNotifierProvider in your main.dart file. This makes your ExpandableListProvider instance available to all widgets below it in the widget tree.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// Import all your defined classes
// If you've organized them into folders, adjust paths accordingly:
// import 'models/item.dart';
// import 'providers/expandable_list_provider.dart';
// import 'widgets/expandable_list_item.dart';
// import 'screens/expandable_list_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
// The create callback initializes an instance of ExpandableListProvider
// when it's first accessed in the widget tree.
create: (context) => ExpandableListProvider(),
child: MaterialApp(
title: 'Expandable List Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: const ExpandableListScreen(),
),
);
}
}
Conclusion
By leveraging Flutter's Provider package, we've successfully built a clean, maintainable, and efficient expandable list widget. This approach elegantly separates state management logic from the UI, making your code easier to test, understand, and scale. The use of ChangeNotifier and notifyListeners() ensures that only the necessary parts of your UI rebuild when the state changes, contributing to a smooth user experience. You can extend this further by adding animations for expansion/collapse, dynamic data loading, or implementing multi-selection features based on the same principles.