-
-
Save felangel/ada82ee2bfc9178b1a772a3ec4fa77bc to your computer and use it in GitHub Desktop.
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()); | |
}, | |
); | |
}, | |
), | |
), | |
); | |
} | |
} |
I’m not sure what you mean. Can you provide some more detail?
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.
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 😄
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