Skip to content

Instantly share code, notes, and snippets.

@felangel felangel/main.dart
Created Jun 26, 2019

Embed
What would you like to do?
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());
},
);
},
),
),
);
}
}
@lewcianci

This comment has been minimized.

Copy link

lewcianci commented Jun 27, 2019

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

This comment has been minimized.

Copy link
Owner Author

felangel commented Jun 27, 2019

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

@lewcianci

This comment has been minimized.

Copy link

lewcianci commented Jun 27, 2019

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

This comment has been minimized.

Copy link
Owner Author

felangel commented Jun 28, 2019

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
You can’t perform that action at this time.