強制アップデートが必要かどうかの結果をEnumで定義
ここでは一例として、以下の3種類を定義します。
-
not
: アップデートなし -
cancelable
: 後回しを許容するアップデートがある -
forcibly
: 強制的なアップデートがある
最新バージョンへのアップデートへのお願いは cancelable
、
古いバージョンで発生した不具合を避けたり、互換性が無くなった時に強制的にアップデートさせたいときは forcibly
を使用する想定です。
enum UpdateRequestType {
/// アップデートなし
not,
/// 後回しを許容するアップデートあり
cancelable,
/// 強制的なアップデートあり
forcibly,
}
RemoteConfigから受け取るデータをアプリ内で定義
今回は以下の3つのパラメータを定義しました。
-
requiredVersion
: 要求するバージョン文字列 -
canCancel
: アップデートをキャンセルしてアプリを使用できるようにするか -
enabledAt
: 強制アップデートを有効にする開始日
freezed
と json_serializable
を使用して、JSONから変換できる fromJson
ファクトリーコンストラクタを持ったクラスを以下のように定義します。
import 'package:freezed_annotation/freezed_annotation.dart';
part 'update_info_entity.freezed.dart';
part 'update_info_entity.g.dart';
class UpdateInfoEntity with _$UpdateInfoEntity {
const factory UpdateInfoEntity({
/// 要求バージョン e.g., '1.0.0'
required String requiredVersion,
/// アップデートを後回し可能にするかどうか
(false) bool canCancel,
/// 有効日(この日時以降のみ有効とする)
required DateTime enabledAt,
}) = _UpdateInfoEntity;
factory UpdateInfoEntity.fromJson(Map<String, dynamic> json) =>
_$UpdateInfoEntityFromJson(json);
}
flutter pub pub run build_runner
コマンドを実行すると、
update_info_entity.freezed.dart
, update_info_entity.g.dart
が生成されます。
アップデート情報を提供するFutureProviderを作成
詳しくはコメントで解説していますが、
- Remote Configのインスタンスを取得して最新の値をフェッチ
- 現在のバージョンと要求バージョンを比較
- 有効期間内かどうかチェック
を行い、 UpdateRequestType
として結果を返しています。
final updateRequesterProvider = FutureProvider<UpdateRequestType>((ref) async {
// 初期化・アクティベート済みのRemoteConfigインスタンス
final remoteConfig = ref.watch(remoteConfigProvider);
await remoteConfig.fetchAndActivate();
// 現在のアプリバージョンを取得するためにPackageInfoを利用
final appPackageInfo = await PackageInfo.fromPlatform();
// Firebase の RemoteConfigコンソールで指定したキーを使って値を取得
final string = remoteConfig.getString('update_info');
if (string.isEmpty) {
return UpdateRequestType.not;
}
// JSON to Map
final map = json.decode(string) as Map<String, Object?>;
// Map(JSON) to Entity
final entity = UpdateInfoEntity.fromJson(map);
final enabledAt = entity.enabledAt;
final requiredVersion = Version.parse(entity.requiredVersion);
final currentVersion = Version.parse(appPackageInfo.version);
// 現在のバージョンより新しいバージョンが指定されているか
final hasNewVersion = requiredVersion > currentVersion;
// 強制アップデート有効期間内かどうか
final isEnabled = enabledAt.compareTo(DateTime.now()) < 0;
if (!isEnabled || !hasNewVersion) {
// 有効期間外、もしくは新しいバージョンは無い
return UpdateRequestType.not;
}
return entity.canCancel
? UpdateRequestType.cancelable
: UpdateRequestType.forcibly;
});
ダイアログを表示するページの作成(デモページ)
前項で作成した updateRequesterProvider
を使用して、アップデート情報を取得します。
loading
パラメータでは、情報取得中の表示なので、インジケーターを表示しています。
非同期処理が終わり、アップデート情報を取得できると、 data
パラメータで情報を取得できます。
_ContentAndDialog
は、次項でStatelessWidgetとして作成します。
(Widgetを分けることは必須ではありません。 data
にそのまま続きを書いても問題ありません)
/// 強制アップデート情報を取得してダイアログを表示するデモページ
class VersionCheckPage extends ConsumerWidget {
const VersionCheckPage({
Key? key,
}) : super(key: key);
Widget build(BuildContext context, ScopedReader watch) {
return Scaffold(
appBar: AppBar(title: const Text('バージョンアップダイアログ')),
body: SafeArea(
child: Center(
// FutureProviderなので、 `when` で loading, error, data のハンドリングができる
child: watch(updateRequesterProvider).when(
loading: () => const CircularProgressIndicator(),
error: (error, stack) => ErrorWidget(error),
data: (requestType) => _ContentAndDialog(requestType: requestType),
),
),
),
);
}
}
ダイアログを表示する
引数として、 VersionCheckPage
からアップデート要求タイプを受け取っています。
showDialog
を使用して、アップデートをお願いするダイアログを表示しています。
「アップデート」ボタンを押した時に、アプリのストアURLを開くか、パッケージ[1]を使用して、App Store or Goole Play に遷移させましょう。
class _ContentAndDialog extends StatelessWidget {
const _ContentAndDialog({
Key? key,
required this.requestType,
}) : super(key: key);
final UpdateRequestType requestType;
Widget build(BuildContext context) {
// ビルド後にダイアログを表示させるための記法
WidgetsBinding.instance!.addPostFrameCallback((_) {
if (requestType != UpdateRequestType.not) {
// 新しいアプリバージョンがある場合はダイアログを表示する
showDialog<void>(
context: context,
// ダイアログの外をタップしても閉じられないようにする
barrierDismissible: false,
builder: (context) {
return WillPopScope(
// AndroidのBackボタンで閉じられないようにする
onWillPop: () async => false,
child: AlertDialog(
title: const Text('最新の更新があります。\nアップデートをお願いします。'),
actions: [
if (requestType == UpdateRequestType.cancelable)
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('キャンセル'),
),
TextButton(
onPressed: () {
// 要追加: App Store or Google Play に飛ばす処理
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Jump to Store.')),
);
Navigator.of(context).pop();
},
child: const Text('アップデート'),
),
],
),
);
},
);
}
});
return const Text('新しいバージョンがある場合はダイアログが表示されます。');
}
}
-
自動でiOSかAndroidか判別してStoreにジャンプさせてくれるメソッドを持ったパッケージがいくつか存在します。
app_review
https://pub.dev/packages/app_review ↩︎