그동안 플러터로 개발해오면서 BuildContext를 애써 무시하며 Stateless, State 등을 렌더링 해왔다.
class App extends StatefulWidget {
const App({super.key});
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
// 여기!
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
...
위 부분 중 BuildContext가 무엇인지 알아볼 것이다.
그동안 우리가 각 제목과 본문 등의 Text에 직접 스타일을 넣어서 꾸며줬다. 사실 그러한 하드코딩은 플러터를 제대로 이용하지 않는 식이었다. MaterialApp
위젯의 theme
을 이용해서 색과 글씨 크기 등을 재사용 가능하도록 편하게 설정 할 수 있다.
class _AppState extends State<App> {
Widget build(BuildContext context) {
return MaterialApp(
// 여기 theme!
theme: ThemeData(
textTheme: const TextTheme(
titleLarge: TextStyle(
color: Colors.red,
),
),
),
home: const Scaffold(
backgroundColor: Color(0xFFF4EDDB),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MyLargeTitle(),
],
),
),
),
);
}
}
class MyLargeTitle extends StatelessWidget {
const MyLargeTitle({
super.key,
});
Widget build(BuildContext context) {
return const Text(
'My Large Title',
style: TextStyle(fontSize: 30),
);
}
}
위 코드는 My Large Title
Text를 꾸며주기 위해 하드코딩했지만
...
return MaterialApp(
// 여기 theme!
theme: ThemeData(
textTheme: const TextTheme(
titleLarge: TextStyle(
color: Colors.red,
),
),
),
...
위 부분을 추가해서 우리 Title
에 titleLarge
를 적용토록 할 것이다.
여기서 BuildContext가 나온다. 위 코드를 보면은 MyLargeTitle
라고, 커스텀 위젯을 만들어서 분리가 돼 _AppState
의 자식으로 속해져버렸다.
근데 _AppState
에서 theme
이 설정되지 않았는가? 이 theme
은 자식에게서 바로 쓸 수가 없다. 왜냐면 분리된 상태니까!
리액트 유저라면 분리된 컴포넌트에서 그에 관한 로직을 짜지 않고서는 서로의 선언된 변수를 사용할 수 없다고 이해하면 된다.
그걸 가능케 하는 것이 바로 BuildContext다.
구동 원리를 보기 전 위젯 트리를 이해하고 넘어가는 것이 중요하다.
참고 : 위젯트리는 어떻게 그려질까?
위 사진을 보면 Container
가 루트 위젯이자 제일 부모 위젯이고, 그 자식 Row
, Row
의 자식이자 형제인 Image
, Text
등으로 트리가 이루어 진다.
내가 썼던 코드에도 이런 식의 트리가 형성돼 있다.
Theme
을 선언한 게 무슨 이유?바로! 부모인 App
위젯에서 Theme
을 선언했지만 자식 위젯이자 커스텀 위젯인 MyLargeTitle
위젯이 그 Theme
을 접근하기 위해서는 BuildContext의 설정이 필요한 것이다!!
왜냐하면 위젯들이 분리돼있기에 선언된 인스턴스나 변수 등을 사용하지 못하기에 그 자식들이 부모의 설정, 테마 등을 접근하고 확인하기 위해 쓰는 것이다!
마치 리액트의 진화된 Props
와 전역 상태의 조합 같달까..
아까 썼던 코드를 이어 써보자.
... // App
class MyLargeTitle extends StatelessWidget {
const MyLargeTitle({
super.key,
});
Widget build(BuildContext context) {
return const Text(
'My Large Title',
style: TextStyle(fontSize: 30, color: Theme.of(context)),
);
}
}
Theme.of
로 선택하고 탐색할 수 있다. 자동완성을 따라가보면 color: Theme.of(context)),
이 됐고, 그 사이 context
가 중요한 포인트다.
우리 '테마'(theme)
'의'(of)
어떠한 것을 '위젯 트리'(context)
로부터 원하는 것을 가져와야한다.
그래서 context
뒤에 우리가 원하는 설정을 가져와보자.
class MyLargeTitle extends StatelessWidget {
const MyLargeTitle({
super.key,
});
Widget build(BuildContext context) {
return Text(
'My Large Title',
style: TextStyle(
fontSize: 30,
// 여기 !
color: Theme.of(context).textTheme.titleLarge!.color,
),
);
}
}
짜잔 됐다! 여기서 !
를 붙인 이유는 다트는 null safe 언어기 때문에 선언이 됐는지 안됐는지 확인이 되지 읺아서 오류가 뜬다. !
를 붙여서 값이 존재한다고 확실히 얘기해주자.
아주 잘된 모습이다.
정리하자면 Props이자 전역 변수같은 BuildContext가 항상 선언이 돼서 부모의 설정, 테마 등의 context를 자식이 사용할 수 있다! 라는 말이다.
이만큼 BuildContext를 잘 이해하고 활용한다면 아주 강력한 기능이 될 것이다.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Theme Example',
theme: ThemeData(
primaryColor: Colors.blue, // 기본 테마의 주요 색상 설정
accentColor: Colors.red, // 기본 테마의 강조 색상 설정
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Theme Example'),
),
body: Center(
child: Text(
'Hello, World!',
style: Theme.of(context).textTheme.headline4,
),
),
);
}
}
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Context Example',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
void _showAlertDialog(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('AlertDialog'),
content: Text('This is an example AlertDialog.'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('OK'),
),
],
);
},
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Context Example'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
_showAlertDialog(context); // BuildContext를 인자로 전달
},
child: Text('Show AlertDialog'),
),
),
);
}
}
context가 들어가면 비슷한 개념들이 많네요 ㅋㅋ 고생하셨습니다