Приведи пример использования локальных ключей
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Локальные ключи (LocalKey) в Flutter
Keys в Flutter — это механизм, который помогает виджетам идентифицировать друг друга при rebuild. LocalKey — это локальный ключ, уникальный только в рамках родительского виджета. Это критично для корректной работы stateful виджетов и анимаций.
Что такое Keys и зачем они нужны?
Когда Flutter пересчитывает дерево виджетов, ему нужно понимать, какой старый виджет соответствует какому новому. Без ключей, Flutter использует тип и позицию виджета, что часто приводит к ошибкам.
// ❌ БЕЗ ключей — проблема
class ItemList extends StatefulWidget {
@override
State<ItemList> createState() => _ItemListState();
}
class _ItemListState extends State<ItemList> {
List<String> items = ["A", "B", "C"];
@override
Widget build(BuildContext context) {
return Column(
children: [
for (final item in items)
StatefulItem(item), // ❌ Без ключей
],
);
}
}
class StatefulItem extends StatefulWidget {
final String label;
const StatefulItem(this.label);
@override
State<StatefulItem> createState() => _StatefulItemState();
}
class _StatefulItemState extends State<StatefulItem> {
late TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = TextEditingController(text: widget.label);
}
@override
Widget build(BuildContext context) {
return TextField(controller: _controller);
}
}
// Если ты удалишь первый item ("A"), Flutter переассигнует состояние:
// Старый State<"A"> → Используется для новой "B"
// Результат: текстовое поле покажет "A", хотя должно быть "B"
Типы LocalKey
В Flutter есть несколько типов локальных ключей:
1. ValueKey — самый распространённый
Используется, когда у каждого виджета есть уникальное значение (ID, имя, и т.д.)
class _ItemListState extends State<ItemList> {
List<String> items = ["A", "B", "C"];
@override
Widget build(BuildContext context) {
return Column(
children: [
for (final item in items)
StatefulItem(
item,
key: ValueKey(item), // ✅ Каждый item имеет уникальный ключ
),
],
);
}
}
Практический пример: Список с удалением
class TodoList extends StatefulWidget {
@override
State<TodoList> createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
final List<Todo> todos = [
Todo(id: 1, title: "Buy milk"),
Todo(id: 2, title: "Call mom"),
Todo(id: 3, title: "Fix bug"),
];
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return TodoItem(
key: ValueKey(todo.id), // ✅ Уникальный ID
todo: todo,
onDelete: () {
setState(() {
todos.removeAt(index);
});
},
);
},
);
}
}
class TodoItem extends StatefulWidget {
final Todo todo;
final VoidCallback onDelete;
const TodoItem({
required Key key,
required this.todo,
required this.onDelete,
}) : super(key: key);
@override
State<TodoItem> createState() => _TodoItemState();
}
class _TodoItemState extends State<TodoItem> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(duration: Duration(milliseconds: 300), vsync: this);
}
@override
Widget build(BuildContext context) {
return SlideTransition(
position: Tween<Offset>(begin: Offset.zero, end: const Offset(1, 0)).animate(_controller),
child: ListTile(
title: Text(widget.todo.title),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () async {
await _controller.forward();
widget.onDelete();
},
),
),
);
}
}
2. ObjectKey — для объектов
Когда значение сложного типа (весь объект)
class Person {
final int id;
final String name;
Person({required this.id, required this.name});
}
class PeopleList extends StatelessWidget {
final List<Person> people = [
Person(id: 1, name: "Alice"),
Person(id: 2, name: "Bob"),
];
@override
Widget build(BuildContext context) {
return Column(
children: [
for (final person in people)
PersonCard(
person: person,
key: ObjectKey(person), // ✅ Используем сам объект как ключ
),
],
);
}
}
3. UniqueKey — для уникальных элементов
Когда нужен вероятностно-уникальный ключ (редко используется)
class DynamicList extends StatefulWidget {
@override
State<DynamicList> createState() => _DynamicListState();
}
class _DynamicListState extends State<DynamicList> {
List<Widget> items = [];
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: () {
setState(() {
items.add(
ColorBox(
key: UniqueKey(), // ✅ Каждый элемент уникален
),
);
});
},
child: const Text("Add Item"),
),
...items,
],
);
}
}
class ColorBox extends StatefulWidget {
const ColorBox({required Key key}) : super(key: key);
@override
State<ColorBox> createState() => _ColorBoxState();
}
class _ColorBoxState extends State<ColorBox> {
late Color color;
@override
void initState() {
super.initState();
color = Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0);
}
@override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 100,
color: color,
);
}
}
Реальный пример: Табы с Stateful контентом
class TabbedContent extends StatefulWidget {
@override
State<TabbedContent> createState() => _TabbedContentState();
}
class _TabbedContentState extends State<TabbedContent> {
int _selectedTab = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
children: [
for (int i = 0; i < 3; i++)
ElevatedButton(
onPressed: () => setState(() => _selectedTab = i),
child: Text("Tab $i"),
),
],
),
// ❌ БЕЗ ключей — состояние смешается
// if (_selectedTab == 0) const TabContent(title: "Tab 0"),
// if (_selectedTab == 1) const TabContent(title: "Tab 1"),
// if (_selectedTab == 2) const TabContent(title: "Tab 2"),
// ✅ С ключами — правильно
if (_selectedTab == 0) const TabContent(key: ValueKey(0), title: "Tab 0"),
if (_selectedTab == 1) const TabContent(key: ValueKey(1), title: "Tab 1"),
if (_selectedTab == 2) const TabContent(key: ValueKey(2), title: "Tab 2"),
],
);
}
}
class TabContent extends StatefulWidget {
final String title;
const TabContent({required Key key, required this.title}) : super(key: key);
@override
State<TabContent> createState() => _TabContentState();
}
class _TabContentState extends State<TabContent> {
late TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = TextEditingController(text: widget.title);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return TextField(
controller: _controller,
decoration: InputDecoration(hintText: "Enter text for ${widget.title}"),
);
}
}
Когда использовать Keys
✅ ИСПОЛЬЗУЙ Keys когда:
- У тебя есть list of StatefulWidget с операциями (удаление, переупорядочивание)
- Нужны анимации при переходах между состояниями
- Один из нескольких похожих StatefulWidget видимый (табы, модалки)
- Используешь custom layout widgets
❌ НЕ НУЖНЫ Keys когда:
- Только StatelessWidget в списке
- Список не меняется (или добавляется только в конец)
- Каждый элемент имеет уникальный Index и не переупорядочивается
Отладка проблем с Keys
# Включи debug flag в Flutter Inspector
flutter run --verbose
# Смотри вывод и ищи warnings о State не сохраняющихся
# "Duplicate GlobalKey detected this widget tree"
# "The [GlobalKey] generated are not stable"
Итог
LocalKeys — это не опциональная фишка, а essential инструмент для корректной работы со Stateful виджетами. Правильное использование ValueKey спасает от множества багов, связанных с потерей состояния, неправильными анимациями и неожиданным поведением UI при изменении списков.