From 924a8f2f448d27301831123a30ac0bf93516f344 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Wed, 14 Oct 2020 12:59:10 -0500 Subject: [PATCH] feat!: remove controller from builder (#2) --- example/lib/main.dart | 211 ++++++------------------------- example/lib/onboarding_flow.dart | 112 ++++++++++++++++ example/lib/profile_flow.dart | 169 +++++++++++++++++++++++++ lib/flow_builder.dart | 25 ++-- pubspec.yaml | 2 +- 5 files changed, 335 insertions(+), 184 deletions(-) create mode 100644 example/lib/onboarding_flow.dart create mode 100644 example/lib/profile_flow.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 821ef41..f5cedf1 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,7 +1,8 @@ import 'package:equatable/equatable.dart'; +import 'package:example/onboarding_flow.dart'; +import 'package:example/profile_flow.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flow_builder/flow_builder.dart'; void main() { EquatableConfig.stringify = kDebugMode; @@ -17,178 +18,46 @@ class Home extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Home')), - body: Center( - child: Builder( - builder: (context) { - return RaisedButton( - onPressed: () async { - final profile = await Navigator.of(context).push( - ProfileFlow.route(), - ); - Scaffold.of(context) - ..hideCurrentSnackBar() - ..showSnackBar(SnackBar(content: Text('$profile'))); - }, - child: const Text('Start'), - ); - }, - ), - ), - ); - } -} - -class ProfileFlow extends StatelessWidget { - static Route route() { - return MaterialPageRoute(builder: (_) => ProfileFlow()); - } - - @override - Widget build(BuildContext context) { - return FlowBuilder( - state: const Profile(), - builder: (context, profile, controller) { - return [ - MaterialPage(child: ProfileNameForm()), - if (profile.name != null) MaterialPage(child: ProfileAgeForm()), - if (profile.age != null) - MaterialPage(child: ProfileWeightForm()), - ]; - }, - ); - } -} - -class ProfileNameForm extends StatefulWidget { - @override - _ProfileNameFormState createState() => _ProfileNameFormState(); -} - -class _ProfileNameFormState extends State { - var _name = ''; - - void _continuePressed() { - context.flow().update((profile) => profile.copyWith(name: _name)); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Name')), - body: Center( - child: Column( - children: [ - TextField( - onChanged: (value) => setState(() => _name = value), - decoration: const InputDecoration( - labelText: 'Name', - hintText: 'John Doe', - ), - ), - RaisedButton( - child: const Text('Continue'), - onPressed: _name.isNotEmpty ? _continuePressed : null, - ) - ], - ), - ), - ); - } -} - -class ProfileAgeForm extends StatefulWidget { - @override - _ProfileAgeFormState createState() => _ProfileAgeFormState(); -} - -class _ProfileAgeFormState extends State { - int _age; - - void _continuePressed() { - context.flow().update((profile) => profile.copyWith(age: _age)); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Age')), - body: Center( - child: Column( - children: [ - TextField( - onChanged: (value) => setState(() => _age = int.parse(value)), - decoration: const InputDecoration( - labelText: 'Age', - hintText: '42', - ), - keyboardType: TextInputType.number, - ), - RaisedButton( - child: const Text('Continue'), - onPressed: _age != null ? _continuePressed : null, - ) - ], - ), - ), - ); - } -} - -class ProfileWeightForm extends StatefulWidget { - @override - _ProfileWeightFormState createState() => _ProfileWeightFormState(); -} - -class _ProfileWeightFormState extends State { - int _weight; - - void _continuePressed() { - context - .flow() - .complete((profile) => profile.copyWith(weight: _weight)); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Weight')), - body: Center( - child: Column( - children: [ - TextField( - onChanged: (value) => setState(() => _weight = int.parse(value)), - decoration: const InputDecoration( - labelText: 'Weight (lbs)', - hintText: '170', + body: Builder( + builder: (context) { + return ListView( + children: [ + ListTile( + leading: const Icon(Icons.help_outline), + title: const Text('Onboarding Flow'), + trailing: const Icon(Icons.chevron_right), + onTap: () async { + await Navigator.of(context).push(OnboardingFlow.route()); + Scaffold.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + const SnackBar( + content: Text('Onboarding Flow Complete!'), + ), + ); + }, ), - keyboardType: TextInputType.number, - ), - RaisedButton( - child: const Text('Continue'), - onPressed: _weight != null ? _continuePressed : null, - ) - ], - ), + ListTile( + leading: const Icon(Icons.person_outline), + title: const Text('Profile Flow'), + trailing: const Icon(Icons.chevron_right), + onTap: () async { + final profile = await Navigator.of(context).push( + ProfileFlow.route(), + ); + Scaffold.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar( + content: Text('Profile Flow Complete! $profile'), + ), + ); + }, + ) + ], + ); + }, ), ); } } - -class Profile extends Equatable { - const Profile({this.name, this.age, this.weight}); - - final String name; - final int age; - final int weight; - - Profile copyWith({String name, int age, int weight}) { - return Profile( - name: name ?? this.name, - age: age ?? this.age, - weight: weight ?? this.weight, - ); - } - - @override - List get props => [name, age, weight]; -} diff --git a/example/lib/onboarding_flow.dart b/example/lib/onboarding_flow.dart new file mode 100644 index 0000000..2e59e8b --- /dev/null +++ b/example/lib/onboarding_flow.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; +import 'package:flow_builder/flow_builder.dart'; + +enum OnboardingState { step1, step2, step3 } + +class OnboardingFlow extends StatelessWidget { + static Route route() { + return MaterialPageRoute(builder: (_) => OnboardingFlow()); + } + + @override + Widget build(BuildContext context) { + return FlowBuilder( + state: OnboardingState.step1, + builder: (context, state) { + return [ + MaterialPage( + child: Scaffold( + appBar: AppBar(), + body: const Center(child: Text('Onboarding Step 1')), + floatingActionButton: Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + FloatingActionButton( + heroTag: 0, + onPressed: () { + context.flow().complete((_) => null); + }, + child: const Icon(Icons.clear), + ), + const SizedBox(width: 8), + FloatingActionButton( + heroTag: 1, + onPressed: () { + context + .flow() + .update((_) => OnboardingState.step2); + }, + child: const Icon(Icons.arrow_forward_ios_rounded), + ), + ], + ), + ), + ), + if (state.index >= 1) + MaterialPage( + child: Scaffold( + appBar: AppBar(), + body: const Center(child: Text('Onboarding Step 2')), + floatingActionButton: Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + FloatingActionButton( + heroTag: 2, + onPressed: () { + context + .flow() + .update((_) => OnboardingState.step1); + }, + child: const Icon(Icons.arrow_back_ios_rounded), + ), + const SizedBox(width: 8), + FloatingActionButton( + heroTag: 3, + onPressed: () { + context + .flow() + .update((_) => OnboardingState.step3); + }, + child: const Icon(Icons.arrow_forward_ios_rounded), + ), + ], + ), + ), + ), + if (state.index >= 2) + MaterialPage( + child: Scaffold( + appBar: AppBar(), + body: const Center(child: Text('Onboarding Step 3')), + floatingActionButton: Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + FloatingActionButton( + heroTag: 4, + onPressed: () { + context + .flow() + .update((_) => OnboardingState.step2); + }, + child: const Icon(Icons.arrow_back_ios_rounded), + ), + const SizedBox(width: 8), + FloatingActionButton( + heroTag: 5, + onPressed: () { + context.flow().complete((_) => null); + }, + child: const Icon(Icons.check), + ), + ], + ), + ), + ), + ]; + }, + ); + } +} diff --git a/example/lib/profile_flow.dart b/example/lib/profile_flow.dart new file mode 100644 index 0000000..3c339f1 --- /dev/null +++ b/example/lib/profile_flow.dart @@ -0,0 +1,169 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flow_builder/flow_builder.dart'; + +class ProfileFlow extends StatelessWidget { + static Route route() { + return MaterialPageRoute(builder: (_) => ProfileFlow()); + } + + @override + Widget build(BuildContext context) { + return FlowBuilder( + state: const Profile(), + builder: (context, profile) { + return [ + MaterialPage(child: ProfileNameForm()), + if (profile.name != null) MaterialPage(child: ProfileAgeForm()), + if (profile.age != null) + MaterialPage(child: ProfileWeightForm()), + ]; + }, + ); + } +} + +class ProfileNameForm extends StatefulWidget { + @override + _ProfileNameFormState createState() => _ProfileNameFormState(); +} + +class _ProfileNameFormState extends State { + var _name = ''; + + void _continuePressed() { + context.flow().update((profile) => profile.copyWith(name: _name)); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Name')), + body: Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + TextField( + onChanged: (value) => setState(() => _name = value), + decoration: const InputDecoration( + labelText: 'Name', + hintText: 'John Doe', + ), + ), + RaisedButton( + child: const Text('Continue'), + onPressed: _name.isNotEmpty ? _continuePressed : null, + ) + ], + ), + ), + ), + ); + } +} + +class ProfileAgeForm extends StatefulWidget { + @override + _ProfileAgeFormState createState() => _ProfileAgeFormState(); +} + +class _ProfileAgeFormState extends State { + int _age; + + void _continuePressed() { + context.flow().update((profile) => profile.copyWith(age: _age)); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Age')), + body: Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + TextField( + onChanged: (value) => setState(() => _age = int.parse(value)), + decoration: const InputDecoration( + labelText: 'Age', + hintText: '42', + ), + keyboardType: TextInputType.number, + ), + RaisedButton( + child: const Text('Continue'), + onPressed: _age != null ? _continuePressed : null, + ) + ], + ), + ), + ), + ); + } +} + +class ProfileWeightForm extends StatefulWidget { + @override + _ProfileWeightFormState createState() => _ProfileWeightFormState(); +} + +class _ProfileWeightFormState extends State { + int _weight; + + void _continuePressed() { + context + .flow() + .complete((profile) => profile.copyWith(weight: _weight)); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Weight')), + body: Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + TextField( + onChanged: (value) { + setState(() => _weight = int.tryParse(value)); + }, + decoration: const InputDecoration( + labelText: 'Weight (lbs)', + hintText: '170', + ), + keyboardType: TextInputType.number, + ), + RaisedButton( + child: const Text('Continue'), + onPressed: _weight != null ? _continuePressed : null, + ) + ], + ), + ), + ), + ); + } +} + +class Profile extends Equatable { + const Profile({this.name, this.age, this.weight}); + + final String name; + final int age; + final int weight; + + Profile copyWith({String name, int age, int weight}) { + return Profile( + name: name ?? this.name, + age: age ?? this.age, + weight: weight ?? this.weight, + ); + } + + @override + List get props => [name, age, weight]; +} diff --git a/lib/flow_builder.dart b/lib/flow_builder.dart index 64ec780..e7bda9a 100644 --- a/lib/flow_builder.dart +++ b/lib/flow_builder.dart @@ -3,8 +3,7 @@ library flow_builder; import 'dart:collection'; import 'package:flutter/widgets.dart'; -typedef PageBuilder = List Function( - BuildContext, T, FlowController); +typedef PageBuilder = List Function(BuildContext, T); typedef Update = void Function(T Function(T)); @@ -34,12 +33,10 @@ class FlowBuilder extends StatefulWidget { class _FlowBuilderState extends State> { final _history = ListQueue(); T _state; - FlowController _controller; @override void initState() { super.initState(); - _controller = FlowController._(_update, _complete); _state = widget.state; _history.add(_state); } @@ -70,14 +67,18 @@ class _FlowBuilderState extends State> { return _FlowState( update: _update, complete: _complete, - child: Navigator( - pages: widget.builder(context, _state, _controller), - onPopPage: (route, dynamic result) { - if (_history.isNotEmpty) { - _history.removeLast(); - _state = _history.last; - } - return route.didPop(result); + child: Builder( + builder: (context) { + return Navigator( + pages: widget.builder(context, _state), + onPopPage: (route, dynamic result) { + if (_history.isNotEmpty) { + _history.removeLast(); + _state = _history.last; + } + return route.didPop(result); + }, + ); }, ), ); diff --git a/pubspec.yaml b/pubspec.yaml index 44ecc05..acd9fd6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter Flows made easy! A Flutter package which simplifies flows w repository: https://github.com/felangel/flow_builder homepage: https://github.com/felangel/flow_builder -version: 0.0.1-dev.1 +version: 0.0.1-dev.2 environment: sdk: ">=2.7.0 <3.0.0"