LoginSignup
0

Flutter Webで「このサイトを離れますか?」を表示する(beforeunloadイベント)

Last updated at Posted at 2021-12-04

はじめに

Flutter Webでスプラトゥーン2のプレイヤー向けの動画プレイヤーアプリを作成しました。プレイの録画ファイルをブラウザにドラッグアンドドロップすると、動画を開き、やられたシーンなど重要シーンを頭出しします。

頭出しは画像認識で行っているので時間がかかります。そして、うっかりタブを閉じてしまった場合は、残念なことに、また最初からの頭出しになってしまいます。よって、動画を開いているときにタブを閉じたときはブラウザ標準の「このサイトを離れますか?」ダイアログを表示します。

スクリーンショット 2021-12-05 6.47.29.png

前提

Flutterと状態管理に使用しているライブラリのバージョン

すこし古いのでバージョンアップしようとしたのですが、Chromeによるデバッグ実行ができなくなる問題を回避できなかったため、現状動くバージョンでの解説になります。

動画ファイルの開閉状態

動画を開いている等のUIの状態はfreezedで作成したクラスとStateNotifierで管理しています。

main_ui_model.dart
@freezed
abstract class MainUiModel with _$MainUiModel {
  const factory MainUiModel(
    bool isOpen /* trueの時は動画ファイルを開いている */,
    String fileName /* 開いた動画ファイルの名前 */
    /* 略 */
  ) = _MainUiModel;
}
main_ui_model_state_notifier_provider.dart
class MainUiModelStateNotifier extends StateNotifier<MainUiModel> {
  /// 動画ファイルがドラッグアンドドロップ領域に落とされた
  /// [fileName] ファイル名
  void onDrop(String fileName) {
    state = state.copyWith(fileName: fileName, isOpen: true);
  }

  /// 動画ファイルを閉じる
  void onCloseFile() {
    state = state.copyWith(fileName: "", isOpen: false);
  }
}

final mainUiModelStateNotifierProvider =
    StateNotifierProvider<MainUiModelStateNotifier, MainUiModel>((ref) {
  return MainUiModelStateNotifier();
});

isOpenフラグがtrueの時のみ、タブを閉じたときに「このサイトを離れますか?」を表示したいです。

JavaScriptでの実装方法

JavaScriptではbeforeunloadイベントを使って実装することができます。

参考 Window: beforeunload イベント - Web API | MDM

window.addEventListener('beforeunload', (event) => {
  event.preventDefault();
  event.returnValue = '';
});

Flutter Webでの実装方法

Flutter Webではbeforeunloadイベントはdart:htmlライブラリWindowクラスのonBeforeUnloadプロパティに実装されています。こちらは Stream<Event> クラスのインスタンスが取得できます。よって、StreamProvider.autoDisposeを使ってこのように実装しました。

confirm_tab_close_stream_provider.dart
final confirmTabCloseStreamProvider = StreamProvider.autoDispose<bool>(
    (ref) => window.onBeforeUnload.map((event) {
          // BeforeUnloadEventにキャスト
          final beforeUnloadEvent = event as BeforeUnloadEvent;
          // UIの状態を取得
          final mainUiModel = ref.read(mainUiModelStateNotifierProvider);
          // 動画を開いているときのみ「このサイトを離れますか?」を表示する。
          if (mainUiModel.isOpen) {
            beforeUnloadEvent.preventDefault();
            beforeUnloadEvent.returnValue = '';
          }
          // 流すインスタンスは何でも良い。
          return true;
        }));

こちらのStreamProviderをタイトル部分など常に表示されているHookConsumerWidgetで監視します。

main_page.dart
/// タイトル部分
class TitleWidget extends HookConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 表示のための状態を監視
    final mainUiModel = ref.watch(mainUiModelStateNotifierProvider);
    // タブを閉じるときの確認担当。
    final futureConfirmTabClose = ref.watch(confirmTabCloseStreamProvider.last);
    // 長いアプリ名
    var title = Strings.titleLong;
    // 動画ファイルを開いている時は「短いアプリ名 - 動画ファイル名」にする
    if (mainUiModel.fileName.isNotEmpty) {
      title = sprintf("%s - %s", [Strings.title, mainUiModel.fileName]);
    }
    double fontSize = 19;
    final titleStyle = TextStyle(fontSize: fontSize, color: Colors.white);
    return Text(title, style: titleStyle);
  }
}

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0