概述
该问题包括Flutter TextFormField
在通过键盘输入第一个字符时完全失去光标-请参阅下面的完整复制步骤。
请注意,TextFormField
正在监视一个流,该流不应(也不会)在下面的发布复制步骤2-3期间发出任何事件。在TextFormField
中的onChanged
上,currentValueProvider
Riverpod StateProvider
将使用TextFormField
值进行更新。
下面的最小工作示例以valueStreamProvider
Riverpod StreamProvider
为特色,这可能看起来有点奇怪-这是因为实际的valueStreamProvider
Riverpod StreamProvider
版本要复杂得多,但是下面的最小工作示例演示了我们旨在克服的问题。
对里弗波德大吼一声,非常棒的装备,非常感谢雷米·鲁塞莱。
请参阅以下问题的完整详细信息,我们感谢所有提前反馈。如果您需要更多示例的详细信息,请随时联系我们。
问题复制步骤:
flutter run
打开web应用程序。TextFormField
随后将按预期使用文本“测试值”进行初始化;TextFormField
的初始文本/值经由正被观看的valueStreamProvider
RiverpodStreamProvider
来填充。
1.单击TextFormField
(光标按预期 Flink )并输入1个字符(通过键盘)-然后出现光标自动从TextFormField
消失的问题,因此用户不再(通过键盘)在TextFormField
中键入任何其他字符。
1.第二次单击TextFormField
(光标按预期 Flink )并输入1个以上字符(通过键盘)-TextFormField
中的光标未消失(按预期),用户能够按预期继续在TextFormField
中键入字符(通过键盘)。
预期行为(未发生):
flutter run
打开web应用程序。然后,TextFormField
将按预期使用文本“测试值”进行初始化;TextFormField
的初始文本/值经由正被观看的valueStreamProvider
RiverpodStreamProvider
来填充。
1.在TextFormField
中单击(光标按预期 Flink )并输入1个以上的字符(通过键盘输入所需数量的字符)。光标在任何时候都不会从“TextFormField”中消失(除非发生单击事件,该事件在“TextFormField”之外发生)。
基本上,只要currentValueProvider
RiverpodStateProvider
填充了1+个字符,我们就希望TextFormField
忽略(不接收)来自valueStreamProvider
RiverpodStreamProvider
的事件(因此currentValueProvider
RiverpodStateProvider
由TextFormField
更新以实现这一点)。
通过调试,我们发现:
final TextEditingController controller = useTextEditingController();
会正确保存游标位置的状态(在上述所有“问题复制步骤”中)。FocusNode
状态也会在上述所有“问题复制步骤”中保存,而且在任何步骤中都不会遗失此焦点。- 如果我们从
TextFormField
中省略valueStreamProvider
RiverpodStreamProvider
,则步骤1中的问题(上述问题复制步骤),因此,这很可能是由valueStreamProvider
RiverpodStreamProvider
引起的。我们已经记录了整个valueStreamProvider
,没有发出任何流事件-也许StreamProvider
正在发出加载事件?或者? - 当游标在上述发出复制步骤的步骤1中丢失时,日志记录已确认
TextformField
已重建-因此可能是游标丢失的原因。
最小工作示例:
请创建一个新的flutter Web应用程序,然后粘贴到以下文件中:
主省道
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() {
runApp(const ProviderScope(child: MyApp() ));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData( primarySwatch: Colors.blue ),
home: const Material(child: TextWidget()),
);
}
}
/// Riverpod Provider - current value
final currentValueProvider = StateProvider<String?>((final ref) => null);
/// Riverpod Provider - Value Stream
final valueStreamProvider = StreamProvider.autoDispose<String?>((final ref) async* {
// DB Document StreamProvider watched here - *** not in use in this minimal
// example *** but to illustrate why a StreamProvider is required
// for valueStreamProvider
// final String? firebaseDocumentStream = await ref.watch(firebaseDocumentStreamProvider
// .selectAsync((final String? v) => v?.firstName),
// );
// Only send event if 'Current Value' is not populated
final String? currentValue = ref.watch(currentValueProvider);
if (currentValue == null) {
yield* Stream<String?>.value('Test Value');
}
});
/// Text Widget
class TextWidget extends HookConsumerWidget {
const TextWidget({ final Key? key, }) : super(key: key);
@override
Widget build(final BuildContext context, final WidgetRef ref) {
final TextEditingController controller = useTextEditingController();
final FocusNode focusNode = useFocusNode();
return Consumer(
builder: (final BuildContext context, final WidgetRef ref, final Widget? child) {
// Listen to Value Stream
final String? value = ref.watch(valueStreamProvider).value;
return TextFormField(
key: UniqueKey(),
focusNode: focusNode,
controller: controller..text = value ?? "",
onChanged: (final String value) async {
// Update 'Current Value' Provider
ref.read(currentValueProvider.notifier).state = value;
},
);
}
);
}
}
出版规范yaml
name: test_text_field_stream
description: A new Flutter project.
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=2.18.5 <3.0.0'
dependencies:
flutter:
sdk: flutter
flutter_hooks: ^0.18.0
hooks_riverpod: ^2.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true
在此之后编辑
回复Remi的友好反馈
请看下面的“解决方案,与雷米的帮助(谢谢!)”回答这个帖子。
进一步查询
关于该解决方案,与雷米的帮助(谢谢!)”回答下面这个帖子,我们有以下进一步的问题,如果我们可以:
1.如果skipLoadingOnReload
未设置为true
,则ref.watch(valueStreamProvider).when
会卡住loading
-这是为什么?如果我们设置skipLoadingOnReload
= true
,则不会出现此问题,并且data
会按预期返回(即按预期构建/填充TextFormField
)...
1.在TextFormField
中,key: UniqueKey()
实际上应该是一个key: GlobalKey()
,这样我们就可以编写集成测试,它与TextFormField's
交互/测试TextFormField's
-我想建议不要在TextFormField
中包含key: GlobalKey()
,而是添加一个带有key: GlobalKey()
的父Container
,并从有没有好的文章解释“何时”和“何时不”使用keys
,特别是关于TextFormField's
,因为完全理解它会很好?
1.如果我们使用controller
(TextEditingController
),而不是将initialValue
替换为TextFormField
(在我们上面的“回复Remi的友好反馈”示例中),当第一个字符输入到TextFormField
**(i)光标跳到TextFormField
的开头,并且(ii)**输入的第一个字符从未出现在TextFormField
中,但从第二个字符开始出现......这是为什么,也许这超出了这个帖子/问题的范围。2请看下面第三点问题的完整例子(pubspec.yaml和原来的例子是一样的):
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() {
runApp(const ProviderScope(child: MyApp() ));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData( primarySwatch: Colors.blue ),
home: const Material(child: TextWidget()),
);
}
}
/// Riverpod Provider - current value
final currentValueProvider = StateProvider<String?>((final ref) => null);
/// Riverpod Provider - Value Stream
final valueStreamProvider = StreamProvider.autoDispose<String?>((final ref) async* {
// DB Document StreamProvider watched here - *** not in use in this minimal
// example *** but to illustrate why a StreamProvider is required
// for valueStreamProvider
// final String? firebaseDocumentStream = await ref.watch(firebaseDocumentStreamProvider
// .selectAsync((final String? v) => v?.firstName),
// );
// Only send event if 'Current Value' is not populated
final String? currentValue = ref.watch(currentValueProvider);
if (currentValue == null) {
yield* Stream<String?>.value('Test Value');
}
});
/// Text Widget
class TextWidget extends HookConsumerWidget {
const TextWidget({ final Key? key, }) : super(key: key);
@override
Widget build(final BuildContext context, final WidgetRef ref) {
final controller = useTextEditingController();
return ref.watch(valueStreamProvider).when(
skipLoadingOnReload: true,
data: (final String? value) => TextFormField(
controller: controller..text = value ?? "",
onChanged: (final String value) async {
// Update 'Current Value' Provider
ref.read(currentValueProvider.notifier).state = value;
},
),
error: (final Object err, final StackTrace stack) =>
throw Exception("Deal with error TODO: $err"),
loading: () => const Text("Loading TODO"),
);
}
}
1条答案
按热度按时间tp5buhyn1#
在Remi的帮助下,解决方案(谢谢!)
感谢Remi为我们指明了正确的方向--现在它可以按预期的方式工作了,并进行了以下更改(下面是完整的示例,pubspec.yaml与上面的原始示例相同):
1.如Remi所建议的那样,省略
key: UniqueKey()
(谢谢!!)1.在观察
valueStreamProvider
StreamProvider
(即ref.watch(valueStreamProvider).when
)时实现.when
,并设置skipLoadingOnReload
=true
1.在
TextFormField
中,将controller
属性取代为initialValue
属性。