Riverpod와 Hive를 사용하여 Flutter 앱 테마 관리하기

반응형

플러터(Flutter)를 사용하여 앱의 테마를 관리하는 것은 중요한 작업 중 하나입니다. 이 글에서는 Riverpod와 Hive를 결합하여 효과적으로 앱 테마를 관리하는 방법에 대해 알아보겠습니다.

테마 관리는 사용자 경험을 크게 향상시킬 수 있는 핵심 요소 중 하나입니다. 앱의 테마를 동적으로 변경하면 사용자가 앱의 외관을 사용자 정의할 수 있으며, 어두운 모드와 밝은 모드 등과 같은 다양한 환경에서 더 나은 가독성을 제공할 수 있습니다.

Riverpod와 Hive 소개

  • Riverpod: Riverpod은 플러터 앱에서 상태 관리를 위한 강력한 도구 중 하나입니다. Provider 패키지를 기반으로 하며, 의존성 주입 및 상태 관리를 쉽게 구현할 수 있도록 도와줍니다.

  • Hive: Hive는 플러터의 로컬 데이터베이스로서 사용됩니다. 가벼우면서도 빠른 데이터 저장 및 검색을 제공하며, Hive를 사용하여 테마 설정과 같은 앱 데이터를 지속적으로 저장할 수 있습니다.

앱 테마 관리 구현

  1. 테마 데이터 모델 정의: 먼저 테마 데이터 모델을 Hive 박스에 저장할 수 있도록 정의합니다. 예를 들어, ThemeData 클래스를 확장하고 필요한 테마 속성을 추가합니다.

  2. Hive 박스 설정: Hive 박스를 설정하고 테마 데이터를 저장하고 검색하는 데 사용합니다. 테마 데이터는 사용자의 테마 선택에 따라 박스에 저장됩니다.

  3. Riverpod 프로바이더 생성: Riverpod를 사용하여 현재 테마를 제공하는 프로바이더를 생성합니다. 이 프로바이더는 Hive 박스에서 현재 테마 데이터를 가져와서 제공합니다.

  4. 테마 변경 메서드 생성: 사용자가 테마를 변경할 때 호출할 메서드를 생성합니다. 이 메서드는 Hive 박스에 새로운 테마 데이터를 저장하고 Riverpod 프로바이더를 업데이트합니다.

  5. UI에서 테마 사용: 앱의 UI에서 Riverpod 프로바이더를 구독하여 현재 테마를 사용하고 UI를 업데이트합니다. 사용자가 테마를 변경하면 UI가 자동으로 업데이트됩니다.

이러한 단계를 따르면 Riverpod와 Hive를 사용하여 앱의 테마를 효과적으로 관리할 수 있습니다. 사용자가 원하는 테마로 앱을 사용할 수 있게 됩니다.


아래는 Riverpod에서 제공하는 샘플 코드를 분석한 내용입니다.

패키지 설치

$ flutter pub add flutter_riverpod 
$ flutter pub add hive_flutter

import 방법

// Riverpod 상태 관리 라이브러리를 사용하기 위한 패키지
import 'package:flutter_riverpod/flutter_riverpod.dart'; 

// Hive 데이터베이스 라이브러리를 사용하기 위한 패키지
import 'package:hive_flutter/hive_flutter.dart'; 

DatabaseService

// Hive Box에 테마 정보를 관리할 DatabaseService 클래스
class DatabaseService {
  //  Hive 데이터베이스에서 테마 정보를 저장하기 위한 박스(Box) 객체. 
  // `themeBox`는 데이터베이스 내에서 테마 정보를 저장하고 검색함..
  late final Box<String> themeBox;

  // `themeBox`에서 저장된 테마 정보를 가져오는 게터 메서드
  String get savedTheme => themeBox.values.first;

  // 테마 정보를 초기화하는 비동기 메서드
  Future<void> initTheme() async {
    //  'theme'이라는 이름의 Hive Box를 오픈하고, `themeBox` 변수에 박스 값을 할당
    await Hive.openBox<String>('theme').then((value) => themeBox = value);

    // 박스가 비어 있으면, 초기 로딩 시에 'light'라는 기본 테마를 추가.
    if (themeBox.values.isEmpty) {
      themeBox.add('light');
    }
  }

  // themeBox에 새로운 테마 모드를 저장하는 메소드
  Future<void> toggleSaveTheme(String mode) async =>
      await themeBox.put(0, mode);
}

