Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to provide TextEditingController #180

Open
RaviKavaiya opened this issue Sep 28, 2020 · 6 comments
Open

Ability to provide TextEditingController #180

RaviKavaiya opened this issue Sep 28, 2020 · 6 comments

Comments

@RaviKavaiya
Copy link

RaviKavaiya commented Sep 28, 2020

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???

@MatyasK
Copy link

MatyasK commented Oct 5, 2020

Did you manage to use "any" widget?

@DuckMouse
Copy link

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

@aaassseee
Copy link
Contributor

@DuckMouse what are you trying to achieve?

@DuckMouse
Copy link

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

@aaassseee
Copy link
Contributor

aaassseee commented Jun 6, 2022

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

@DuckMouse
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants