🌔

LightモードとDarkモードを切り替えてみる

2024/04/14に公開

Tips💡

スマートフォンがLightモードかDarkモードで、Flutterアプリの画面全体のテーマを変更するのをやってみました。テーマが変わるので、テキストのカラーもライトならブラックで、ダークならホワイトに切り替わります。切り替わるだけだと、アプリを閉じたらリセットされるので、状態を保持するために端末に情報を保持させる方法を使います。

ボタンを押すと、テーマが切り替わるようにする必要がありますが、非同期処理と状態が変更されたのを通知して、画面を更新する必要があります。列挙型とサービスクラスを使って、ロジックを作りStatefulWidgetを使用して、setStateを使って画面を更新して、ライトモードとダークモードの切り替えをできるようにしております。

サンプルコード:

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

enum ThemeModeSetting { light, dark }

class ThemeService {
  Future<ThemeModeSetting> getThemeMode() async {
    final prefs = await SharedPreferences.getInstance();
    return (prefs.getBool('isDarkMode') ?? false)
        ? ThemeModeSetting.dark
        : ThemeModeSetting.light;
  }

  Future<void> switchThemeMode(ThemeModeSetting setting) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setBool('isDarkMode', setting == ThemeModeSetting.dark);
  }
}

void main() {
  runApp(const MyApp());
}

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

  
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final ThemeService themeService = ThemeService();
  late Future<ThemeModeSetting> themeMode;

  
  void initState() {
    super.initState();
    themeMode = themeService.getThemeMode();
  }

  
  Widget build(BuildContext context) {
    return FutureBuilder<ThemeModeSetting>(
      future: themeMode,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const CircularProgressIndicator();
        } else {
          return MaterialApp(
            theme: ThemeData.light(),
            darkTheme: ThemeData.dark(),
            themeMode: snapshot.data == ThemeModeSetting.dark
                ? ThemeMode.dark
                : ThemeMode.light,
            home: ThemeToggle(
              themeService: themeService,
              currentTheme: snapshot.data!,
              onThemeSwitched: () {
                setState(() {
                  themeMode = themeService.getThemeMode();
                });
              },
            ),
          );
        }
      },
    );
  }
}

class ThemeToggle extends StatelessWidget {
  const ThemeToggle({
    Key? key,
    required this.themeService,
    required this.currentTheme,
    required this.onThemeSwitched,
  }) : super(key: key);

  final ThemeService themeService;
  final ThemeModeSetting currentTheme;
  final VoidCallback onThemeSwitched;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // light or dark theme button
        leading: IconButton(
          icon: Icon(currentTheme == ThemeModeSetting.dark
              ? Icons.light_mode
              : Icons.dark_mode),
          onPressed: () async {
            ThemeModeSetting newSetting =
                currentTheme == ThemeModeSetting.dark
                    ? ThemeModeSetting.light
                    : ThemeModeSetting.dark;
            await themeService.switchThemeMode(newSetting);
            onThemeSwitched();
          },
        ),
        title: const Text('Theme Switcher'),
      ),
      body: Center(
        child: Text('Hello, World!'),
      ),
    );
  }
}

最後に

端末が、ライトモードかダークモードで背景色とテキストが切り替わるロジックは、SwiftUIでやったことあるので、ボタンを押して切り替えるロジックが必要なのか疑問ですが、時々このようなロジックを使っているのを見かけて作ってみました。最近はやりのsealed classなるものでも試してみたのですが、うまくいかなくて、 Enumで書いた方が見やすいし無理してやらなくてもいいのではと思って諦めました。Kotlinだったら使用例は紹介されてますが。

Discussion