// Provider를 사용하여 databaseService를 제공
final databaseService = Provider<DatabaseService>((_) => DatabaseService());

ThemeController

// ThemeController 클래스는 `ChangeNotifier`를 상속받아, 
// 테마 설정과 관련된 상태와 로직을 관리한다.
class ThemeController with ChangeNotifier {
  ThemeController(this._database);

  late final DatabaseService _database;

  // 현재 저장된 테마를 가져오는 게터 메서드. 
  // 현재 테마 정보를 제공한다.
  String get theme => _database.savedTheme;

  // 테마를 변경하는 토글 메서드.
  // boolean 변수 `mode`를 받는다.
  // mode가 true면 dart 테마, false면 light 테마이다.
  void toggle(bool mode) {
    (mode)
        ? _database.toggleSaveTheme("dark")
        : _database.toggleSaveTheme("light");
    
    // 테마가 변경되었음을 알리기 위해 `notifyListeners()`를 호출한다. 
    // 이것은 상태가 변경되었음을 알리고 UI 업데이트를 트리거한다.
    notifyListeners();
  }
}

// StateNotifierProvider를 사용하여 themeController를 제공한다.
final themeController = ChangeNotifierProvider<ThemeController>((ref) {
  final database = ref.watch(databaseService);
  return ThemeController(database);
});

home_screen.dart

import 'package:flutter/material.dart';

import 'app.dart';

class HomeScreen extends ConsumerWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    //  `ref.watch` 메서드를 사용하여 `themeController`를 관찰합니다.
    // `themeController`에서 현재 테마를 읽어와서 `mode` 변수에 할당합니다.
    final mode = ref.watch(themeController).theme;

    return Scaffold(
      appBar: AppBar(title: const Text('Riverpod - Hive')),
      body: SwitchListTile(
			title: const Text('Dark Theme'),
			value: (mode == 'dark') ? true : false,
			onChanged: (value) {
				// `themeController`의 `toggle` 메서드를 호출하여 테마를 변경한다.
				ref.watch(themeController.notifier).toggle(value);
			},
		 ),
    );
  }
}

app.dart

import 'package:flutter/material.dart';

import 'home_screen.dart';

class App extends ConsumerWidget {
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // themeController에서 theme 값을 관찰한다.
    final mode = ref.watch(themeController).theme;

    return MaterialApp(
      theme: (mode == 'dark') ? ThemeData.dark() : ThemeData.light(),
      home: const HomeScreen(),
    );
  }
}

main.dart

import 'package:flutter/material.dart';

import 'app.dart';

void main() async {
  // Flutter 바인딩을 초기화하는 메서드. 
  // Flutter 앱을 실행하기 전에 호출해야 함.
  WidgetsFlutterBinding.ensureInitialized(); 

  // Hive 데이터베이스를 초기화하는 비동기 메서드. 
  // Hive는 경량의 로컬 데이터베이스 라이브러리로 사용됨.
  await Hive.initFlutter(); 

  // 테마 관련 설정을 관리 할 `DatabaseService` 클래스의 인스턴스를 생성. 
  final dbService = DatabaseService();

  // 앱의 테마 설정을 초기화
  await dbService.initTheme();

  runApp(
    ProviderScope(
      overrides: [
        // `databaseService`를 오버라이드하고 `dbService`로 설정합니다. 이렇게 함으로써, 앱 내에서 `databaseService`를 요청할 때 항상 `dbService` 인스턴스가 반환됩니다.
        databaseService.overrideWith((_) => dbService),
      ],
      child: const App(),
    ),
  );
}

StateNotifier와 StateNotifierProvider를 활용한 themeController 작성하기

StateNotifier를 이용하여 themeController를 작성하면 코드가 더 간결해질 수 있습니다.
themeController를 다음과 같이 작성할 수도 있습니다.

class ThemeController extends StateNotifier<String> {
  ThemeController(this._database) : super(_database.savedTheme);

  late final DatabaseService _database;

  void toggle(bool mode) {
    (mode)
        ? _database.toggleSaveTheme("dark")
        : _database.toggleSaveTheme("light");
    state = _database.savedTheme;

    // 이제 더이상 `notifyListeners()` 메소드를 호출할 필요가 없습니다.
  }
}

final themeController = StateNotifierProvider<ThemeController, String>((ref) {
  final database = ref.watch(databaseService);

  return ThemeController(database);
});

END.


or

[카카오페이로 후원하기] [토스페이로 후원하기]

반응형