form_bloc icon indicating copy to clipboard operation
form_bloc copied to clipboard

Ability to provide TextEditingController

Open RaviKavaiya opened this issue 3 years ago • 6 comments

Hi @GiancarloCode , nice Library 👍

Can we have ability to give custom TextEditingController to the TextFieldBlocBuilder?? Actually, I want to use some of the controller's functionalities like text selection on tap, etc.

EDIT:

On the page: https://giancarlocode.github.io/form_bloc/#/ (tutorial of Simple, step-9) it is mentioned that we can use any widget we want to use. Can you provide an example for that???

RaviKavaiya avatar Sep 28 '20 05:09 RaviKavaiya

Did you manage to use "any" widget?

MatyasK avatar Oct 05 '20 19:10 MatyasK

Has anyone found a workaround for this? I also need this.

DuckMouse avatar Jun 03 '22 01:06 DuckMouse

@DuckMouse what are you trying to achieve?

aaassseee avatar Jun 03 '22 08:06 aaassseee

Hi @aaassseee,

I have a screen that handles some editing. On this widget, I have multiple fields and a Focusnode that keeps track of which field currently has focus. One of those fields, is a Narration field. Which if that field has focus, it needs to select all the text so that the user can type and automatically overwrite the current value

DuckMouse avatar Jun 06 '22 01:06 DuckMouse

@DuckMouse Currently, TextFieldBlocBuilder doesn't expose TextEditingController to public due to prevent unpredictable behavior. If controller is exposed to public, weird things may happened. For example, although the text field bloc value is 'sample', programmer can change the value by controller instead of passing data by bloc without affecting the bloc value. We need to block this behavior in the library point of view. However, there is a way to fulfill your needs. Due to bloc and layout is separated in this library, you can actually implement text field bloc by your own widget. Here's a simple example from simple form example : before

TextFieldBlocBuilder(
  textFieldBloc: loginFormBloc.email,
  keyboardType: TextInputType.emailAddress,
  autofillHints: const [
    AutofillHints.username,
  ],
  decoration: const InputDecoration(
    labelText: 'Email',
    prefixIcon: Icon(Icons.email),
  ),
),

after

BlocBuilder<TextFieldBloc, TextFieldBlocState>(
  bloc: loginFormBloc.email,
  builder: (context, state) {
    return TextField(
      controller: controller,
      keyboardType: TextInputType.emailAddress,
      autofillHints: const [
        AutofillHints.username,
      ],
      decoration: InputDecoration(
        labelText: 'Email',
        prefixIcon: const Icon(Icons.email),
        errorText: state.canShowError
            ? FieldBlocBuilder.defaultErrorBuilder(
                context,
                state.error!,
                loginFormBloc.email,
              )
            : null,
      ),
      onChanged: (value) {
        loginFormBloc.email.updateValue(value);
      },
    );
  },
),

Custom Textfield with bloc builder will let you get full control on TextEditingController but remember you need to know what you are doing.

Full code:

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 MaterialApp(
      debugShowCheckedModeBanner: false,
      home: LoginForm(),
    );
  }
}

class LoginFormBloc extends FormBloc<String, String> {
  final email = TextFieldBloc(
    validators: [
      FieldBlocValidators.required,
      FieldBlocValidators.email,
    ],
  );

  final password = TextFieldBloc(
    validators: [
      FieldBlocValidators.required,
    ],
  );

  final showSuccessResponse = BooleanFieldBloc();

  LoginFormBloc() {
    addFieldBlocs(
      fieldBlocs: [
        email,
        password,
        showSuccessResponse,
      ],
    );
  }

  @override
  void onSubmitting() async {
    debugPrint(email.value);
    debugPrint(password.value);
    debugPrint(showSuccessResponse.value.toString());

    await Future<void>.delayed(const Duration(seconds: 1));

    if (showSuccessResponse.value) {
      emitSuccess();
    } else {
      emitFailure(failureResponse: 'This is an awesome error!');
    }
  }
}

class LoginForm extends StatelessWidget {
  LoginForm({Key? key}) : super(key: key);
  final TextEditingController controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => LoginFormBloc(),
      child: Builder(
        builder: (context) {
          final loginFormBloc = context.read<LoginFormBloc>();

          return Scaffold(
            resizeToAvoidBottomInset: false,
            appBar: AppBar(title: const Text('Login')),
            body: FormBlocListener<LoginFormBloc, String, String>(
              onSubmitting: (context, state) {
                LoadingDialog.show(context);
              },
              onSubmissionFailed: (context, state) {
                LoadingDialog.hide(context);
              },
              onSuccess: (context, state) {
                LoadingDialog.hide(context);

                Navigator.of(context).pushReplacement(
                    MaterialPageRoute(builder: (_) => const SuccessScreen()));
              },
              onFailure: (context, state) {
                LoadingDialog.hide(context);

                ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text(state.failureResponse!)));
              },
              child: SingleChildScrollView(
                physics: const ClampingScrollPhysics(),
                child: AutofillGroup(
                  child: Column(
                    children: <Widget>[
                      BlocBuilder<TextFieldBloc, TextFieldBlocState>(
                        bloc: loginFormBloc.email,
                        builder: (context, state) {
                          return TextField(
                            controller: controller,
                            keyboardType: TextInputType.emailAddress,
                            autofillHints: const [
                              AutofillHints.username,
                            ],
                            decoration: InputDecoration(
                              labelText: 'Email',
                              prefixIcon: const Icon(Icons.email),
                              errorText: state.canShowError
                                  ? FieldBlocBuilder.defaultErrorBuilder(
                                      context,
                                      state.error!,
                                      loginFormBloc.email,
                                    )
                                  : null,
                            ),
                            onChanged: (value) {
                              loginFormBloc.email.updateValue(value);
                            },
                          );
                        },
                      ),
                      TextFieldBlocBuilder(
                        textFieldBloc: loginFormBloc.email,
                        keyboardType: TextInputType.emailAddress,
                        autofillHints: const [
                          AutofillHints.username,
                        ],
                        decoration: const InputDecoration(
                          labelText: 'Email',
                          prefixIcon: Icon(Icons.email),
                        ),
                      ),
                      TextFieldBlocBuilder(
                        textFieldBloc: loginFormBloc.password,
                        suffixButton: SuffixButton.obscureText,
                        autofillHints: const [AutofillHints.password],
                        decoration: const InputDecoration(
                          labelText: 'Password',
                          prefixIcon: Icon(Icons.lock),
                        ),
                      ),
                      SizedBox(
                        width: 250,
                        child: CheckboxFieldBlocBuilder(
                          booleanFieldBloc: loginFormBloc.showSuccessResponse,
                          body: Container(
                            alignment: Alignment.centerLeft,
                            child: const Text('Show success response'),
                          ),
                        ),
                      ),
                      ElevatedButton(
                        onPressed: loginFormBloc.submit,
                        child: const Text('LOGIN'),
                      ),
                    ],
                  ),
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

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: (_) => LoginForm())),
              icon: const Icon(Icons.replay),
              label: const Text('AGAIN'),
            ),
          ],
        ),
      ),
    );
  }
}

aaassseee avatar Jun 06 '22 12:06 aaassseee

@aaassseee Wow.. Thank you for the comprehensive response. I will get back to you on how I go with implementing this.

DuckMouse avatar Jun 07 '22 02:06 DuckMouse