From 0ba73e2a5e77e7524ba4e1ff89cba0a85881a888 Mon Sep 17 00:00:00 2001 From: Stefan Schaller Date: Fri, 12 Jun 2026 16:32:18 +0200 Subject: [PATCH 1/6] remove onnextframe test --- .fvmrc | 4 +- analysis_options.yaml | 2 +- example/.gitignore | 2 + example/ios/Flutter/AppFrameworkInfo.plist | 2 - example/ios/Runner.xcodeproj/project.pbxproj | 6 +- .../xcshareddata/xcschemes/Runner.xcscheme | 3 + example/ios/Runner/AppDelegate.swift | 11 ++-- example/ios/Runner/Info.plist | 29 +++++++-- example/lib/main.dart | 13 +++- example/pubspec.lock | 64 +++++++++---------- lib/src/text_field/base_text_field.dart | 47 ++++++-------- pubspec.yaml | 2 +- test/text_field/base_text_field_test.dart | 2 +- 13 files changed, 109 insertions(+), 78 deletions(-) diff --git a/.fvmrc b/.fvmrc index 8f59eb5..084b2bc 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.22.2" -} \ No newline at end of file + "flutter": "3.41.9" +} diff --git a/analysis_options.yaml b/analysis_options.yaml index a8b8c42..4184332 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1 +1 @@ -include: package:tapped_lints/flutter.yaml +include: package:tapped_lints/flutter-3.29.yaml diff --git a/example/.gitignore b/example/.gitignore index 24476c5..6c31954 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 7c56964..391a902 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 12.0 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 2d5f6c6..cd7ed99 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -275,7 +275,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -352,7 +352,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -401,7 +401,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 5e31d3d..9c12df5 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 70693e4..c30b367 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -1,13 +1,16 @@ -import UIKit import Flutter +import UIKit -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } } diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 7f55346..01e816d 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -24,6 +26,29 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + FlutterSceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -43,9 +68,5 @@ UIViewControllerBasedStatusBarAppearance - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - diff --git a/example/lib/main.dart b/example/lib/main.dart index 70a56f3..1d3a279 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -30,11 +30,14 @@ class _ExampleAppState extends State { children: [ BaseTextField( text: _text, - onChanged: (value, source) { + onChanged: (value) { + + print("Changed to: $value"); + setState(() => _text = value); }, decoration: - const InputDecoration(label: Text("Enter some text")), + const InputDecoration(hintText: "Enter some text"), textStyle: const TextStyle(), ), const SizedBox(height: 25), @@ -44,6 +47,12 @@ class _ExampleAppState extends State { }, child: const Text("Add large text"), ), + MaterialButton( + onPressed: () { + setState(() => _text = ""); + }, + child: const Text("Remove text"), + ), MaterialButton( onPressed: () { setState(() => _text = "Short text."); diff --git a/example/pubspec.lock b/example/pubspec.lock index bfe1585..a4c5af3 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -21,26 +21,26 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" cupertino_icons: dependency: "direct main" description: @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" flutter: dependency: "direct main" description: flutter @@ -79,26 +79,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -111,26 +111,26 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.17.0" non_uniform_border: dependency: transitive description: @@ -143,15 +143,15 @@ packages: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: @@ -164,18 +164,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: @@ -203,10 +203,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.10" universal_platform: dependency: transitive description: @@ -219,10 +219,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -232,5 +232,5 @@ packages: source: hosted version: "14.2.5" sdks: - dart: ">=3.3.0 <4.0.0" + dart: ">=3.9.0-0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/lib/src/text_field/base_text_field.dart b/lib/src/text_field/base_text_field.dart index 9a96342..a7867bd 100644 --- a/lib/src/text_field/base_text_field.dart +++ b/lib/src/text_field/base_text_field.dart @@ -6,14 +6,12 @@ import 'package:tapped_toolkit/src/after_first_build/after_first_build_mixin.dar import 'package:tapped_toolkit/src/after_first_build/on_next_frame_extension.dart'; import 'package:universal_platform/universal_platform.dart'; -enum TextFieldSource { inside, outside } - typedef TextStyleMutation = TextStyle Function(TextStyle style); class BaseTextField extends StatefulWidget { final String text; - final void Function(String value, TextFieldSource source) onChanged; + final void Function(String value) onChanged; final void Function(String value)? onFieldSubmitted; final String? Function(String? value)? validator; final TextStyle Function(TextStyle)? textStyleMutator; @@ -256,25 +254,23 @@ class BaseTextFieldState extends State // We don't need anything whenever the change is from the textfield itself if (_textEditingController.text == text) return; - // this will be called, whenever a change comes from outside and we need to handle the changed event - onNextFrame(() { - if (text.isEmpty) { - _textEditingController.clear(); - } else { - final selection = _textEditingController.selection; - - final isNewTextSmaller = oldWidget.text.length > text.length; - _textEditingController.value = _textEditingController.value.copyWith( - text: text, - selection: isNewTextSmaller - ? TextSelection.collapsed(offset: text.length) - : selection.copyWith( - baseOffset: text.length, - extentOffset: text.length, - ), - ); - } - }); + // we need to wait until the next microtask + if (text.isEmpty) { + _textEditingController.clear(); + } else { + final selection = _textEditingController.selection; + + final isNewTextSmaller = oldWidget.text.length > text.length; + _textEditingController.value = _textEditingController.value.copyWith( + text: text, + selection: isNewTextSmaller + ? TextSelection.collapsed(offset: text.length) + : selection.copyWith( + baseOffset: text.length, + extentOffset: text.length, + ), + ); + } } } @@ -288,10 +284,9 @@ class BaseTextFieldState extends State _previousTextValue = value; - widget.onChanged( - value, - value == widget.text ? TextFieldSource.outside : TextFieldSource.inside, - ); + if (value != widget.text) { + widget.onChanged(value); + } // we need to wait until the next microtask await Future.delayed(const Duration()); diff --git a/pubspec.yaml b/pubspec.yaml index d04214b..7bf4eac 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.1 + flutter_lints: ^6.0.0 tapped_lints: git: url: https://github.com/tappeddev/tapped_lints.git diff --git a/test/text_field/base_text_field_test.dart b/test/text_field/base_text_field_test.dart index 8345e25..8c7c13d 100644 --- a/test/text_field/base_text_field_test.dart +++ b/test/text_field/base_text_field_test.dart @@ -64,7 +64,7 @@ class _ScreenState extends State<_TestScreen> { child: BaseTextField( key: textFieldKey, text: text, - onChanged: (newText, source) { + onChanged: (newText) { setState(() => text = newText); }, decoration: const InputDecoration(), From 5094fa823060c32aa37e913179ada2001bffa91b Mon Sep 17 00:00:00 2001 From: Stefan Schaller Date: Fri, 12 Jun 2026 17:12:36 +0200 Subject: [PATCH 2/6] remove onnextframe test --- example/lib/main.dart | 100 +++++++++++++----------- lib/src/text_field/base_text_field.dart | 10 ++- 2 files changed, 62 insertions(+), 48 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 1d3a279..2d491cc 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -15,56 +15,68 @@ class ExampleApp extends StatefulWidget { class _ExampleAppState extends State { var _text = ""; + final _formKey = GlobalKey(); @override Widget build(BuildContext context) { return MaterialApp( - home: Builder( - builder: (context) { - return Scaffold( - appBar: AppBar(), - body: Container( - padding: const EdgeInsets.all(10.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - BaseTextField( - text: _text, - onChanged: (value) { + home: Form( + key: _formKey, + child: Builder( + builder: (context) { + return Scaffold( + appBar: AppBar(), + body: Container( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BaseTextField( + text: _text, + validator: (value) { + if ((value?.length ?? 0) < 2) return "Enter text"; - print("Changed to: $value"); - - setState(() => _text = value); - }, - decoration: - const InputDecoration(hintText: "Enter some text"), - textStyle: const TextStyle(), - ), - const SizedBox(height: 25), - MaterialButton( - onPressed: () { - setState(() => _text = "This is a really long text..."); - }, - child: const Text("Add large text"), - ), - MaterialButton( - onPressed: () { - setState(() => _text = ""); - }, - child: const Text("Remove text"), - ), - MaterialButton( - onPressed: () { - setState(() => _text = "Short text."); - }, - child: const Text("Add small text"), - ), - const DropDownExample(numberOfEntries: 25), - ], + return null; + }, + onChanged: (value) { + setState(() => _text = value); + }, + decoration: + const InputDecoration(hintText: "Enter some text"), + textStyle: const TextStyle(), + ), + const SizedBox(height: 25), + MaterialButton( + onPressed: () { + _formKey.currentState!.validate(); + }, + child: const Text("Validate"), + ), + MaterialButton( + onPressed: () { + setState(() => _text = "This is a really long text..."); + }, + child: const Text("Add large text"), + ), + MaterialButton( + onPressed: () { + setState(() => _text = ""); + }, + child: const Text("Remove text"), + ), + MaterialButton( + onPressed: () { + setState(() => _text = "Short text."); + }, + child: const Text("Add small text"), + ), + const DropDownExample(numberOfEntries: 25), + ], + ), ), - ), - ); - }, + ); + }, + ), ), ); } diff --git a/lib/src/text_field/base_text_field.dart b/lib/src/text_field/base_text_field.dart index a7867bd..eb079d6 100644 --- a/lib/src/text_field/base_text_field.dart +++ b/lib/src/text_field/base_text_field.dart @@ -250,10 +250,12 @@ class BaseTextFieldState extends State final text = widget.text; - if (oldWidget.text != text) { - // We don't need anything whenever the change is from the textfield itself - if (_textEditingController.text == text) return; + if (oldWidget.text == text) return; + // We don't need anything whenever the change is from the textfield itself + if (_textEditingController.text == text) return; + + Future.microtask(() { // we need to wait until the next microtask if (text.isEmpty) { _textEditingController.clear(); @@ -271,7 +273,7 @@ class BaseTextFieldState extends State ), ); } - } + }); } void validate() { From eee7354472d904366d183cb0c050f9bc57088e44 Mon Sep 17 00:00:00 2001 From: Stefan Schaller Date: Fri, 12 Jun 2026 17:32:55 +0200 Subject: [PATCH 3/6] remove onnextframe test --- lib/src/text_field/base_text_field.dart | 27 +++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/src/text_field/base_text_field.dart b/lib/src/text_field/base_text_field.dart index eb079d6..0b7bef1 100644 --- a/lib/src/text_field/base_text_field.dart +++ b/lib/src/text_field/base_text_field.dart @@ -255,7 +255,18 @@ class BaseTextFieldState extends State // We don't need anything whenever the change is from the textfield itself if (_textEditingController.text == text) return; - Future.microtask(() { + // External updates of [text] are propagated to the internal + // [TextEditingController] on the next frame (via `addPostFrameCallback`), + // not synchronously. This is required because mutating + // `TextEditingController.value` calls `notifyListeners()`, and Flutter's own + // listeners on that controller — most notably the surrounding [Form] / + // [FormField] and the internal `EditableText` — react by calling + // `markNeedsBuild`. Doing that during the build phase (which is exactly when + // `didUpdateWidget` runs) throws + // `setState() or markNeedsBuild() called during build`. Deferring the update + // to the next frame ensures the framework is out of the build phase before + // the controller is mutated. + WidgetsBinding.instance.addPostFrameCallback((_) { // we need to wait until the next microtask if (text.isEmpty) { _textEditingController.clear(); @@ -291,13 +302,13 @@ class BaseTextFieldState extends State } // we need to wait until the next microtask - await Future.delayed(const Duration()); - - // ⚠️ validate after the new input is attached - // we always want to validate the new input when the current state is invalid - if (!_textFieldIsValid) { - _formFieldKey.currentState?.validate(); - } + WidgetsBinding.instance.addPostFrameCallback((_) { + // ⚠️ validate after the new input is attached + // we always want to validate the new input when the current state is invalid + if (!_textFieldIsValid) { + _formFieldKey.currentState?.validate(); + } + }); } void _addFocusNodeListener() { From 050a48c10fb495239c7f844dbd851cacdb605372 Mon Sep 17 00:00:00 2001 From: Stefan Schaller Date: Fri, 12 Jun 2026 18:01:35 +0200 Subject: [PATCH 4/6] remove onnextframe test --- lib/src/custom_drop_down/custom_drop_down.dart | 4 ++-- lib/src/text_field/base_text_field.dart | 4 ++-- test/text_field/base_text_field_test.dart | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/src/custom_drop_down/custom_drop_down.dart b/lib/src/custom_drop_down/custom_drop_down.dart index 5a601b7..d07398a 100644 --- a/lib/src/custom_drop_down/custom_drop_down.dart +++ b/lib/src/custom_drop_down/custom_drop_down.dart @@ -56,8 +56,8 @@ class CustomDropDown extends StatefulWidget { this.autoValidate = false, this.itemHeight = 48.0, this.cornerRadius = 10, - Key? key, - }) : super(key: key); + super.key, + }); @override CustomDropDownState createState() => CustomDropDownState(); diff --git a/lib/src/text_field/base_text_field.dart b/lib/src/text_field/base_text_field.dart index 0b7bef1..d516176 100644 --- a/lib/src/text_field/base_text_field.dart +++ b/lib/src/text_field/base_text_field.dart @@ -101,8 +101,8 @@ class BaseTextField extends StatefulWidget { this.cursorOpacityAnimates, this.onTap, this.enabled, - Key? key, - }) : super(key: key); + super.key, + }); @override BaseTextFieldState createState() => BaseTextFieldState(); diff --git a/test/text_field/base_text_field_test.dart b/test/text_field/base_text_field_test.dart index 8c7c13d..9664c08 100644 --- a/test/text_field/base_text_field_test.dart +++ b/test/text_field/base_text_field_test.dart @@ -37,9 +37,9 @@ class _TestScreen extends StatefulWidget { final String initialTextFieldText; const _TestScreen({ - Key? key, + super.key, this.initialTextFieldText = "", - }) : super(key: key); + }); @override State<_TestScreen> createState() => _ScreenState(); From f2afdf283bab74575de6ceae338b9e71abec7d00 Mon Sep 17 00:00:00 2001 From: Stefan Schaller Date: Mon, 15 Jun 2026 07:13:56 +0200 Subject: [PATCH 5/6] add mounted check --- lib/src/text_field/base_text_field.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/src/text_field/base_text_field.dart b/lib/src/text_field/base_text_field.dart index d516176..41dfc20 100644 --- a/lib/src/text_field/base_text_field.dart +++ b/lib/src/text_field/base_text_field.dart @@ -248,8 +248,10 @@ class BaseTextFieldState extends State void didUpdateWidget(covariant BaseTextField oldWidget) { super.didUpdateWidget(oldWidget); + // the new text, which was provided from the widget final text = widget.text; + // in case the text didn't change, we don't need to do anything if (oldWidget.text == text) return; // We don't need anything whenever the change is from the textfield itself @@ -267,6 +269,8 @@ class BaseTextFieldState extends State // to the next frame ensures the framework is out of the build phase before // the controller is mutated. WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + // we need to wait until the next microtask if (text.isEmpty) { _textEditingController.clear(); @@ -303,6 +307,7 @@ class BaseTextFieldState extends State // we need to wait until the next microtask WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; // ⚠️ validate after the new input is attached // we always want to validate the new input when the current state is invalid if (!_textFieldIsValid) { From a355a1da820dfb0fafbda964a924b03533956d1f Mon Sep 17 00:00:00 2001 From: Stefan Schaller Date: Mon, 15 Jun 2026 08:02:07 +0200 Subject: [PATCH 6/6] add mounted check --- lib/src/text_field/base_text_field.dart | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/src/text_field/base_text_field.dart b/lib/src/text_field/base_text_field.dart index 41dfc20..3d5709c 100644 --- a/lib/src/text_field/base_text_field.dart +++ b/lib/src/text_field/base_text_field.dart @@ -161,8 +161,7 @@ class BaseTextFieldState extends State _textEditingController = TextEditingController(); } - _textEditingController - .addListener(() => _onChanged(_textEditingController.text)); + _textEditingController.addListener(_onChanged); if (UniversalPlatform.isWeb) { _selectableRegionFocusNode.addListener(() { @@ -268,7 +267,7 @@ class BaseTextFieldState extends State // `setState() or markNeedsBuild() called during build`. Deferring the update // to the next frame ensures the framework is out of the build phase before // the controller is mutated. - WidgetsBinding.instance.addPostFrameCallback((_) { + onNextFrame(() { if (!mounted) return; // we need to wait until the next microtask @@ -295,8 +294,9 @@ class BaseTextFieldState extends State _formFieldKey.currentState?.validate(); } - // ignore: avoid_void_async - void _onChanged(String value) async { + void _onChanged() { + final value = _textEditingController.text; + if (_previousTextValue == value) return; _previousTextValue = value; @@ -305,8 +305,9 @@ class BaseTextFieldState extends State widget.onChanged(value); } - // we need to wait until the next microtask - WidgetsBinding.instance.addPostFrameCallback((_) { + // we need to wait for the next frame, otherwise the change + the set state happens in the same moment and throws: + // `setState() or markNeedsBuild() called during build` + onNextFrame(() { if (!mounted) return; // ⚠️ validate after the new input is attached // we always want to validate the new input when the current state is invalid