🌊

【Flutter】アプリのどこにいてもメンテナンスモードを作動させる

2024/11/26に公開

はじめに

モバイルアプリの要件に「メンテナンスモード」が含まれることは多いと思います。

しかし、ネット上には「main()内でメンテナンスフラグを取得してtrueの時はメンテナンス画面に遷移させる」ような 「アプリ起動時にしか作動しないメンテナンスモード」 の記事はたくさんありますが、 「アプリのどこからでも作動し、かつ、自動解除もするメンテナンスモード」 の記事はあまり無いように感じたので書いてみました。

前提

下記を使用

  • Firebase / Remote Config
  • Riverpod
  • GoRouter

実現方法(概要)

ざっくり下記を実装すればOKです

  1. リアルタイムRemoteConfigのリッスン
  2. 上記のProviderまたはNotifier
  3. GoRouterで上記ProviderまたはNotifierをwatch
  4. MaterialAppで上記GoRouterをwatch

実現方法(具体)

実現方法(概要)をコード付きで解説していきます。

1.リアルタイムRemoteConfigのリッスン

リッスン自体は公式Doc通りに下記で行えます。

import 'package:firebase_remote_config/firebase_remote_config.dart';

// インスタンス取得と初期設定
FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.instance;
await remoteConfig.setConfigSettings(RemoteConfigSettings(
  fetchTimeout: const Duration(minutes: 1),
  // 自動フェッチしてくれるので0でOK。
  // 自動フェッチを使わない場合は明示的なポーリングが必要なので当該値の設定が必要。
  minimumFetchInterval: const Duration(hours: 0), 
));

// 対象設定変数のデフォルト値の設定(なくてもいいけどあった方が安全)
await remoteConfig.setDefaults(const {"is_maintenance": false});

// リアルタイムRemoteConfigのリッスンを開始
remoteConfig.onConfigUpdated.listen((RemoteConfigUpdate update) async {
  // 更新された値を有効化
  await remoteConfig.activate();

  // あとはやりたき処理を実装...
});

2. 上記のProviderまたはNotifier

Riverpodを使って上記リスナーの永続化と取得した設定変数をstateで状態管理します。

firebase_remote_config_notifier.dart
import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'firebase_remote_config_notifier.g.dart';

(keepAlive: true) // 永続化。代わりにeager_initializationでもOK。
// Notifierにしてるけど、別でServiceを定義してそれのProviderでもOK。
class FirebaseRemoteConfigNotifier extends _$FirebaseRemoteConfigNotifier {
  late FirebaseRemoteConfig remoteConfig;

  
  bool build() {
    initialize();
    return remoteConfig.getBool('is_maintenance');
  }

  Future<void> initialize() async {
    remoteConfig = FirebaseRemoteConfig.instance;
    await remoteConfig.setConfigSettings(RemoteConfigSettings(
      fetchTimeout: const Duration(minutes: 1),
      minimumFetchInterval: const Duration(hours: 0),
    ));

    await remoteConfig.setDefaults(const {
      "is_maintenance": false,
    });

    remoteConfig.onConfigUpdated.listen((RemoteConfigUpdate update) async {
      await remoteConfig.activate();
      state = remoteConfig.getBool('is_maintenance');
    });
  }
}

3. GoRouterで上記ProviderまたはNotifierをwatch

GoRouterでルーティングを定義して、かつ、上記Notifierをwatchします。

go_router.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:sample/notifier/firebase_remote_config_notifier.dart';

part 'go_router.g.dart';


GoRouter goRouter(GoRouterRef ref) {
    // 設定変数をwatch
    bool isMaintenance = ref.watch(firebaseRemoteConfigNotifierProvider);

    return GoRouter(
      // ここで設定変数に応じて分岐
      initialLocation: isMaintenance ? '/maintenance' : '/home',
      routes: [
        GoRoute(
            path: '/maintenance',
            builder: (context, state) => const MaintenanceView(),
            routes: const []),
        GoRoute(
            path: '/home',
            builder: (context, state) => const HomeView(),
            routes: const []),
      ]);
}

4. MaterialAppで上記GoRouterをwatch

最後にMaterialAppのrouterConfigにGoRouterをセットします。

main.dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:sample/go_router.dart';

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

  // Firebaseの初期化
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  runApp(ProviderScope(child: const MyApp()));
}

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

  
  Widget build(BuildContext context, WidgetRef ref) {
    return MaterialApp.router(routerConfig: ref.watch(goRouterProvider);
  }
}

以上

RemoteConfigの設定変数値の更新をトリガーにGoRouterがリビルドされて、アプリのどこにいてもメンテナンスモードが作動できます。
また、設定変数値が更新されると自動でメンテナンス画面からホーム画面に遷移します。

参考

Discussion