본문 바로가기
Flutter

[Flutter] 앱 내에서 테마 변경 기능(Feat. ValueNotifier)

by 개발짜 2024. 11. 20.

배경 설명

Flutter 로 기차 예매 서비스를 만들고 있는데 시스템 환경에 따라 라이트/다크 모드로 나오게끔 테마 설정을 해주었다. 하지만 시스템 환경이 라이트 모드이지만 앱은 다크 모드로 쓰고 싶을수도 있는 법. 앱 내에서도 테마 지정이 가능하면 좋을 거 같다는 생각에 아이콘을 눌렀을 때 테마 변경하는 기능을 추가하도록 하고싶다.

 

 

동작 방식 구상

테마를 변경하는 방법을 찾아보기 전에 간단하게 동작 방식을 구상해보았다.

 

먼저 테마가 지정되는 순간부터 적용되는 순서를 알아야 한다. MyApp 이 build 가 될 때 themeMode 는 시스템 설정 값을 가져와 light 이면 lightTheme 를 dark 면 darkTheme 로 지정이 된다. 그렇다면 themeMode 의 값을 다른 페이지에서 가져와서 MyApp 을 빌드를 다시하면 되지 않을까 라는 생각을 한다. themeMode 타입을 가진 currentMode 변수에 사용자의 입력(아이콘을 클릭하거나 toggle 이 움직이는 등)이 들어오면 light 혹은 dark 타입으로 변경이 되도록 한다. 값이 변경된 currentMode 는 MyApp 에서 themeMode 를 다시 지정해준다. currentMode 값이 변경된 것을 확인하려면 상태 관리 라이브러리를 사용하거나 값이 변경되었는지 체크하는 방법이 있을 것이라는 생각을 했다.

임의로 구상한 동작 방식

 

구현 방법 1 - 상태 관리 라이브러리 사용

앱의 테마 모드를 변경하는 방법을 찾아봤을 때 가장 먼저 보였던 Riverpod 라이브러리를 사용해서 구현하는 것이었다. 상태 관리 라이브러리인 Riverpod 사용하면 쉬웠겠지만 이 기능만을 위해 써드파티 라이브러리 사용하는 것이 싫었고, 기껏 StatefulWidget 상속받아서 사용했던 HomePage 위젯을 변경해야된다는 문제가 있기 때문에 나는 다른 방법을 찾아봤다. 하지만 누군가는 Riverpod 를 사용하는 방법도 궁금할수도 있으니 아래에 링크를 첨부하겠다.

 

참고: https://stackoverflow.com/questions/70849633/how-app-theme-mode-can-be-saved-in-flutter-according-to-user

 

How app theme mode can be saved in flutter according to user

I have added dark and light mode in my app but I am unable to save the mode selected. I am new so that's why I'm unable to understand what to do. Here's my code of dark and light mode button though...

stackoverflow.com

 

 

구현 방법 2 - 값이 변경되는지 체크

Flutter 내장 라이브러리에서 제공하는 클래스에서 상태 변경을 캐치하는 위젯을 찾았다. ValueNotifier 를 활용하여 값이 변경될 때 해당 값으로 테마 모드를 지정하는 방법을 채택했다.

 

ValueNotifier 란?

ChangeNotifier 의 하위 위젯으로 값(value) 이 기존의 값과 다르다면(!=) 리스너에게 알린다. ValueNotifier 는 값의 id 가 변경될 때만 리스너에게 알리기 때문에 가변 타입의 값이 바뀔때는 해당 리스너가 동작하지 않는다. 

 

예를 들어 ValueNotifier<List<int>> 타입의 notifier 는 list 값이 변할 때(add, remove 등)는 리스너가 동작하지 않는다.

ValueNotifier<List<int>> notifier = ValueNotifier([0, 1, 2]);

// list 가 변해도 리스너는 동작하지 않음
notifier.add(3);
notifier.removeLast();

// notifier 값(value) 자체를 새로 할당했을 때만 리스너가 동작함
notifier.value = [0];

 

 

대표적인 불변 타입인 String 타입의 notifier 를 설정한 예시이다. value 가 다르면 리스너에게 알리는 데 notifier.value 가 두번째 변경할 때에는 id 뿐만 아니라 등식 연산자(==) 가 달라지는 것 까지 확인한다.

ValueNotifier<String> notifier = ValueNotifier('notifier1');

// 리스너가 동작함
// notifier1 == notifier2 => false
notifier.value = 'notifier2';

// 리스너가 동작하지 않음
// notifier2 == notifier2 => true
notifier.value = 'notifier2';

// 리스너가 동작함
// notifier2 == notifier1 => false
notifier.value = 'notifier1'

 

ValueNotifier 클래스에 대해 알아보았으니 이제 실제로 테마 변경 기능을 구현해보자.

 

구현

1. MyApp 에서 ValueNotifier 타입의 notifier 를 생성한다.

2. ValueListenableBuilder 에서 valueListenalbe 을 themeNotifier 로 지정한다.

3. themeMode 를 currentMode 값을 받아온다.

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // ValueNotifier 생성
  static final ValueNotifier<ThemeMode> themeNotifier = ValueNotifier(ThemeMode.system);

  @override
  Widget build(BuildContext context) {
    // ValueListenableBuilder 에 themeNotifier
    return ValueListenableBuilder<ThemeMode>(
      valueListenable: themeNotifier,
      // notifier 의 제네릭 타입의 변수를 지정
      // themeNotifier.value 가 변경되면 해당 builder 가 다시 호출되어 UI 를 그린다
      builder: (_, ThemeMode currentMode, __) {
        return MaterialApp(
          home: HomePage(),
          themeMode: currentMode,
          theme: lightTheme,
          darkTheme: darkTheme,
        );
      },
    );
  }
}

 

4. icon 을 클릭했을 때 지금이 light 모드라면 themeNotifier.value 의 값을 ThemeMode.dark 로 변경하고 dark 모드라면 Theme.light 로 변경한다.

5. icon 모양도 위와 동일하게 맞춰준다.

class HomePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    // 테마 모드 확인
    bool isLightMode = Theme.of(context).brightness == Brightness.light;

    return Scaffold(
      appBar: AppBar(
        title: Text('기차 예매'),
        actions: [
          IconButton(
            onPressed: () {
              // icon 클릭할 때 Brightness 가 light 일 경우 value 값을 ThemeMode.dark 값으로 지정 아니면 ThemeMode.light
              MyApp.themeNotifier.value =
                  isLightMode ? ThemeMode.dark : ThemeMode.light;
            },
            // 모드가 light 일 경우 icon 이 dark_mode 아니면 light_mode
            icon: Icon(isLightMode ? Icons.dark_mode : Icons.light_mode),
          ),
        ],
      ),
      ...
    );
  }
}

 

아래와 같이 AppBar 오른쪽에 아이콘을 두어 클릭했을 때 테마가 바뀌는 것을 볼 수 있다. 

라이트/다크 모드일 때 변하는 icon

 

참고: https://heeeju4lov.tistory.com/17

 

[Flutter Dev] 라이트 / 다크 모드 테마 변경하기

요즘 괜찮다는 앱들은 사용자 편의를 위해 다크 모드를 제공하고 있습니다. Flutter에서는 다크모드를 어떻게 사용할수 있는지 알아보도록 하겠습니다. 샘플은 Flutter 프로젝트 생성시 자동은 생

heeeju4lov.tistory.com

 

댓글