💡 학습목표
1. BuildContext 란 뭘까?
2. 위젯 키란 뭘까? - Widget({Key? key})
BuildContext(context) 는 위젯 트리의 현재 위치에 대한 정보를 가진 객체입니다. 이 context를 통해 위젯 트리에서 상위 위젯을 참조하거나 다양한 메타데이터(예: 테마, MediaQuery, Locale 등)를 얻을 수 있습니다. 단, context를 통해서 위젯 트리의 "상위" 위젯만 참조할 수 있습니다. 자식 위젯이나 형제 위젯을 직접적으로 참조하는 것은 context를 통해서는 불가능합니다. 그리고 context는 위젯 자체와 연결되어 있지 않습니다. 즉, BuildContext 객체는 위젯이 다시 빌드될 때마다 새롭게 생성될 수 있습니다.
위젯 키는 Flutter에서 위젯의 고유 식별자로 사용됩니다. 그래서 모든 위젯은 키 값을 가질 수 있습니다. Flutter는 위젯의 형태와 위치를 기반으로 위젯을 재사용하거나 다시 만듭니다. 그러나 특정 상황에서 개발자는 Flutter에게 어떤 위젯이 어떤 위젯과 일치하는지 명확하게 알려줄 필요가 있습니다. 이런 경우에 Key를 활용 할 수 있습니다.
💡 먼저 위젯 키를 사용하지 않아도 되는 경우를 살펴 봅시다.
시나리오 코드 - 1 (위젯키가 필요 없는 경우)
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<Widget> widgetList = [A(), B()];
onChange() {
setState(() {
// A위젯을 삭제하고 - widgetList.removeAt(0)
// 삭제하면 삭제한 위젯을 반환까지 함
widgetList.insert(1, widgetList.removeAt(0));
});
}
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(child: Scaffold(
body: Column(
children: [
Row(
children: widgetList,
),
ElevatedButton(onPressed:onChange, child: Text('toggle'))
],
),
)),
);
}
}
///////////////////////////////////////////
class A extends StatefulWidget {
const A({super.key});
State<A> createState() => _AState();
}
class _AState extends State<A> {
Widget build(BuildContext context) {
return Expanded(
child: Container(
height: 300,
color: Colors.orange[300],
child: Center(
child: Text("A"),
),
),
);
}
}
////////////////////////////
class B extends StatefulWidget {
const B({super.key});
State<B> createState() => _BState();
}
class _BState extends State<B> {
Widget build(BuildContext context) {
return Expanded(
child: Container(
color: Colors.blue,
height: 300,
child: Center(
child: Text("B"),
),
),
);
}
}
시나리오 코드 - 2 (위젯키가 필요 없는 경우)
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<Widget> widgetList = [A(), B()];
onChange() {
setState(() {
// A위젯을 삭제하고 - widgetList.removeAt(0)
// 삭제하면 삭제한 위젯을 반환 까지 함
widgetList.insert(1, widgetList.removeAt(0));
});
}
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(
child: Scaffold(
body: Column(
children: [
Row(
children: widgetList,
),
ElevatedButton(
onPressed: onChange,
child: Text('toggle'),
)
],
),
),
),
);
}
}
//////////////////////////////////////////
class A extends StatefulWidget {
const A({super.key});
State<A> createState() => _AState();
}
class _AState extends State<A> {
Widget build(BuildContext context) {
return Expanded(
child: Container(
height: 300,
color: Colors.orange[300],
child: Center(
child: Text("A"),
),
),
);
}
}
//////////////////////////////////////////
class B extends StatefulWidget {
const B({super.key});
State<B> createState() => _BState();
}
class _BState extends State<B> {
Widget build(BuildContext context) {
return Expanded(
child: Container(
color: Colors.blue[300],
height: 300,
child: Center(
child: Text("B"),
),
),
);
}
}
💡 change() 메서드를 실행하면 사실 위젯은 변경이 되지만 색상은 변경 되지 않습니다.
즉, 위젯 트리 구조가 변경되면서 Stateful 하위 객체인 State 객체를
각 위젯 객체에 올바르게 연결해야 하지만 데이터 타입이 같아서 제대로 식별하지 못하는 경우 입니다.
💡 GlobalKey 와 LocalKey 의 가장 큰 차이점은 어느 영역에서 까지 유일한 값인가 입니다.
GlobalKey는 앱 전체 LocalKey는 지정된 위젯의 부모로부터 자식 위젯에서 유일한 값입니다.
ValueKey: 가장 간단한 형태의 키로, 일반적인 값(문자열, 정수 등)으로 위젯을 식별합니다. 예를 들면, ValueKey('first_child') 또는 ValueKey(1)와 같이 사용됩니다.
Widget build(BuildContext context) {
return ListTile(
key: ValueKey('first_list_tile'),
title: Text('First ListTile'),
);
}
ObjectKey: 객체를 기반으로 하는 키입니다. ObjectKey는 해당 객체의 hashCode와 runtimeType을 사용하여 위젯을 고유하게 식별합니다. 예를 들어, 데이터 모델에서 가져온 객체를 기반으로 리스트 항목을 식별하려는 경우 유용합니다.
final item = DataItem(id: 1, name: 'Item 1');
Widget build(BuildContext context) {
return ListTile(
key: ObjectKey(item),
title: Text(item.name),
);
}
UniqueKey: 매번 새로운 고유한 식별자를 생성하는 키입니다. 이 키는 위젯이 매번 고유해야 할 때 유용하며, 재생성될 때마다 새로운 값을 갖게 됩니다.
Widget build(BuildContext context) {
return ListTile(
key: UniqueKey(),
title: Text('Dynamically Generated ListTile'),
);
}
시나리오 코드 - 위젯키 사용해보기
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<Widget> widgetList = [
A(
Colors.orange,
key: UniqueKey(),
),
A(
Colors.blueAccent,
key: UniqueKey(),
)
];
onChange() {
setState(() {
// A위젯을 삭제하고 - widgetList.removeAt(0)
// 삭제하면 삭제한 위젯을 반환까지 함
widgetList.insert(1, widgetList.removeAt(0));
});
}
Widget build(BuildContext context) {
return MaterialApp(
home: SafeArea(
child: Scaffold(
body: Column(
children: [
Row(
children: widgetList,
),
ElevatedButton(onPressed: onChange, child: Text('toggle'))
],
),
)),
);
}
}
///////////////////////////////////////////
class A extends StatefulWidget {
Color color;
A(this.color, {super.key});
State<A> createState() => _AState(this.color);
}
class _AState extends State<A> {
Color _color;
// 기본생성자에서 --> 사용자 정의생성자
_AState(this._color);
Widget build(BuildContext context) {
return Expanded(
child: Container(
height: 300,
color: _color,
child: Center(
child: Text("A"),
),
),
);
}
}