stepper widget with bloc

In previous article we’ve been discuss how to create Stepper in flutter, and now we will discuss how to use Stepper with Bloc for management state.

Table of Contents

What is Bloc?

Refers to freecodecamp,  The Business Logic Component (BLoC) pattern is a pattern created by Google and announced at Google I/O ’18. The BLoC pattern uses Reactive Programming to handle the flow of data within an app.

A BLoC stands as a middleman between a source of data in your app (e.g an API response) and widgets that need the data. It receives streams of events/data from the source, handles any required business logic and publishes streams of data changes to widgets that are interested in them.

Bloc is a state management for Flutter and very recommended by Google developers, it will helps you to managing state in your app and make access to your data.

There are so many management state available in flutter, for more detail information about Bloc you can visit here. Or if you want to use another state mangement you can learn here.

Oke, without any further due,

Let’s just started.….

First we need to add package to pubspec.yaml, add flutter_bloc: ^0.6.0 under tags dependencies:

name: stepper_content_widget_bloc
description: A new Flutter project.

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1

environment:
  sdk: ">=2.1.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  flutter_bloc: ^0.6.0

dev_dependencies:
  flutter_test:
    sdk: flutter

Bloc Stepper

Afterward, we create file StepperBloc.dart and import package bloc in the first line then we create class StepperState

import 'package:bloc/bloc.dart';
class StepperState {
  final int currentStep;
  final double rate;
  final double volume;

  const StepperState({this.currentStep, this.rate, this.volume});

  factory StepperState.initial() => StepperState(currentStep: 0);
}

In this StepperState we put every data that we want to handle in state, currentStep for handle step of Stepper, rate and volume to store data input value. You can put everything variable as you need to manage your state.

abstract class StepperEvent {
}

class ContinueEvent extends StepperEvent {}

class BackEvent extends StepperEvent {}

class SaveRateEvent extends StepperEvent  {
  final double rate;
  final double volume;

  SaveRateEvent({this.rate, this.volume});
}

Next we create abstract class StepperEvent and another extends to StepperEvent, this class responsible to handle every event in your app, in that event we have 3 event, ContinueEvent for handle next step, BackEvent for handle back step and SaveRateEvent for handle data input.

then we create class StepperClass and extends to Bloc class,

class StepperBloc extends Bloc<StepperEvent, StepperState> {
  void onContinue() {
    dispatch(ContinueEvent());
  }

  void onBack() {
    dispatch(BackEvent());
  }

  void onSaveRate(double rate, double volume) {
    dispatch(SaveRateEvent(rate: rate, volume: volume));
  }

  @override
  StepperState get initialState => StepperState.initial();

  @override
  Stream<StepperState> mapEventToState(StepperState state, StepperEvent event) async* {
    final _currentState = currentState;
    if (event is ContinueEvent) {
      yield StepperState(currentStep: _currentState.currentStep + 1, rate: _currentState.rate, volume: _currentState.volume);
    } else if (event is BackEvent) {
      yield StepperState(currentStep: _currentState.currentStep - 1, rate: _currentState.rate, volume: _currentState.volume);
    } else if (event is SaveRateEvent) {
      yield StepperState(currentStep: _currentState.currentStep, rate: event.rate, volume: event.volume);
    }
  }
}

In class StepperBloc extends to Bloc and store StepperEvent dan StepperState, we create method to call every event we created in Event class. In stream mapEventToState have yield that’s for add a value to output stream, it’s pretty much like return if you experienced create method with return value.

here’s full source of StepperBloc.dart

import 'package:bloc/bloc.dart';

class StepperBloc extends Bloc<StepperEvent, StepperState> {
  void onContinue() {
    dispatch(ContinueEvent());
  }

  void onBack() {
    dispatch(BackEvent());
  }

  void onSaveRate(double rate, double volume) {
    dispatch(SaveRateEvent(rate: rate, volume: volume));
  }

  @override
  StepperState get initialState => StepperState.initial();

  @override
  Stream<StepperState> mapEventToState(StepperState state, StepperEvent event) async* {
    final _currentState = currentState;
    if (event is ContinueEvent) {
      yield StepperState(currentStep: _currentState.currentStep + 1, rate: _currentState.rate, volume: _currentState.volume);
    } else if (event is BackEvent) {
      yield StepperState(currentStep: _currentState.currentStep - 1, rate: _currentState.rate, volume: _currentState.volume);
    } else if (event is SaveRateEvent) {
      yield StepperState(currentStep: _currentState.currentStep, rate: event.rate, volume: event.volume);
    }
  }
}

abstract class StepperEvent {
  
}

class ContinueEvent extends StepperEvent {}

class BackEvent extends StepperEvent {}

class SaveRateEvent extends StepperEvent  {
  final double rate;
  final double volume;

  SaveRateEvent({this.rate, this.volume});
}

class StepperState {
  final int currentStep;
  final double rate;
  final double volume;

  const StepperState({this.currentStep, this.rate, this.volume});

  factory StepperState.initial() => StepperState(currentStep: 0);
}

Next, let’s implement to widget.

In main.dart we add BlocProvider and call StepperBloc.dart in _HomePageState.

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    final bloc = StepperBloc();
    return BlocProvider(
      bloc: bloc,
      child: Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: StepperView(),
      ),
    );   
  }
}

here’s full source in main.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:stepper_content_widget_bloc/StepperView.dart';

