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

[General]: Improve focus behavior when validating a FormBuilderTextField when autovalidateMode is not set to AutovalidateMode.disabled #1462

Open
3 of 7 tasks
quochuyR opened this issue Jan 10, 2025 · 1 comment
Labels
bug Something isn't working

Comments

@quochuyR
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Package/Plugin version

10.0.0-dev.1

Platforms

  • Android
  • iOS
  • Linux
  • MacOS
  • Web
  • Windows

Flutter doctor

Flutter doctor
[√] Flutter (Channel stable, 3.27.1, on Microsoft Windows [Version 10.0.26100.2605], locale en-US)
[√] Windows Version (Installed version of Windows is version 10 or higher)
[√] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
[√] Chrome - develop for the web
[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.10.4)
[√] Android Studio (version 2024.1)
[√] VS Code (version 1.96.2)
[√] Connected device (5 available)
[√] Network resources

Minimal code example

Code sample
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
final RouteObserver<ModalRoute> routeObserver = RouteObserver<ModalRoute>();
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      navigatorObservers: [routeObserver],
      home: FormScreen(),
    );
  }
}


class FormScreen extends StatefulWidget {

  const FormScreen({super.key});

  @override
  State<FormScreen> createState() => _FormScreenState();
}

class _FormScreenState extends State<FormScreen> {
 

  @override
  Widget build(BuildContext context) {
    // final size = MediaQuery.of(context).size;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Form Builder Example'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: SingleChildScrollView(
          child: FormChild(),
        ),
      ),
    );
  }
}

class FormChild extends StatefulWidget {
  const FormChild({super.key});

  @override
  State<FormChild> createState() => _FormChildState();
}

class _FormChildState extends State<FormChild>  with ScopedGetItMixin  {

  final GlobalKey<FormBuilderState> _formBuilderKey = GlobalKey<FormBuilderState>();
  AutovalidateMode _autovalidateMode = AutovalidateMode.disabled;

  @override
  Widget build(BuildContext context) {
    return FormBuilder(
      key: _formBuilderKey,
      autovalidateMode: _autovalidateMode,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: Row(
              spacing: 10.0,
              children: [
                // Submit button
                ElevatedButton(
                  style: ButtonStyle(shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0)))),
                  onPressed: () {
                    FocusManager.instance.primaryFocus?.unfocus();
                  },
                  child: const Text('Unfocus'),
                ),
                ElevatedButton(
                  style: ButtonStyle(shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0)))),
                  onPressed: () {
                    final result =  _formBuilderKey.currentState?.saveAndValidate();
                    if(result != true && _autovalidateMode != AutovalidateMode.onUserInteraction){
                      setState(() {
                        _autovalidateMode = AutovalidateMode.onUserInteraction;
                      });
                    }
                  },
                  child: const Text('submit'),
                ),
                ElevatedButton(
                  style: ButtonStyle(shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0)))),
                  onPressed: () {
                    showModalBottomSheet(
                      context: context, 
                      isScrollControlled: true,
                      showDragHandle: true,
                      builder: (context) {
                        return SizedBox(
                          height: 500,
                          width: 400,
                          child: Column(
                            mainAxisSize: MainAxisSize.min,
                            children: [
                              FormBuilderTextField(
                                name: 'textfield2',
                                decoration: const InputDecoration(
                                  labelText: 'Enter text 1',
                                ),
                                validator: FormBuilderValidators.required(),
                              )
                            ],
                          ),
                        );
                      },
                    );
                  },
                  child: Text("Open bottom sheet"),
                ),
              ],
            ),
          ),
          // TextField 1
          FormBuilderTextField(
            name: 'textfield1',
            decoration: const InputDecoration(
              labelText: 'Enter text 1',
            ),
            validator: FormBuilderValidators.required(),
          ),
          const SizedBox(height: 20),
        ],
      ),
    );
  }
}

mixin ScopedGetItMixin<T extends StatefulWidget> on State<T> implements RouteAware {

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    routeObserver.subscribe(this, ModalRoute.of(context)!);
  }

  @override
  void dispose() {
    super.dispose();
    routeObserver.unsubscribe(this);
  }


  @override
  void didPop() {

  }
  
  @override
  void didPopNext() {
  }
  
  @override
  void didPush() {
  }
  
  @override
  void didPushNext() {
  }

}

Current Behavior

I've developed a mixin called ScopedGetItMixin for use with StatefulWidget that contains a FormBuilder. However, I've noticed that after the form calls saveAndValidate, tapping a button to open a showModalBottomSheet causes a FormBuilderTextField to unexpectedly regain focus, triggering the keyboard to appear.
The main cause might be ScopedGetItMixin, but at the moment, I’m not sure why using ScopedGetItMixin results in this behavior.

Expected Behavior

I hope the focus behavior of FormBuilderTextField gets fixed.

Steps To Reproduce

I will describe it again through the video below.

focus_textfield1.mp4

Aditional information

No response

@quochuyR quochuyR added the bug Something isn't working label Jan 10, 2025
@quochuyR
Copy link
Author

The second issue is related to the focus behavior of FormBuilderTextField.
It’s not possible to unfocus the FormBuilderTextField when the field has an error, if the parent widget uses MediaQuery.of(context)

import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: FormScreen(),
    );
  }
}


class FormScreen extends StatefulWidget {

  const FormScreen({super.key});

  @override
  State<FormScreen> createState() => _FormScreenState();
}

class _FormScreenState extends State<FormScreen> {
 

  @override
  Widget build(BuildContext context) {
    // Using it here causes the bug
    final size = MediaQuery.of(context).size;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Form Builder Example'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: SingleChildScrollView(
          child: FormChild(),
        ),
      ),
    );
  }
}

class FormChild extends StatefulWidget {
  const FormChild({super.key});

  @override
  State<FormChild> createState() => _FormChildState();
}

class _FormChildState extends State<FormChild>  {

  final GlobalKey<FormBuilderState> _formBuilderKey = GlobalKey<FormBuilderState>();
  AutovalidateMode _autovalidateMode = AutovalidateMode.disabled;

  @override
  Widget build(BuildContext context) {
    // Using it here will not cause the bug.
    // final size = MediaQuery.of(context).size;
    return FormBuilder(
      key: _formBuilderKey,
      autovalidateMode: _autovalidateMode,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: Row(
              spacing: 10.0,
              children: [
                // Submit button
                ElevatedButton(
                  style: ButtonStyle(shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0)))),
                  onPressed: () {
                    FocusManager.instance.primaryFocus?.unfocus();
                  },
                  child: const Text('Unfocus'),
                ),
                ElevatedButton(
                  style: ButtonStyle(shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0)))),
                  onPressed: () {
                    final result =  _formBuilderKey.currentState?.saveAndValidate();
                    if(result != true && _autovalidateMode != AutovalidateMode.onUserInteraction){
                      setState(() {
                        _autovalidateMode = AutovalidateMode.onUserInteraction;
                      });
                    }
                  },
                  child: const Text('submit'),
                ),
                
              ],
            ),
          ),
          // TextField 1
          FormBuilderTextField(
            name: 'textfield1',
            decoration: const InputDecoration(
              labelText: 'Enter text 1',
            ),
            validator: FormBuilderValidators.required(),
          ),
          const SizedBox(height: 20),
        ],
      ),
    );
  }
}

The behavior that causes the error is described in the video below.

focus_textfield2.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant