..

Flutter 2.5 スケルトンをながめる(4)

前回 の続き。 Flutter2.5からスケルトンが提供されるようになったので、それをながめてみる。

6. settings

settingsディレクトリをながめる。このディレクトリには3つのファイルが含まれている。
サフィックスのとおり、コントローラー・サービス・画面である。 settingsを設定する画面と、その機能を提供するコントローラー、コントローラーのバックエンドとしてデーターの保存・復元を行うサービスが、ひとつのディレクトリに置かれている。

└── src
    └── settings
        ├── settings_controller.dart
        ├── settings_service.dart
        └── settings_view.dart

6.1. SettingsView

まず、コンストラクタで、SettingsControllerを引数で受け取って保存している。

つぎに、SampleItemListViewと同様に、画面遷移のためのrouteNameを設定している(4.3. 画面遷移(ルート)を参照)。

最後に、build関数で画面を生成している。

appBarは、タイトルを設定しているのみである。

bodyには、DropdownButtonをおき、3つのテーマモード(system/light/dark)を選択可能にしている。 valueにはcontroller.themeMode、onChangedにはcontroller.updateThemeModeを設定しており、SettingsControllerと連携している。

child: DropdownButton<ThemeMode>(
  value: controller.themeMode,
  onChanged: controller.updateThemeMode,
  items: const [

onChangedでは、controller.updateThemeModeを引数なしで呼び出している。これで選択された値が渡るのか。

6.2. SettingsController

SettingsControllerクラスは、ChangeNotifierをmixinしている。 コンストラクタでSettingsServiceを受け取り、プライベート変数に保持している。 変数名を_(アンダースコア)で始めることでプライベート変数となるのは、Dartの言語仕様。

また、プロパティthemeModeを読み取り専用に設定している。

  late ThemeMode _themeMode;
  ThemeMode get themeMode => _themeMode;

メソッドは2つ、loadSettingsとupdateThemeMode。

loadSettingsは、保存されたサービスから読み込んでプロパティに保持するための関数。 このスケルトンでは、プロパティはthemeModeのみ。読み込み後は、notifyListeners関数を実行して、ウィジェットへの通知を行う。

updateThemeModeは、themeModeを変更するための関数。SettingsView内のDropdownButtonのonChangedから呼び出される。 呼び出し元では引数なしだが、こちらでは引数を受け取っている。仮引数がNull許容型なので、Nullチェックを行った後、プロパティに保存している。 プロパティはNull非許容型なので、チェックなしだとコンパイルエラーとなる。

Future<void> updateThemeMode(ThemeMode? newThemeMode) async {
  if (newThemeMode == null) return;
  _themeMode = newThemeMode;

つづいてnotifyListeners関数を実行してウィジェットへの通知を行う。 app.dartで生成しているMaterialApp内で、themeModeにこのコントローラーのthemeModeプロパティを割り当てているので、 このプロパティを変更することでアプリケーションのテーマの変更が反映される。
app.dart

theme: ThemeData(),
darkTheme: ThemeDate.dark(),
themeMode: settingsController.themeMode,

最後に、サービスを使って設定値を保存している。

6.3. SettingsService

サービスの読取・保存を担当するクラスだが、その機能はスケルトンでは実装されていない。

メソッドは2つ、themeModeとupdateThemeMode。
themeMode関数は、つねにThemeMode.systemを返却している。
updateThemeModeは、なにもしていない。

6.4. 改造

ということで、保存できるように改造する。
settings_service.dart内のコメントにあるように、shared_preferencesを使う。

shared_preferences 2.0.8

Installingタブにあるとおりに、pubspec.yamlに追記した後、pub getを実行しておく。 pub getの後は、ホットリロードではなく、再デプロイが必要となる。

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  shared_preferences: ^2.0.8 //追記

SettingsServiceを変更する。

settings_service.dart

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

class SettingsService {
  static const themeModeKey = 'themeMode';
  Future<ThemeMode> themeMode() async {
    final _storage = await SharedPreferences.getInstance();
    var _themeMode = _storage.getString(themeModeKey);
    switch (_themeMode) {
      case 'ThemeMode.light':
        return ThemeMode.light;
      case 'ThemeMode.dark':
        return ThemeMode.dark;
      default:
        return ThemeMode.system;
    }
  }

  Future<void> updateThemeMode(ThemeMode theme) async {
    final _storage = await SharedPreferences.getInstance();
    await _storage.setString(themeModeKey, theme.toString());
  }
}

モード名は列挙型にすべきかもしれないが、まずは、これで動作確認。
ところが、SharedPreferences.getInstance();でエラーが発生した。

E/flutter (10113): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: Null check operator used on a null value

どうやら、事前にWidgetsFlutterBinding.ensureInitialized()を呼び出しておく必要があるらしい。 参考記事によると「一言で言うとrunApp()を呼び出す前にFlutter Engineの機能を利用したい場合にコールします」とのこと。
参考 [Flutter] WidgetsBindingとは何か?
たしかに、app.dart内で、runApp()の前にSettingsController.loadSettings()を呼び出しており、これがSettingsService.themeMode()を呼び出している。

app.dart

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

この一行を追加することで、動作するようになった。


スケルトンをながめるのは、この記事で終了である。