[英]Getx not updating list of TextFormField correctly
我在 Flutter Web 应用程序中使用 Getx 和ListView.builder
来呈现带有TextFormField
的项目列表。 每个呈现的项目都有一个删除按钮。 当我单击删除项目时,包含数据的列表似乎正确更新,但相应的 UI 错误地删除了显示的绝对最后一项,而不是您单击要删除的实际项目。 这个问题似乎专门发生在TextFormField
上。
我在下面包含了一个说明问题的示例应用程序。 要进行测试,只需安装Getx ,然后运行该应用程序(我将其作为 Web 应用程序运行)。 应用程序运行后,在左侧列(名为“ Using TextFormFields ”)中尝试删除项目,您会发现问题所在——删除的始终是最后显示的项目,即使您单击删除第一个项目也是如此. 为了进行比较,我在右侧包含了一个使用ListTile
s 而不是TextFormField
s 的设置,并且可以正常工作。
有谁知道为什么TextFormField
会出现这个问题? 你知道如何解决这个问题吗? 在此先感谢您的帮助!
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class Item {
final int id;
final String name;
Item({
required this.id,
required this.name,
});
}
class OverviewPageController extends GetxController {
final itemsList = [
Item(id: 0, name: 'Item 1'),
Item(id: 1, name: 'Item 2'),
Item(id: 2, name: 'Item 3'),
Item(id: 3, name: 'Item 4'),
].obs;
void deleteItem(Item item) {
int index = itemsList.indexOf(item);
var itemRemoved = itemsList.removeAt(index);
print('item deleted: ${itemRemoved.name}');
itemsList.refresh();
}
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const GetMaterialApp(
debugShowCheckedModeBanner: false,
home: OverviewPage(),
);
}
}
class OverviewPage extends StatelessWidget {
const OverviewPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(40),
child: Row(
children: const [
Expanded(child: TextFormFieldsSection()),
SizedBox(width: 40),
Expanded(child: ListTilesSection()),
],
),
),
),
);
}
}
class ListTilesSection extends StatelessWidget {
const ListTilesSection({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final controller1 = Get.put(OverviewPageController(), tag: '1');
return Column(
children: [
const Text('Using ListTiles', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
Obx(
() => ListView.builder(
shrinkWrap: true,
itemCount: controller1.itemsList.length,
itemBuilder: (context, index) {
return Column(
children: [
ListTile(
title: Text(controller1.itemsList[index].name),
trailing: OutlinedButton(
onPressed: () => controller1.deleteItem(controller1.itemsList[index]),
child: const Icon(Icons.delete_forever),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: const BorderSide(color: Colors.black12),
),
),
const SizedBox(height: 5),
],
);
},
),
),
],
);
}
}
class TextFormFieldsSection extends StatelessWidget {
const TextFormFieldsSection({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final controller2 = Get.put(OverviewPageController(), tag: '2');
return Column(
children: [
const Text('Using TextFormFields', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
Obx(
() => ListView.builder(
shrinkWrap: true,
itemCount: controller2.itemsList.length,
itemBuilder: (context, index) {
return Column(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.black12)
),
child: Row(
children: [
Expanded(
child: TextFormField(
readOnly: true,
initialValue: controller2.itemsList[index].name,
decoration: const InputDecoration(border: InputBorder.none),
),
),
OutlinedButton(
onPressed: () => controller2.deleteItem(controller2.itemsList[index]),
child: const Icon(Icons.delete_forever),
),
],
),
),
const SizedBox(height: 5),
],
);
},
),
),
],
);
}
}
你的代码是完全正确的,应该可以正常工作,它是 Flutter 不知道要删除哪个小部件,我将解释:
当 Flutter 引擎注意到树中存在的元素已从树中删除(您的代码确实如此)时,它会用具有相同runtimeType
的其他小部件替换它,因此当您想删除TextFormField
时,Flutter 将确切的小部件误认为是即使您的代码完全没问题,也请删除。
在大多数情况下,此行为对性能确实有帮助,可以避免在树中进行额外的必要构建。
你的问题的答案是告诉 Flutter 每个TextFormField
都是独一无二的,通过为 TextFormField 分配一个唯一的Key
,这是你的新代码:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class Item {
final int id;
final String name;
Item({
required this.id,
required this.name,
});
}
class OverviewPageController extends GetxController {
final itemsList = [
Item(id: 0, name: 'Item 1'),
Item(id: 1, name: 'Item 2'),
Item(id: 2, name: 'Item 3'),
Item(id: 3, name: 'Item 4'),
].obs;
void deleteItem(Item item) {
int index = itemsList.indexOf(item);
var itemRemoved = itemsList.removeAt(index);
print('item deleted: ${itemRemoved.name}');
itemsList.refresh();
}
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const GetMaterialApp(
debugShowCheckedModeBanner: false,
home: OverviewPage(),
);
}
}
class OverviewPage extends StatelessWidget {
const OverviewPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(40),
child: Row(
children: const [
Expanded(child: TextFormFieldsSection()),
SizedBox(width: 40),
Expanded(child: ListTilesSection()),
],
),
),
),
);
}
}
class ListTilesSection extends StatelessWidget {
const ListTilesSection({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final controller1 = Get.put(OverviewPageController(), tag: '1');
return Column(
children: [
const Text('Using ListTiles',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
Obx(
() => ListView.builder(
shrinkWrap: true,
itemCount: controller1.itemsList.length,
itemBuilder: (context, index) {
return Column(
key: UniqueKey(), // add UniqueKey()
children: [
ListTile(
key: UniqueKey(),
title: Text(controller1.itemsList[index].name),
trailing: OutlinedButton(
onPressed: () =>
controller1.deleteItem(controller1.itemsList[index]),
child: const Icon(Icons.delete_forever),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: const BorderSide(color: Colors.black12),
),
),
const SizedBox(height: 5),
],
);
},
),
),
],
);
}
}
class TextFormFieldsSection extends StatelessWidget {
const TextFormFieldsSection({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final controller2 = Get.put(OverviewPageController(), tag: '2');
return Column(
children: [
const Text('Using TextFormFields',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
Obx(
() => ListView.builder(
shrinkWrap: true,
itemCount: controller2.itemsList.length,
itemBuilder: (context, index) {
return Column(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.black12)),
child: Row(
children: [
Expanded(
child: TextFormField(
key: UniqueKey(), // add UniqueKey()
readOnly: true,
initialValue: controller2.itemsList[index].name,
decoration:
const InputDecoration(border: InputBorder.none),
),
),
OutlinedButton(
onPressed: () => controller2
.deleteItem(controller2.itemsList[index]),
child: const Icon(Icons.delete_forever),
),
],
),
),
const SizedBox(height: 5),
],
);
},
),
),
],
);
}
}
请注意我分配给小部件的UniqueKey()
,现在再次运行您的应用程序,它应该会按预期工作。
请同时参考这些主题:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.