Flutter

Theme.of(context) 사용시 주의할 점

flutter developer 2025. 2. 11. 22:13

Theme.of(context)의 내부 탐색 과정

Flutter에서 Theme.of(context)는 BuildContext를 이용해 InheritedWidget을 검색하는해쉬테이블 방식으로 동작한다. Theme 위젯은 InheritedWidget을 확장한 형태로 존재한다.
 
📌 실제 내부 구현 방식 (Theme.of)

static ThemeData of(BuildContext context) {
  final Theme? theme = context.dependOnInheritedWidgetOfExactType<Theme>();
  return theme?.data ?? ThemeData.fallback();
}
  • context.dependOnInheritedWidgetOfExactType<Theme>() 메서드는 현재 BuildContext에서 Theme를 찾는다.
  • 만약 Theme 위젯이 없다면 ThemeData.fallback()을 반환한다.
  • 만약 현재 build 함수에서 새롭게 Theme 을 정의한다면 BuildContext에서 아직 인식되지 않는다


Theme.of(context)가 현재 build에서 Theme 위젯을 찾지 못하는 이유

Flutter의 위젯 트리는 단순한 트리 구조가 아니라, Element Tree라는 메커니즘을 활용해 렌더링된다.  Widget Tree는 불변(Immutable)하며, Element Tree는 이 위젯을 기반으로 동적으로 변한다.
 
📌 위젯이 렌더링되는 순서

  1. build 함수가 호출됨.
  2. 위젯이 Element Tree에 추가되지만, 새로운 Theme 위젯이 아직 등록되지 않음.
  3. Theme.of(context)가 실행될 때, Element Tree에서 Theme를 찾으려 하지만 아직 BuildContext에 반영되지 않음.
  4. 부모 MaterialApp.theme를 반환 → 새로운 Theme가 적용되지 않음.

📌 잘못된 예시 (현재 BuildContext에서 Theme가 반영되지 않음)

Theme(
  data: ThemeData(primaryColor: Colors.red),
  child: Container(
    color: Theme.of(context).primaryColor, // ❌ 올바른 primaryColor를 찾을 수 없음
  ),
);
  • Theme.of(context).primaryColor가 Colors.red가 아닌, MaterialApp.theme의 기본 primaryColor를 가져옴.
  • 이는 Theme 위젯이 아직 Element Tree에 반영되지 않았기 때문.

 

✅ 1. Builder를 활용한 새로운 BuildContext 생성

Builder는 새로운 BuildContext를 생성하여, Theme가 적용된 이후에 Theme.of(context)를 실행하도록 도와준다.
 
📌 Builder를 활용한 해결 코드

Theme(
  data: ThemeData(primaryColor: Colors.red),
  child: Builder(
    builder: (context) { // 새로운 BuildContext 생성
      return Container(
        color: Theme.of(context).primaryColor, // ✅ 정상적으로 Colors.red를 가져옴
      );
    },
  ),
);
  • Builder는 새로운 BuildContext를 생성하므로, Theme.of(context)가 Theme을 정상적으로 찾을 수 있음.
  • 불필요한 위젯 트리 재생성을 방지하면서, 최적화된 방식으로 Theme을 적용 가능.

✅ 2. 새로운 Stateful/Stateless 위젯으로 분리

Flutter의 StatelessWidget이나 StatefulWidget은 자동으로 새로운 BuildContext를 생성한다.
이를 활용하면 Builder 없이도 Theme.of(context)를 올바르게 사용할 수 있다.
 
📌 StatelessWidget을 활용한 해결 코드

Theme(
  data: ThemeData(primaryColor: Colors.red),
  child: MyCustomWidget(), // 새로운 StatelessWidget 사용
);

class MyCustomWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Theme.of(context).primaryColor, // ✅ 정상적으로 Colors.red를 가져옴
    );
  }
}
  • MyCustomWidget이 새로운 BuildContext를 가지므로, Theme.of(context)가 현재 Theme을 정상적으로 찾을 수 있다.
  • Builder 없이도 구조적으로 깔끔한 코드 유지가 가능하다.

 

✅ 3. InheritedWidget을 활용한 확장성 높은 테마 관리

대규모 프로젝트에선 Theme.of(context)를 직접 호출하는 대신, InheritedWidget을 활용해 테마 데이터를 공유하는 것이 효율적이다
 
📌 커스텀 InheritedWidget을 활용한 Theme 관리

class CustomTheme extends InheritedWidget {
  final ThemeData themeData;

  const CustomTheme({required this.themeData, required Widget child, Key? key})
      : super(key: key, child: child);

  static ThemeData of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CustomTheme>()!.themeData;
  }

  @override
  bool updateShouldNotify(CustomTheme oldWidget) {
    return oldWidget.themeData != themeData;
  }
}

 
📌 사용 예시

CustomTheme(
  themeData: ThemeData(primaryColor: Colors.red),
  child: SomeWidget(),
);

class SomeWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: CustomTheme.of(context).primaryColor, // ✅ Colors.red 적용됨
    );
  }
}
  • ThemeData를 효율적으로 공유하면서, Theme.of(context)를 직접 호출하는 의존성을 줄일 수 있음.
  • 대규모 프로젝트에서 성능 최적화 및 재사용성을 높이는 패턴.

Theme 활용법 정리

기본적인 Theme.of(context)의 동작 원리

  • Theme.of(context)는 현재 BuildContext의 InheritedWidget을 탐색하여 ThemeData를 반환함.
  • 하지만, 현재 build에서 생성된 Theme는 아직 BuildContext에 반영되지 않음.

실제 대규모 앱에서 Theme.of(context) 문제를 해결하는 방법

  1. Builder 사용: BuildContext를 새로 생성하여 Theme 위젯을 정상적으로 참조할 수 있도록 함.
  2. 새로운 StatefulWidget/StatelessWidget 분리: 새로운 BuildContext를 자동으로 생성하여 문제 해결.
  3. InheritedWidget을 활용한 커스텀 테마 관리: 대규모 애플리케이션에서 테마 적용을 최적화하고 확장성 높임.