플러터(Flutter)를 사용하여 앱의 테마를 관리하는 것은 중요한 작업 중 하나입니다. 이 글에서는 Riverpod와 Hive를 결합하여 효과적으로 앱 테마를 관리하는 방법에 대해 알아보겠습니다.
테마 관리는 사용자 경험을 크게 향상시킬 수 있는 핵심 요소 중 하나입니다. 앱의 테마를 동적으로 변경하면 사용자가 앱의 외관을 사용자 정의할 수 있으며, 어두운 모드와 밝은 모드 등과 같은 다양한 환경에서 더 나은 가독성을 제공할 수 있습니다.
Riverpod와 Hive 소개
-
Riverpod: Riverpod은 플러터 앱에서 상태 관리를 위한 강력한 도구 중 하나입니다. Provider 패키지를 기반으로 하며, 의존성 주입 및 상태 관리를 쉽게 구현할 수 있도록 도와줍니다.
-
Hive: Hive는 플러터의 로컬 데이터베이스로서 사용됩니다. 가벼우면서도 빠른 데이터 저장 및 검색을 제공하며, Hive를 사용하여 테마 설정과 같은 앱 데이터를 지속적으로 저장할 수 있습니다.
앱 테마 관리 구현
-
테마 데이터 모델 정의: 먼저 테마 데이터 모델을 Hive 박스에 저장할 수 있도록 정의합니다. 예를 들어,
ThemeData
클래스를 확장하고 필요한 테마 속성을 추가합니다. -
Hive 박스 설정: Hive 박스를 설정하고 테마 데이터를 저장하고 검색하는 데 사용합니다. 테마 데이터는 사용자의 테마 선택에 따라 박스에 저장됩니다.
-
Riverpod 프로바이더 생성: Riverpod를 사용하여 현재 테마를 제공하는 프로바이더를 생성합니다. 이 프로바이더는 Hive 박스에서 현재 테마 데이터를 가져와서 제공합니다.
-
테마 변경 메서드 생성: 사용자가 테마를 변경할 때 호출할 메서드를 생성합니다. 이 메서드는 Hive 박스에 새로운 테마 데이터를 저장하고 Riverpod 프로바이더를 업데이트합니다.
-
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
'개발 > 플러터(Flutter)' 카테고리의 다른 글
(Flutter) 플러터에서 Openai API 사용하기 (0) | 2023.04.13 |
---|---|
(Flutter) Riverpod의 Provider 종류 (0) | 2023.03.30 |
(Flutter) Riverpod 상태 불변성(Immutability) (0) | 2023.03.30 |
(Flutter) Riverpod의 Provider Lifecycles (0) | 2023.03.29 |
(Flutter) Riverpod의 ProviderObserver (0) | 2023.03.29 |