customize horizontal stepperblocbuilder
Hi everybody. Is there a way to hide the stepper spots on the top of the StepperBlocBuilder?
You can implement you own stepper layout by building your own layout by using the values (e.g currentStep, numberOfSteps, lastStep) from form bloc state. You can go through the code inside the StepperFormBlocBuilder and Stepper to give you some ideas.
Ok thank you
Il Gio 28 Apr 2022, 08:48 Jack Liu @.***> ha scritto:
You can implement you own stepper layout by building your own layout by using the values (e.g currentStep, numberOfSteps, lastStep) from form bloc state. You can go through the code inside the StepperFormBlocBuilder and Stepper to give you some ideas.
— Reply to this email directly, view it on GitHub https://github.com/GiancarloCode/form_bloc/issues/292#issuecomment-1111810601, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADEWB4NBQD4ZPBEQDP4VSSTVHIYEPANCNFSM5RKDLRDQ . You are receiving this because you authored the thread.Message ID: @.***>
I you need more help, please give us an example for the context of discussion.
The only thing I would like to acheive is to hide the horizontal stepper. I would like to use the stepper logic but without the header. For now I solve this issue by wrapping the StepperFormBlocBuilder inside a Stack and then I use an empty AppBar over the stepper header, but I think is not the best solution.

If you don't share any code. I don't know what difficulties still bothering you on custom layout build with flutter_form_bloc .
As we all know flutter_form_bloc is extended from flutter_bloc library. So all the flutter_form_bloc can be use with flutter_bloc. With limited information that provided by you. I can only do an example with you reference which is WizardForm.
Here's an example building custom layout with stepping mechanism. You can compare with the original version which is used on web.
import 'package:flutter/material.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';
void main() => runApp(const App());
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: WizardForm(),
);
}
}
class WizardFormBloc extends FormBloc<String, String> {
final username = TextFieldBloc(
validators: [FieldBlocValidators.required],
);
final email = TextFieldBloc<String>(
validators: [
FieldBlocValidators.required,
FieldBlocValidators.email,
],
);
final password = TextFieldBloc(
validators: [
FieldBlocValidators.required,
FieldBlocValidators.passwordMin6Chars,
],
);
final firstName = TextFieldBloc();
final lastName = TextFieldBloc();
final gender = SelectFieldBloc(
items: ['Male', 'Female'],
);
final birthDate = InputFieldBloc<DateTime?, Object>(
initialValue: null,
validators: [FieldBlocValidators.required],
);
final github = TextFieldBloc();
final twitter = TextFieldBloc();
final facebook = TextFieldBloc();
WizardFormBloc() {
addFieldBlocs(
step: 0,
fieldBlocs: [username, email, password],
);
addFieldBlocs(
step: 1,
fieldBlocs: [firstName, lastName, gender, birthDate],
);
addFieldBlocs(
step: 2,
fieldBlocs: [github, twitter, facebook],
);
}
bool _showEmailTakenError = true;
@override
void onSubmitting() async {
if (state.currentStep == 0) {
await Future.delayed(const Duration(milliseconds: 500));
if (_showEmailTakenError) {
_showEmailTakenError = false;
email.addFieldError('That email is already taken');
emitFailure();
} else {
emitSuccess();
}
} else if (state.currentStep == 1) {
emitSuccess();
} else if (state.currentStep == 2) {
await Future.delayed(const Duration(milliseconds: 500));
emitSuccess();
}
}
}
class WizardForm extends StatefulWidget {
const WizardForm({Key? key}) : super(key: key);
@override
_WizardFormState createState() => _WizardFormState();
}
class _WizardFormState extends State<WizardForm> {
final pageController = PageController(initialPage: 0);
@override
void dispose() {
pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => WizardFormBloc(),
child: Builder(
builder: (context) {
final wizardFormBloc = context.read<WizardFormBloc>();
return Theme(
data: Theme.of(context).copyWith(
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
),
),
),
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: const Text('Wizard'),
),
body: SafeArea(
child: MultiBlocListener(
listeners: [
FormBlocListener<WizardFormBloc, String, String>(
onSubmitting: (context, state) =>
LoadingDialog.show(context),
onSubmissionFailed: (context, state) =>
LoadingDialog.hide(context),
onSuccess: (context, state) {
LoadingDialog.hide(context);
if (state.stepCompleted == state.lastStep) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => const SuccessScreen()));
}
},
onFailure: (context, state) {
LoadingDialog.hide(context);
},
),
BlocListener<FormBloc<String, String>,
FormBlocState<String, String>>(
bloc: wizardFormBloc,
listenWhen: (previous, current) =>
previous.currentStep != current.currentStep,
listener: (context, state) =>
pageController.animateToPage(state.currentStep,
duration: const Duration(milliseconds: 500),
curve: Curves.easeIn),
),
],
child: BlocBuilder<FormBloc<String, String>,
FormBlocState<String, String>>(
bloc: wizardFormBloc,
builder: (context, state) {
return PageView(
physics: const NeverScrollableScrollPhysics(),
controller: pageController,
children: [
_buildStepOne(wizardFormBloc),
_buildStepTwo(wizardFormBloc),
_buildStepThree(wizardFormBloc),
],
);
},
),
),
),
),
);
},
),
);
}
Widget _buildStepOne(WizardFormBloc wizardFormBloc) =>
Column(
children: <Widget>[
TextFieldBlocBuilder(
textFieldBloc: wizardFormBloc.username,
keyboardType: TextInputType.emailAddress,
enableOnlyWhenFormBlocCanSubmit: true,
decoration: const InputDecoration(
labelText: 'Username',
prefixIcon: Icon(Icons.person),
),
),
TextFieldBlocBuilder(
textFieldBloc: wizardFormBloc.email,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
),
),
TextFieldBlocBuilder(
textFieldBloc: wizardFormBloc.password,
keyboardType: TextInputType.emailAddress,
suffixButton: SuffixButton.obscureText,
decoration: const InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock),
),
),
Row(
children: <Widget>[
TextButton(
onPressed: wizardFormBloc.submit,
child: const Text('Continue to Step 2'),
),
],
),
],
);
Widget _buildStepTwo(WizardFormBloc wizardFormBloc) =>
Column(
children: <Widget>[
TextFieldBlocBuilder(
textFieldBloc: wizardFormBloc.firstName,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'First Name',
prefixIcon: Icon(Icons.person),
),
),
TextFieldBlocBuilder(
textFieldBloc: wizardFormBloc.lastName,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Last Name',
prefixIcon: Icon(Icons.person),
),
),
RadioButtonGroupFieldBlocBuilder<String>(
selectFieldBloc: wizardFormBloc.gender,
itemBuilder: (context, value) => FieldItem(
child: Text(value),
),
decoration: const InputDecoration(
labelText: 'Gender',
prefixIcon: SizedBox(),
),
),
DateTimeFieldBlocBuilder(
dateTimeFieldBloc: wizardFormBloc.birthDate,
firstDate: DateTime(1900),
initialDate: DateTime.now(),
lastDate: DateTime.now(),
format: DateFormat('yyyy-MM-dd'),
decoration: const InputDecoration(
labelText: 'Date of Birth',
prefixIcon: Icon(Icons.cake),
),
),
Row(
children: <Widget>[
TextButton(
onPressed: wizardFormBloc.submit,
child: const Text('Continue to Step 3'),
),
TextButton(
onPressed: wizardFormBloc.previousStep,
child: const Text('Back to Step 1'),
),
],
),
],
);
Widget _buildStepThree(WizardFormBloc wizardFormBloc) =>
Column(
children: <Widget>[
TextFieldBlocBuilder(
textFieldBloc: wizardFormBloc.github,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Github',
prefixIcon: Icon(Icons.sentiment_satisfied),
),
),
TextFieldBlocBuilder(
textFieldBloc: wizardFormBloc.twitter,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Twitter',
prefixIcon: Icon(Icons.sentiment_satisfied),
),
),
TextFieldBlocBuilder(
textFieldBloc: wizardFormBloc.facebook,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Facebook',
prefixIcon: Icon(Icons.sentiment_satisfied),
),
),
Row(
children: <Widget>[
TextButton(
onPressed: wizardFormBloc.submit,
child: const Text('Submit'),
),
TextButton(
onPressed: wizardFormBloc.previousStep,
child: const Text('Back to Step 2'),
),
],
),
],
);
}
class LoadingDialog extends StatelessWidget {
static void show(BuildContext context, {Key? key}) => showDialog<void>(
context: context,
useRootNavigator: false,
barrierDismissible: false,
builder: (_) => LoadingDialog(key: key),
).then((_) => FocusScope.of(context).requestFocus(FocusNode()));
static void hide(BuildContext context) => Navigator.pop(context);
const LoadingDialog({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Center(
child: Card(
child: Container(
width: 80,
height: 80,
padding: const EdgeInsets.all(12.0),
child: const CircularProgressIndicator(),
),
),
),
);
}
}
class SuccessScreen extends StatelessWidget {
const SuccessScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(Icons.tag_faces, size: 100),
const SizedBox(height: 10),
const Text(
'Success',
style: TextStyle(fontSize: 54, color: Colors.black),
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
ElevatedButton.icon(
onPressed: () => Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const WizardForm())),
icon: const Icon(Icons.replay),
label: const Text('AGAIN'),
),
],
),
),
);
}
}
Thank you. This is exactly what I was looking for.
Il giorno ven 27 mag 2022 alle ore 17:42 Jack Liu @.***> ha scritto:
If you don't share any code. I don't know what difficulties still bothering you on custom layout build with flutter_form_bloc .
As we all know flutter_form_bloc is extended from flutter_bloc library. So all the flutter_form_bloc can be use with flutter_bloc. With limited information that provided by you. I can only do an example with you reference which is WizardForm.
Here's an example building custom layout with stepping mechanism. You can compare with the original version https://github.com/GiancarloCode/form_bloc/blob/eb7dacfd95f06b97b28e9526d42603dd2d4b8d7e/form_bloc_web/lib/examples/wizard_form.dart which is used on web.
import 'package:flutter/material.dart';import 'package:flutter_form_bloc/flutter_form_bloc.dart'; void main() => runApp(const App()); class App extends StatelessWidget { const App({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { return const MaterialApp( debugShowCheckedModeBanner: false, home: WizardForm(), ); } } class WizardFormBloc extends FormBloc<String, String> { final username = TextFieldBloc( validators: [FieldBlocValidators.required], );
final email = TextFieldBloc<String>( validators: [ FieldBlocValidators.required, FieldBlocValidators.email, ], );
final password = TextFieldBloc( validators: [ FieldBlocValidators.required, FieldBlocValidators.passwordMin6Chars, ], );
final firstName = TextFieldBloc();
final lastName = TextFieldBloc();
final gender = SelectFieldBloc( items: ['Male', 'Female'], );
final birthDate = InputFieldBloc<DateTime?, Object>( initialValue: null, validators: [FieldBlocValidators.required], );
final github = TextFieldBloc();
final twitter = TextFieldBloc();
final facebook = TextFieldBloc();
WizardFormBloc() { addFieldBlocs( step: 0, fieldBlocs: [username, email, password], ); addFieldBlocs( step: 1, fieldBlocs: [firstName, lastName, gender, birthDate], ); addFieldBlocs( step: 2, fieldBlocs: [github, twitter, facebook], ); }
bool _showEmailTakenError = true;
@override void onSubmitting() async { if (state.currentStep == 0) { await Future.delayed(const Duration(milliseconds: 500));
if (_showEmailTakenError) { _showEmailTakenError = false; email.addFieldError('That email is already taken'); emitFailure(); } else { emitSuccess(); } } else if (state.currentStep == 1) { emitSuccess(); } else if (state.currentStep == 2) { await Future.delayed(const Duration(milliseconds: 500)); emitSuccess(); }} } class WizardForm extends StatefulWidget { const WizardForm({Key? key}) : super(key: key);
@override _WizardFormState createState() => _WizardFormState(); } class _WizardFormState extends State<WizardForm> { final pageController = PageController(initialPage: 0);
@override void dispose() { pageController.dispose(); super.dispose(); }
@override Widget build(BuildContext context) { return BlocProvider( create: (context) => WizardFormBloc(), child: Builder( builder: (context) { final wizardFormBloc = context.read<WizardFormBloc>(); return Theme( data: Theme.of(context).copyWith( inputDecorationTheme: InputDecorationTheme( border: OutlineInputBorder( borderRadius: BorderRadius.circular(20), ), ), ), child: Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( title: const Text('Wizard'), ), body: SafeArea( child: MultiBlocListener( listeners: [ FormBlocListener<WizardFormBloc, String, String>( onSubmitting: (context, state) => LoadingDialog.show(context), onSubmissionFailed: (context, state) => LoadingDialog.hide(context), onSuccess: (context, state) { LoadingDialog.hide(context);
if (state.stepCompleted == state.lastStep) { Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) => const SuccessScreen())); } }, onFailure: (context, state) { LoadingDialog.hide(context); }, ), BlocListener<FormBloc<String, String>, FormBlocState<String, String>>( bloc: wizardFormBloc, listenWhen: (previous, current) => previous.currentStep != current.currentStep, listener: (context, state) => pageController.animateToPage(state.currentStep, duration: const Duration(milliseconds: 500), curve: Curves.easeIn), ), ], child: BlocBuilder<FormBloc<String, String>, FormBlocState<String, String>>( bloc: wizardFormBloc, builder: (context, state) { return PageView( physics: const NeverScrollableScrollPhysics(), controller: pageController, children: [ _buildStepOne(wizardFormBloc), _buildStepTwo(wizardFormBloc), _buildStepThree(wizardFormBloc), ], ); }, ), ), ), ), ); }, ), );}
Widget _buildStepOne(WizardFormBloc wizardFormBloc) => Column( children: <Widget>[ TextFieldBlocBuilder( textFieldBloc: wizardFormBloc.username, keyboardType: TextInputType.emailAddress, enableOnlyWhenFormBlocCanSubmit: true, decoration: const InputDecoration( labelText: 'Username', prefixIcon: Icon(Icons.person), ), ), TextFieldBlocBuilder( textFieldBloc: wizardFormBloc.email, keyboardType: TextInputType.emailAddress, decoration: const InputDecoration( labelText: 'Email', prefixIcon: Icon(Icons.email), ), ), TextFieldBlocBuilder( textFieldBloc: wizardFormBloc.password, keyboardType: TextInputType.emailAddress, suffixButton: SuffixButton.obscureText, decoration: const InputDecoration( labelText: 'Password', prefixIcon: Icon(Icons.lock), ), ), Row( children: <Widget>[ TextButton( onPressed: wizardFormBloc.submit, child: const Text('Continue to Step 2'), ), ], ), ], );
Widget _buildStepTwo(WizardFormBloc wizardFormBloc) => Column( children: <Widget>[ TextFieldBlocBuilder( textFieldBloc: wizardFormBloc.firstName, keyboardType: TextInputType.emailAddress, decoration: const InputDecoration( labelText: 'First Name', prefixIcon: Icon(Icons.person), ), ), TextFieldBlocBuilder( textFieldBloc: wizardFormBloc.lastName, keyboardType: TextInputType.emailAddress, decoration: const InputDecoration( labelText: 'Last Name', prefixIcon: Icon(Icons.person), ), ), RadioButtonGroupFieldBlocBuilder<String>( selectFieldBloc: wizardFormBloc.gender, itemBuilder: (context, value) => FieldItem( child: Text(value), ), decoration: const InputDecoration( labelText: 'Gender', prefixIcon: SizedBox(), ), ), DateTimeFieldBlocBuilder( dateTimeFieldBloc: wizardFormBloc.birthDate, firstDate: DateTime(1900), initialDate: DateTime.now(), lastDate: DateTime.now(), format: DateFormat('yyyy-MM-dd'), decoration: const InputDecoration( labelText: 'Date of Birth', prefixIcon: Icon(Icons.cake), ), ), Row( children: <Widget>[ TextButton( onPressed: wizardFormBloc.submit, child: const Text('Continue to Step 3'), ), TextButton( onPressed: wizardFormBloc.previousStep, child: const Text('Back to Step 1'), ), ], ), ], );
Widget buildStepThree(WizardFormBloc wizardFormBloc) => Column( children: <Widget>[ TextFieldBlocBuilder( textFieldBloc: wizardFormBloc.github, keyboardType: TextInputType.emailAddress, decoration: const InputDecoration( labelText: 'Github', prefixIcon: Icon(Icons.sentiment_satisfied), ), ), TextFieldBlocBuilder( textFieldBloc: wizardFormBloc.twitter, keyboardType: TextInputType.emailAddress, decoration: const InputDecoration( labelText: 'Twitter', prefixIcon: Icon(Icons.sentiment_satisfied), ), ), TextFieldBlocBuilder( textFieldBloc: wizardFormBloc.facebook, keyboardType: TextInputType.emailAddress, decoration: const InputDecoration( labelText: 'Facebook', prefixIcon: Icon(Icons.sentiment_satisfied), ), ), Row( children: <Widget>[ TextButton( onPressed: wizardFormBloc.submit, child: const Text('Submit'), ), TextButton( onPressed: wizardFormBloc.previousStep, child: const Text('Back to Step 2'), ), ], ), ], ); } class LoadingDialog extends StatelessWidget { static void show(BuildContext context, {Key? key}) => showDialog
( context: context, useRootNavigator: false, barrierDismissible: false, builder: ( ) => LoadingDialog(key: key), ).then((_) => FocusScope.of(context).requestFocus(FocusNode()));static void hide(BuildContext context) => Navigator.pop(context);
const LoadingDialog({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async => false, child: Center( child: Card( child: Container( width: 80, height: 80, padding: const EdgeInsets.all(12.0), child: const CircularProgressIndicator(), ), ), ), ); } } class SuccessScreen extends StatelessWidget { const SuccessScreen({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Icon(Icons.tag_faces, size: 100), const SizedBox(height: 10), const Text( 'Success', style: TextStyle(fontSize: 54, color: Colors.black), textAlign: TextAlign.center, ), const SizedBox(height: 10), ElevatedButton.icon( onPressed: () => Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (_) => const WizardForm())), icon: const Icon(Icons.replay), label: const Text('AGAIN'), ), ], ), ), ); } }
— Reply to this email directly, view it on GitHub https://github.com/GiancarloCode/form_bloc/issues/292#issuecomment-1139735267, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADEWB4ISPGARMQ35FAQKAD3VMDUOBANCNFSM5RKDLRDQ . You are receiving this because you authored the thread.Message ID: @.***>
I am using StepperFormBlocBuilder and my steppers are in vertical direction..but I am getting some empty space between each steppers..why is that?