왜 이 내용을 정리하나
Flutter 앱을 만들다 보면 MaterialApp에 theme을 한 번 지정해 두고, 그 뒤로 여러 화면을 Navigator로 쌓아 가는 구조가 흔하다. 이때 첫 화면에서는 색이 잘 보이는데 push한 새 화면의 AppBar나 버튼 색이 기대와 다르게 나오면 원인을 찾기 어렵다. 특히 화면마다 Theme 위젯을 따로 감싸 둔 경우에는 어느 쪽 설정이 이기는지 헷갈린다. 그래서 theme의 primaryColor가 새 라우트까지 실제로 내려가는지 눈으로 보는 대신 코드로 확인하는 방법을 정리해 본다.
개념 설명
MaterialApp의 theme 값은 앱 전체의 기본 ThemeData로 쓰인다. Navigator가 push하는 라우트는 같은 MaterialApp 아래에 있으므로 별다른 지정이 없으면 같은 ThemeData를 상속받는다. 즉 primaryColor도 기본적으로 그대로 전달된다.
다만 Theme 위젯을 중간에 한 번 더 감싸면 그 하위 트리에서는 바깥 값을 덮어쓴 ThemeData가 쓰인다. 라우트는 Navigator 아래의 별도 Overlay 항목으로 올라가지만, Theme 조회는 위젯 트리의 BuildContext를 따라 올라가며 이뤄진다. 따라서 push된 라우트의 context에서 Theme.of(context)를 호출하면 그 시점에 유효한 primaryColor를 얻을 수 있다.
핵심은 화면에 그려진 색을 추측하지 않고, 라우트의 build 안에서 Theme.of(context).primaryColor 값을 읽어 확인하는 것이다. 값을 직접 출력하거나 테스트에서 expect로 비교하면 된다.
코드 예제
아래 예제는 첫 화면에서 두 번째 화면으로 push한 뒤, 두 번째 화면이 읽은 primaryColor를 화면에 텍스트로 보여준다. 기대값은 MaterialApp에 지정한 색이며, 일치 여부도 함께 표시한다.
import 'package:flutter/material.dart';
const Color kExpectedPrimary = Color(0xFF1565C0);
void main() {
runApp(const ThemeCheckApp());
}
class ThemeCheckApp extends StatelessWidget {
const ThemeCheckApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'theme check',
theme: ThemeData(
primaryColor: kExpectedPrimary,
),
home: const FirstPage(),
);
}
}
class FirstPage extends StatelessWidget {
const FirstPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('첫 화면')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const SecondPage()),
);
},
child: const Text('두 번째 화면으로 이동'),
),
),
);
}
}
class SecondPage extends StatelessWidget {
const SecondPage({super.key});
@override
Widget build(BuildContext context) {
final Color actual = Theme.of(context).primaryColor;
final bool matched = actual.value == kExpectedPrimary.value;
return Scaffold(
appBar: AppBar(title: const Text('두 번째 화면')),
body: Center(
child: Text(
matched ? 'primaryColor 일치' : 'primaryColor 불일치',
style: TextStyle(color: actual, fontSize: 20),
),
),
);
}
}
이 코드를 실행해 첫 화면의 버튼을 누르면 두 번째 화면이 열리고, 그 화면의 AppBar와 가운데 텍스트가 MaterialApp에서 지정한 색으로 표시된다. 텍스트 내용은 기대한 색과 실제 색이 같으면 'primaryColor 일치'가 나오고 다르면 '불일치'가 나온다. 색상 값은 TextStyle의 color로도 그대로 적용되어 시각적으로도 확인할 수 있다.
헷갈리는 점
가장 흔한 혼동은 AppBar 색과 primaryColor를 같은 것으로 보는 것이다. Material 3에서는 ColorScheme 기반으로 색이 계산되므로 primaryColor만 바꿔도 AppBar 배경이 그대로 바뀌지 않을 수 있다. 그래서 색을 확인할 때는 AppBar 모양보다 Theme.of(context)에서 읽은 값을 기준으로 보는 편이 정확하다.
두 번째로, Navigator.push 전에 감싼 Theme 위젯이 라우트 바깥에 있는지 안에 있는지에 따라 결과가 달라진다. push 호출 위치의 context가 아니라 라우트 builder 안의 context를 기준으로 읽어야 한다는 점도 자주 놓친다. 이 노트의 예제는 builder 안에서 Theme.of를 호출하므로 그 차이를 피한다.
세 번째로, 다이얼로그나 BottomSheet처럼 Navigator 라우트가 아닌 Overlay 기반 위젯은 같은 방식으로 확인하더라도 context 위치가 다를 수 있다. 그런 경우에도 해당 위젯의 build 안에서 Theme.of를 호출해 값을 직접 읽는 방식이 같게 적용된다.
정리
push된 새 라우트에서 primaryColor가 적용되는지는 라우트 build 안에서 Theme.of(context).primaryColor를 읽어 기대값과 비교하면 코드로 확인할 수 있다. MaterialApp의 theme은 Navigator 라우트에 기본으로 상속되며, 중간에 Theme 위젯이 끼어 있으면 그 하위 값이 우선한다. AppBar의 실제 색은 Material 3의 ColorScheme 계산에 따라 달라질 수 있으므로 화면보다 읽은 값을 기준으로 판단하는 것이 안전하다. 이 노트에서는 theme 값을 라우트 안에서 직접 읽어 비교하는 방법을 정리해 본다.
#Flutter #MaterialApp #theme #primaryColor #Navigator #라우팅 #Dart
'Flutter' 카테고리의 다른 글
| Flutter async*와 yield로 만든 Stream을 StreamBuilder에 연결했을 때 ConnectionState 변화 순서 확인하기 (0) | 2026.10.26 |
|---|---|
| Dart 명명된 매개변수에서 required와 기본값을 함께 쓸 때의 동작 확인 (0) | 2026.10.24 |
| Dart 레코드를 함수 반환값으로 쓰고 구조 분해로 받아 확인하는 방법 (0) | 2026.10.24 |
| Flutter Column과 Row로 화면을 배치하는 방법 (0) | 2026.10.23 |
| Flutter에서 StatelessWidget과 StatefulWidget을 나누는 기준 (0) | 2026.10.23 |