form_bloc icon indicating copy to clipboard operation
form_bloc copied to clipboard

customize horizontal stepperblocbuilder

Open bobosette opened this issue 3 years ago • 7 comments

Hi everybody. Is there a way to hide the stepper spots on the top of the StepperBlocBuilder?

Cattura

bobosette avatar Mar 22 '22 08:03 bobosette

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.

aaassseee avatar Apr 28 '22 06:04 aaassseee

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: @.***>

bobosette avatar Apr 28 '22 06:04 bobosette

I you need more help, please give us an example for the context of discussion.

aaassseee avatar Apr 28 '22 11:04 aaassseee

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.

image

bobosette avatar May 26 '22 13:05 bobosette

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'),
            ),
          ],
        ),
      ),
    );
  }
}

aaassseee avatar May 27 '22 15:05 aaassseee

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: @.***>

bobosette avatar May 27 '22 15:05 bobosette

I am using StepperFormBlocBuilder and my steppers are in vertical direction..but I am getting some empty space between each steppers..why is that?

NishithaRaiS avatar Oct 19 '23 05:10 NishithaRaiS