Skip to content

Instantly share code, notes, and snippets.

@felangel
Created June 26, 2019 02:52
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save felangel/ada82ee2bfc9178b1a772a3ec4fa77bc to your computer and use it in GitHub Desktop.
Save felangel/ada82ee2bfc9178b1a772a3ec4fa77bc to your computer and use it in GitHub Desktop.
Flutter Bloc Stepper
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
abstract class StepperEvent extends Equatable {
StepperEvent([List props = const []]) : super(props);
}
class StepTapped extends StepperEvent {
final int step;
StepTapped({@required this.step}) : super([step]);
@override
String toString() => 'StepTapped { step: $step }';
}
class StepCancelled extends StepperEvent {
@override
String toString() => 'StepCancelled';
}
class StepContinue extends StepperEvent {
@override
String toString() => 'StepContinue';
}
class StepperState extends Equatable {
final int step;
final int maxSteps;
StepperState({
@required this.step,
@required this.maxSteps,
}) : super([step, maxSteps]);
StepperState copyWith({int step, int maxSteps}) {
return StepperState(
step: step ?? this.step,
maxSteps: maxSteps ?? this.maxSteps,
);
}
@override
String toString() => 'StepperState { step: $step, maxSteps: $maxSteps }';
}
class StepperBloc extends Bloc<StepperEvent, StepperState> {
final int maxSteps;
StepperBloc({@required this.maxSteps});
@override
StepperState get initialState => StepperState(step: 0, maxSteps: maxSteps);
@override
void onTransition(Transition<StepperEvent, StepperState> transition) {
super.onTransition(transition);
print(transition);
}
@override
Stream<StepperState> mapEventToState(StepperEvent event) async* {
if (event is StepTapped) {
yield currentState.copyWith(step: event.step);
} else if (event is StepCancelled) {
yield currentState.copyWith(
step: currentState.step - 1 >= 0 ? currentState.step - 1 : 0,
);
} else if (event is StepContinue) {
yield currentState.copyWith(
step: currentState.step + 1 < maxSteps ? currentState.step + 1 : 0,
);
}
}
}
void main() {
final List<Step> steps = [
Step(
title: Text("Step 1"),
content: Text("Hello!"),
isActive: true,
),
Step(
title: Text("Step 2"),
content: Text("World!"),
state: StepState.editing,
isActive: true,
),
Step(
title: Text("Step 3"),
content: Text("Hello World!"),
isActive: true,
),
];
runApp(
MaterialApp(
home: BlocProvider(
builder: (context) => StepperBloc(maxSteps: steps.length),
child: MyHome(steps: steps),
),
),
);
}
class MyHome extends StatelessWidget {
final List<Step> steps;
MyHome({Key key, @required this.steps}) : super(key: key);
@override
Widget build(BuildContext context) {
print('MyHome build');
final stepperBloc = BlocProvider.of<StepperBloc>(context);
return Scaffold(
appBar: AppBar(
title: Text('Flutter Bloc Stepper'),
),
body: Container(
child: BlocBuilder(
bloc: stepperBloc,
builder: (BuildContext context, StepperState state) {
return Stepper(
currentStep: state.step,
steps: steps,
type: StepperType.vertical,
onStepTapped: (step) {
stepperBloc.dispatch(StepTapped(step: step));
},
onStepCancel: () {
stepperBloc.dispatch(StepCancelled());
},
onStepContinue: () {
stepperBloc.dispatch(StepContinue());
},
);
},
),
),
);
}
}
@azimuthdeveloper
Copy link

It seems like it's maybe a good idea to break out the steps into a seperate list? I've seen this in a few examples so far

@felangel
Copy link
Author

I’m not sure what you mean. Can you provide some more detail?

@azimuthdeveloper
Copy link

I just mean, you're declaring the steps in a generic list, and then passing these steps into MyHome (which is a StatelessWidget). MyHome takes the list of steps and also gets the instance of the Bloc to use from runApp (when its invoked with the BlocProvider).

Previously, in relation to the steps, I was just declaring them in the stateless widget. But from the examples I've seen (like your examples above) it seems like its a better idea to put the steps somewhere else in a List of steps and then pass those into the Stepper. It's a good pattern.

Super brief backstory, I'm coming from Xamarin Forms. So I'm used to spinning up a new Page whenever I want to do anything and I found it very difficult to re-use components between Pages. The paradigm shift from "make new pages and draw things you want on each page", and, honestly, huge component re-use. I used to use Prism for MVVM so the idea of keeping the business logic seperate from the visual layer isn't new to me, but the state stuff is new. But flutter_bloc makes a lot of sense (and is just a great package all round). Plus learning Dart :D So if my questions are weird that's why - just on a huge journey to learning Flutter (And also architecting it well via Bloc). Thanks for all your help, it's been absolutely great so far.

@felangel
Copy link
Author

Thanks for clarifying! Yeah I definitely recommend separating things where it makes sense because, like you said, it helps with reuse and modularity.

That's awesome! Thanks so much for the positive feedback and don't hesitate to ask any more questions regarding the bloc library either by opening an issue or posting a question in the chat 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment