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

Using a key to avoid the problem of different Steps amounts generates another problem, that of the TextField losing focus every time something is typed. There's any solution? #112357

Closed
lohhans opened this issue Sep 25, 2022 · 11 comments
Labels
in triage Presently being triaged by the triage team waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds

Comments

@lohhans
Copy link

lohhans commented Sep 25, 2022

A solution is needed, because as commented in this answer, using a key to avoid the problem of different Steps amounts generates another problem, that of the TextField losing focus every time something is typed...

Give random key every time widget rebuild

Stepper(key: Key(Random.secure().nextDouble().toString())

I used this solution for some time, there is one problem though:

If there is a input field inside a Step and you try to input data, the keyboard immediately closes, probably as the key is regenerated due to changing viewport dimensions and therefore focus to the input element is lost.

Originally posted by @janhaa in #27187 (comment)

@lohhans lohhans changed the title > **A solution is needed, because as commented in this answer, using a key to avoid the problem of different Steps amounts generates another problem, that of the TextField losing focus every time something is typed...** Using a key to avoid the problem of different Steps amounts generates another problem, that of the TextField losing focus every time something is typed. Any solution? Sep 25, 2022
@lohhans lohhans changed the title Using a key to avoid the problem of different Steps amounts generates another problem, that of the TextField losing focus every time something is typed. Any solution? Using a key to avoid the problem of different Steps amounts generates another problem, that of the TextField losing focus every time something is typed. There's any solution? Sep 25, 2022
@lohhans
Copy link
Author

lohhans commented Sep 25, 2022

That's the error showed when a different amount of steps is given to the Stepper:

image

On line 338: assert(widget.steps.length == oldWidget.steps.length);
This try to assert to "1 == 2", causing the error...

The workaround given was:

Workaround

Give random key every time widget rebuild

Stepper(key: Key(Random.secure().nextDouble().toString())

Originally posted by @gersur in #27187 (comment)

But this causes the behavior mentioned in this issue, and already commented on in the previous issue, but no solution was given...

Simulator Screen Recording - iPhone 14 Pro Max - 2022-09-25 at 20 30 55
(Here you can see the focus loss behavior in the textfield)

@lohhans
Copy link
Author

lohhans commented Sep 26, 2022

flutter doctor -v
[✓] Flutter (Channel stable, 3.3.2, on macOS 12.5.1 21G83 darwin-x64, locale pt-BR)
    • Flutter version 3.3.2 on channel stable at /Users/armstrongquintino/fvm/versions/stable
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision e3c29ec00c (11 days ago), 2022-09-14 08:46:55 -0500
    • Engine revision a4ff2c53d8
    • Dart version 2.18.1
    • DevTools version 2.15.0

[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0-rc1)
    • Android SDK at /Users/armstrongquintino/Library/Android/sdk
    • Platform android-31, build-tools 33.0.0-rc1
    • ANDROID_HOME = /Users/armstrongquintino/Library/Android/sdk
    • ANDROID_SDK_ROOT = /Users/armstrongquintino/Library/Android/sdk
    • Java binary at: /Users/armstrongquintino/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/212.5712.43.2112.8815526/Android Studio.app/Contents/jre/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 14.0)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 14A309
    • CocoaPods version 1.11.3

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2021.2)
    • Android Studio at /Users/armstrongquintino/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/212.5712.43.2112.8815526/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840)

[✓] Android Studio (version 2021.2)
    • Android Studio at /Users/armstrongquintino/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/212.5712.43.2112.8609683/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840)

[✓] VS Code (version 1.71.2)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.48.0

[✓] Connected device (3 available)
    • iPhone 14 Pro Max (mobile) • FAAC4E85-28AF-466A-822A-0C61E4BBA099 • ios            • com.apple.CoreSimulator.SimRuntime.iOS-16-0 (simulator)
    • macOS (desktop)            • macos                                • darwin-x64     • macOS 12.5.1 21G83 darwin-x64
    • Chrome (web)               • chrome                               • web-javascript • Google Chrome 105.0.5195.125

[✓] HTTP Host Availability
    • All required HTTP hosts are available

• No issues found!

@darshankawar darshankawar added the in triage Presently being triaged by the triage team label Sep 26, 2022
@darshankawar
Copy link
Member

@lohhans
Can you provide complete minimal reproducible code sample that shows the error you are reporting ?
Also, when you open an issues, please don't use an existing comment as issue description, but use new issue template and fill out requested details so that we can address it in proper way.

@darshankawar darshankawar added the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label Sep 26, 2022
@lohhans
Copy link
Author

lohhans commented Sep 26, 2022

@darshankawar
Ok, sorry about that...

Here has a code sample who shows the behavior when UniqueKey() is used to avoid the another error mentioned...

main.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (context) => BothPagesController(),
        ),
      ],
      child: MaterialApp(
        title: 'Flutter - Stepper bug',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const SecondPage(title: 'Flutter - Stepper bug'),
      ),
    );
  }
}

class SecondPage extends StatefulWidget {
  const SecondPage({super.key, required this.title});
  final String title;

  @override
  State<SecondPage> createState() => _SecondPageState();
}

class _SecondPageState extends State<SecondPage> {
  int _index = 0;
  Map<String, TextEditingController> textEditingControllers = {};
  Map<String, TextField> textFields = {};
  late final BothPagesController controller;
  var listOne = ["A", "B", "C"];
  bool enableNextButton = false;

  @override
  void initState() {
    controller = context.read<BothPagesController>();

    // need to be on initState because receives diferents lists (its a form)
    listOne.forEach((string) {
      var aFieldController = TextEditingController(text: '');
      textEditingControllers.putIfAbsent(
        string,
        () => aFieldController,
      );
      textFields.putIfAbsent(string, () {
        return TextField(
          controller: aFieldController,
          onChanged: (value) => _validateFields,
        );
      });
    });
    super.initState();
  }

  // The validate with state changes makes the textfield loose focus when has UniqueKey
  get _validateFields {
    var isAllFilled = <bool>[];
    textEditingControllers.forEach((key, value) {
      isAllFilled.add(value.text.isNotEmpty);
    });
    if (mounted && isAllFilled.every((boolean) => boolean == true)) {
      setState(() {
        enableNextButton = true;
      });
    } else {
      setState(() {
        enableNextButton = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Flexible(
              child: Stepper(
                currentStep: _index,
                onStepCancel: () {
                  if (_index > 0) {
                    setState(() {
                      _index -= 1;
                    });
                  }
                },
                onStepContinue: () {
                  if (_index <= 0) {
                    setState(() {
                      _index += 1;
                    });
                  }
                },
                onStepTapped: (int index) {
                  setState(() {
                    _index = index;
                  });
                },
                type: StepperType.horizontal,
                steps: listOne
                    .map((string) => Step(
                          title: Text(string),
                          content: Container(
                            alignment: Alignment.centerLeft,
                            child: textFields[string],
                          ),
                        ))
                    .toList(),
                key:
                    UniqueKey(), // need to be unique to avoid metioned previous error (workaround citted)
              ),
            ),
            Flexible(
              child: ElevatedButton(
                // 3
                onPressed: enableNextButton
                    ? () {
                        print('Create');
                      }
                    : null,
                child: const Text('Click Me!'),
              ),
            ),
            const Text('(Enables if all textfields are filled)'),
          ],
        ),
      ),
    );
  }
}

// Example of structure used on real app
enum ControllerOfBothPagesState { idle, success, error, loading }

class BothPagesController extends ChangeNotifier {
  var state = ControllerOfBothPagesState.idle;
  GenericModel _genericModel = GenericModel(
    listData: [],
  );
  GenericModel get genericModel => _genericModel;
  void setExperimentRequestModel(
    GenericModel genericModel,
  ) {
    _genericModel = genericModel;
    notifyListeners();
  }
}

class GenericModel {
  List listData;

  GenericModel({required this.listData});
}

pubspec.yaml

name: stepper_bug
description: A new Flutter project.

version: 1.0.0+1

environment:
  sdk: '>=2.18.1 <3.0.0'

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.2
  provider: ^6.0.2

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_lints: ^2.0.0

flutter:
  uses-material-design: true

@github-actions github-actions bot removed the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label Sep 26, 2022
@darshankawar
Copy link
Member

Thanks for the details @lohhans
Does the same behavior occur without using provider library ? If so, please provide us updated code sample because we want to make sure that the error is not causing due to external library and to narrow it down to framework.

@darshankawar darshankawar added the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label Sep 27, 2022
@lohhans
Copy link
Author

lohhans commented Sep 27, 2022

Yes, the same happens without provider! See the code below...

main.dart

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter - Stepper bug',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const SecondPage(title: 'Flutter - Stepper bug'),
    );
  }
}

class SecondPage extends StatefulWidget {
  const SecondPage({super.key, required this.title});
  final String title;

  @override
  State<SecondPage> createState() => _SecondPageState();
}

class _SecondPageState extends State<SecondPage> {
  int _index = 0;
  Map<String, TextEditingController> textEditingControllers = {};
  Map<String, TextField> textFields = {};
  var listOne = ["A", "B", "C"];
  bool enableNextButton = false;

  late GenericModel genericModel;

  @override
  void initState() {
    genericModel = GenericModel(
      listData: [],
    );

    // need to be on initState because receives diferents lists (its a form)
    listOne.forEach((string) {
      var aFieldController = TextEditingController(text: '');
      textEditingControllers.putIfAbsent(
        string,
        () => aFieldController,
      );
      textFields.putIfAbsent(string, () {
        return TextField(
          controller: aFieldController,
          onChanged: (value) => _validateFields,
        );
      });
    });
    super.initState();
  }

  // The validate with state changes makes the textfield loose focus when has UniqueKey
  get _validateFields {
    var isAllFilled = <bool>[];
    textEditingControllers.forEach((key, value) {
      isAllFilled.add(value.text.isNotEmpty);
    });
    if (mounted && isAllFilled.every((boolean) => boolean == true)) {
      setState(() {
        enableNextButton = true;
      });
    } else {
      setState(() {
        enableNextButton = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Flexible(
              child: Stepper(
                currentStep: _index,
                onStepCancel: () {
                  if (_index > 0) {
                    setState(() {
                      _index -= 1;
                    });
                  }
                },
                onStepContinue: () {
                  if (_index <= 0) {
                    setState(() {
                      _index += 1;
                    });
                  }
                },
                onStepTapped: (int index) {
                  setState(() {
                    _index = index;
                  });
                },
                type: StepperType.horizontal,
                steps: listOne
                    .map((string) => Step(
                          title: Text(string),
                          content: Container(
                            alignment: Alignment.centerLeft,
                            child: textFields[string],
                          ),
                        ))
                    .toList(),
                key:
                    UniqueKey(), // need to be unique to avoid metioned previous error (workaround citted)
              ),
            ),
            Flexible(
              child: ElevatedButton(
                // 3
                onPressed: enableNextButton
                    ? () {
                        print('Create');
                      }
                    : null,
                child: const Text('Click Me!'),
              ),
            ),
            const Text('(Enables if all textfields are filled)'),
          ],
        ),
      ),
    );
  }
}

class GenericModel {
  List listData;

  GenericModel({required this.listData});
}

@github-actions github-actions bot removed the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label Sep 27, 2022
@darshankawar
Copy link
Member

Thanks for the details. Can you also provide exact steps to replicate the error ?

Screenshot 2022-09-28 at 11 46 34 AM

@darshankawar darshankawar added the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label Sep 28, 2022
@lohhans
Copy link
Author

lohhans commented Sep 28, 2022

Yes, now try to digit anything on textfield, the focus scope will unfocus the textfield, so, you can't type a word at once, like "flutter", it will be typed letter by letter instead of normal typing, try to do it and you'll see.

@github-actions github-actions bot removed the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label Sep 28, 2022
@darshankawar
Copy link
Member

Can you take a look at this and see if it helps ?

@darshankawar darshankawar added the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label Sep 29, 2022
@github-actions
Copy link

Without additional information, we are unfortunately not sure how to resolve this issue. We are therefore reluctantly going to close this bug for now.
If you find this problem please file a new issue with the same description, what happens, logs and the output of 'flutter doctor -v'. All system setups can be slightly different so it's always better to open new issues and reference the related ones.
Thanks for your contribution.

@github-actions
Copy link

github-actions bot commented Nov 3, 2022

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 3, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
in triage Presently being triaged by the triage team waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds
Projects
None yet
Development

No branches or pull requests

2 participants