import 'StepperBloc.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Stepper Content Widget with Bloc',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Stepper Content Widget with Bloc'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    final bloc = StepperBloc();
    return BlocProvider(
      bloc: bloc,
      child: Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: StepperView(),
      ),
    );  
  }
}

Widget Stepper

In StepperView.dart use BlocBuilder to call StepperBloc, and onStepCancel we call onBack event, and onStepContinue we call onContinue event from StepperBloc.

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:stepper_content_widget_bloc/StepOneView.dart';
import 'package:stepper_content_widget_bloc/StepTwoView.dart';

import 'StepperBloc.dart';

class StepperView extends StatefulWidget {
  StepperView({Key key}) : super(key: key);
  
  @override
  _StepperViewState createState() => new _StepperViewState();
}

class _StepperViewState extends State<StepperView> {
  
  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        stepperList(),
      ],
    );
  }
    
  Widget stepperList() { 
    final bloc = BlocProvider.of<StepperBloc>(context); 
    return BlocBuilder(
      bloc: bloc,
      builder: (BuildContext context, StepperState state) {
        return Container(
          child: Stepper(
            controlsBuilder: (BuildContext context, {VoidCallback onStepContinue, VoidCallback onStepCancel}) {
              return Padding(
                padding: EdgeInsets.all(8.0),
                child: SizedBox()
              );
            },
            
            steps: <Step>[
              Step(
                title: Text("Step 1"),
                content: new Wrap(
                  children:<Widget>[
                    StepOneView()
                  ],
                ),
                isActive: (state.currentStep == 0 ? true : false),
                state: (state.currentStep >= 1) ? StepState.complete : StepState.indexed,
              ),
              Step(
                title: Text("Step 2"),
                content: new Wrap(
                  children:<Widget>[
                    StepTwoView()
                    ],
                ),
                isActive: (state.currentStep == 1 ? true : false),
                state: (state.currentStep >= 2) ? StepState.complete : StepState.indexed,
              )
            ],
            currentStep: state.currentStep,
            type: StepperType.horizontal,
            onStepTapped: (step) {
              (step <= state.currentStep) ? bloc.onBack() : bloc.onContinue();
            },
            onStepCancel: () {
              bloc.onBack();
            },
            onStepContinue: () {
              bloc.onContinue();
            },
          ),
        );
      }
    );
  }  
}

Next StepOneView.dart this widget contain form input, and on the RaiseButton widget we call onSaveRate to store input value, and onContinue event for jump to next step.

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

import 'StepperBloc.dart';

class StepOneView extends StatefulWidget {
  StepOneView({Key key}) : super(key: key);
  
  @override
  _StepOneViewState createState() => _StepOneViewState();
}

class _StepOneViewState extends State<StepOneView>{
  final textControllerRate = TextEditingController(), 
        textControllerVolume = TextEditingController();

  @override
  Widget build(BuildContext context) {
    final bloc = BlocProvider.of<StepperBloc>(context); 
    return BlocBuilder(
      bloc: bloc,
      builder: (BuildContext context, StepperState state) {
        textControllerRate.text = (state.rate != null) ? state.rate.toString() : textControllerRate.text;
        textControllerVolume.text = (state.volume != null) ? state.volume.toString() : textControllerVolume.text;
        return Padding(
          padding: const EdgeInsets.all(10.0),
          child: Card(
            child: Column(
            children: <Widget>[
              new TextFormField(
                controller: textControllerRate,
                decoration: const InputDecoration(labelText: 'Rate'),
                keyboardType: TextInputType.number,
              ),
              new TextFormField(
                controller: textControllerVolume,
                decoration: const InputDecoration(labelText: 'Volume'),
                keyboardType: TextInputType.number,
              ),

              new Padding(
                padding: EdgeInsets.all(8.0),
                child: RaisedButton(
                  onPressed: () {
                    bloc.onSaveRate(double.parse(textControllerRate.text), double.parse(textControllerVolume.text));
                    bloc.onContinue();
                  },
                  color: Colors.blue,
                  textColor: Colors.white,
                  child: Text('Next'),
                ),
              )
            ],
          ),
          )
        );
      }
    );
  }
}

Last, widget StepTwoView.dart, this widget for showing state value from widget StepOneView.dart.

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

import 'StepperBloc.dart';

class StepTwoView extends StatefulWidget {
  StepTwoView({Key key}) : super(key: key);
  
  @override
  _StepTwoViewState createState() => _StepTwoViewState();
}

class _StepTwoViewState extends State<StepTwoView>{

  @override
  Widget build(BuildContext context) {
    final bloc = BlocProvider.of<StepperBloc>(context); 
    return BlocBuilder(
      bloc: bloc,
      builder: (BuildContext context, StepperState state) {
        return Padding(
          padding: const EdgeInsets.all(10.0),
          child: Card(
            child: Column(
            children: <Widget>[
              Text("Rate from step 1 : " + bloc.currentState.rate.toString()),
              Text("Volume from step 1 : " + bloc.currentState.volume.toString()),
            ],
          ),
        )
      );
    });
  }
}

Finally, our app is ready to start and when you run your project, the result will be like this.

flutter stepper with -bloc
flutter stepper

Congratulation, your project is done.

What you have learned from here:

  1. You learn about Stepper
  2. How to use Bloc as state management.
  3. How to use Stepper with Bloc

For full source code, you can get here, you can also see demo video here.

You can also learn more about Stepper by visit here

Stay tuned for more tutorial, you can check this to see all of flutter tutorials.

HAPPY SHARE.

Advertisements
Share: