Building Dynamic Stepper Widgets in Flutter
Stepper widgets are a common UI pattern used to guide users through a series of sequential steps or a multi-stage process. In Flutter, the built-in Stepper widget provides a convenient way to implement such functionality. While the basic Stepper is straightforward, building one that dynamically adapts its steps based on data, user input, or business logic offers greater flexibility and reusability.
This article will guide you through creating a dynamic stepper in Flutter, covering how to manage step data, update the UI, and handle navigation efficiently.
Understanding Flutter's Stepper Widget
Before diving into dynamic behavior, let's briefly review the core properties of Flutter's Stepper widget:
steps: AList<Step>that defines all the steps in the stepper. EachStepobject requires atitleandcontent, and can optionally havesubtitle,isActive, andstateproperties.currentStep: An integer indicating the currently active step (0-indexed).onStepContinue: A callback function triggered when the "Continue" button is pressed.onStepCancel: A callback function triggered when the "Cancel" button is pressed.onStepTapped: A callback function triggered when a step header is tapped, allowing direct navigation to a specific step.type: Determines the orientation of the stepper, eitherStepperType.vertical(default) orStepperType.horizontal.controlsBuilder: A builder function to customize the navigation buttons (continue/cancel).
The key to making a stepper dynamic lies in how we populate the steps list and how we manage the currentStep in response to user interactions.
Making the Stepper Dynamic
A dynamic stepper means that the number of steps, their titles, content, or even the widgets they display, are not hardcoded but are generated from a data source. This data could come from:
- An API response.
- A local database.
- User configurations.
- A predefined list that can change based on conditions.
Here's the general approach:
- Define a Data Structure: Create a list of objects or maps that represent your steps. Each item in this list should contain all the necessary information for a single step (title, content, and potentially a widget to display).
- Use a
StatefulWidget: The stepper's state, specificallycurrentStep, needs to be managed, so aStatefulWidgetis essential. - Generate
List<Step>Dynamically: Map your data structure to aList<Step>that theStepperwidget can consume. - Update State on Navigation: Implement
onStepContinue,onStepCancel, andonStepTappedto update the_currentStepvariable withinsetState().
Implementation Example
Let's build a dynamic stepper that simulates a multi-step form where the steps are defined by a list of maps, and each step presents a different input field.
1. Set up the Project
Create a new Flutter project if you don't have one:
flutter create dynamic_stepper_demo
cd dynamic_stepper_demo
2. Create the Dynamic Stepper Widget
We'll create a StatefulWidget called DynamicStepperScreen to house our stepper logic.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Dynamic Stepper Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: DynamicStepperScreen(),
);
}
}
class DynamicStepperScreen extends StatefulWidget {
@override
_DynamicStepperScreenState createState() => _DynamicStepperScreenState();
}
class _DynamicStepperScreenState extends State {
int _currentStep = 0; // State variable to keep track of the current step
// Simulate dynamic data for steps.
// In a real application, this might come from an API or complex logic.
final List
Explanation of the Code:
_stepData: ThisList<Map<String, dynamic>>serves as our dynamic data source. Each map represents a step and contains atitle,content, and awidget. The `widget` key holds the specific UI element (e.g., aTextFieldor aTextwidget) for that step._getSteps(): This method iterates through_stepDatausingmap()and transforms each data item into a FlutterStepobject.isActiveis set to_currentStep >= index, meaning the current step and any subsequent steps are visually active.stateuses a conditional check to showStepState.completefor steps already passed,StepState.editingfor the current step, andStepState.indexedfor future steps.
onStepContinue: Increments_currentStep. If it's the last step, it triggers a "Form Submitted!" message (you would integrate your form submission logic here).onStepCancel: Decrements_currentStep, preventing it from going below 0.onStepTapped: Allows direct navigation to a tapped step by updating_currentStepto the index of the tapped step.controlsBuilder: We've customized the default buttons. The "NEXT" button becomes "SUBMIT" on the final step, and the "BACK" button is hidden on the first step.
Further Customization and Considerations
- Validation: For real-world forms, you would add validation logic within
onStepContinue. If validation fails, prevent_currentStepfrom advancing and display an error. You could also setStepState.errorfor specific steps. - Data Collection: In this example,
TextFields are present but their values aren't explicitly collected. You would typically useTextEditingControllers for each input field and store their values in a state management solution or a dedicated data model. - Different Widget Types: The
'widget'key in_stepDatacan hold any Flutter widget, allowing for highly flexible step content (e.g., dropdowns, sliders, image pickers). - Horizontal Stepper: Simply change
type: StepperType.verticaltotype: StepperType.horizontalfor a horizontal layout. - Custom Stepper Headers: You can customize the entire step display by not just setting the
titleandsubtitle, but by wrapping them in custom widgets.
Conclusion
Building dynamic stepper widgets in Flutter enhances the user experience by providing clear, sequential guidance through complex processes. By leveraging Flutter's StatefulWidget and dynamically generating the List<Step> from a data source, you can create highly adaptable and reusable stepper components. This approach ensures your UI remains flexible, easily scalable, and maintainable as your application's requirements evolve.