FlutterでAndroid端末のmulti-windowモードを検知したい


暫くQiitaを書いてませんでした。

最近のあるFlutterアプリ開発案件ですが、Androidがマルチスクリーンモードを入ると、画面の部品が崩れる事象が発生してました。

一応、Flutter側はマルチスクリーンを検知できることはない感じ...

ぐぐって見たが、
一応、Android端末のマルチスクリーンを検知できることは可能であるようですが、詳しい説明がなくてこちらで、詳細な解決案と、実装を紹介したいと思います。

Qiitaには、昔以下のような紹介がありました。

https://qiita.com/Shiozawa/items/24755e0a0eab9756a79f

但し、記事の作成時間が古くて、あんまりの助けにならない感じ???

以下のAndrodi開発者公式サイトでは、詳しく説明したようが、実装例がないぽい
https://developer.android.com/guide/topics/ui/multi-window?hl=ja

一応、実機環境では
minSDKVersion 21 
targetSDKVersion 3３
ではサポートされなかったので、それぞれ24,34にあげておきました。

では、実際の実装に進めたいと思います。

### １）　マルチスクリーン事件の発生を検知したい
Flutter のAndroidディレクトリの MainActivity.ktファイルに以下のソースを追加します。


```kotlin:MainActivity.kt

class MainActivity: FlutterActivity() {

    override
    fun onMultiWindowModeChanged(isInMutiWindowMode: Boolean) {
        super.onMultiWindowModeChanged(isInMutiWindowMode)

        Log.d(TAG, "onMultiWindowModeChanged $isInMutiWindowMode")
        
    }
}
```

上記実装により、Android向けにビルドされたアプリがマルチスクリーンモードに入ってるか、抜けてるかの通知を検知できるようになりました。

### 2）　Flutter側に通知を送りたい
次に検知された、マルチスクリーン事件を、Flutterアプリに通知する仕組みを作りたいと思います。

同じMainActivity.ktファイルが存在するフォルダー下に、以下のようにフラグインファイルを新規作成します。

```kotlin:MultiScreenCheckPlugin.kt
/** MultiScreenCheckPlugin */

class MultiScreenCheckPlugin : FlutterPlugin, MethodCallHandler {

}

```

Flutterと情報交換するには、MethodChannelというものを利用してますが、Qiitaにも色々詳しく紹介している記事が多いので、こちらは割愛させていただきます。
以下のように、フラグインの基本構造を実装します。

```kotlin:MultiScreenCheckPlugin.kt
/** MultiScreenCheckPlugin */

class MultiScreenCheckPlugin : FlutterPlugin, MethodCallHandler {
    companion object {
        private const val CHANNEL_NAME = "com.example.android_multi_screen_checker/methodchannel"
        private const val METHOD_SPLIT_SCREEN_STATUS = "splitScreenStatus"
    }
    private var pendingResult: MethodChannel.Result? = null
    private lateinit var messenger: BinaryMessenger
    private lateinit var methodChannel: MethodChannel

    override
    fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        messenger = flutterPluginBinding.binaryMessenger
        methodChannel = MethodChannel(messenger, CHANNEL_NAME)
        methodChannel.setMethodCallHandler(this)
    }

    override
    fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
        Log.d(TAG, "onDetachedFromEngine!!!!!")
        methodChannel.setMethodCallHandler(null)
    }

    override
    fun onMethodCall(@NonNull call: MethodCall,
                     @NonNull result: MethodChannel.Result ) {
        Log.d(TAG, "onMethodCall!!!!!")

    }

}

```

もし、Flutter側から、呼ばれたメソットを受けて処理する場合は、onMethodCall()中に弄らなければならないですが、
今度のように、AndroidのPlatformから発生されたイベントをFlutterへ送るようになったので、このonMethodCall()は空ままにしておきます。

上記の　MultiScreenCheckPlugin　クラス内に、送信メソットを追加実装します。


```kotlin:MultiScreenCheckPlugin.kt
    public fun sendSplitScreenStatus(isSplitScreen: Boolean) {
        if(methodChannel == null) {
            Log.d(TAG, "methodChannel is null")
            return
        }
        Log.d(TAG, "sendSplitScreenStatus $isSplitScreen")
        methodChannel.invokeMethod(METHOD_SPLIT_SCREEN_STATUS, isSplitScreen)
    }

```

このままだと、ビルドに大変怒られるので、必要なライブラリをそれぞれimportしなければなりませんね...

一応、MultiScreenCheckPlugin.ktファイルの全部内容は以下となります。

```kotlin:MultiScreenCheckPlugin.kt
package jp.co.origon.android_multi_screen_checker


import android.util.Log
import androidx.annotation.NonNull

import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.BinaryMessenger

import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result


/** MultiScreenCheckPlugin */

class MultiScreenCheckPlugin : FlutterPlugin, MethodCallHandler {
    private val TAG = "MultiScreenCheckPlugin"

    companion object {
        private const val CHANNEL_NAME = "com.example.android_multi_screen_checker/methodchannel"
        private const val METHOD_SPLIT_SCREEN_STATUS = "splitScreenStatus"
    }

    private var pendingResult: MethodChannel.Result? = null
    private lateinit var messenger: BinaryMessenger
    private lateinit var methodChannel: MethodChannel

    override
    fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        messenger = flutterPluginBinding.binaryMessenger
        methodChannel = MethodChannel(messenger, CHANNEL_NAME)
        methodChannel.setMethodCallHandler(this)
    }

    override
    fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
        Log.d(TAG, "onDetachedFromEngine!!!!!")
        methodChannel.setMethodCallHandler(null)
    }

    override
    fun onMethodCall(@NonNull call: MethodCall,
                     @NonNull result: MethodChannel.Result ) {
        Log.d(TAG, "onMethodCall!!!!!")
    }

    public fun sendSplitScreenStatus(isSplitScreen: Boolean) {
        if(methodChannel == null) {
            Log.d(TAG, "methodChannel is null")
            return
        }
        Log.d(TAG, "sendSplitScreenStatus $isSplitScreen")
        methodChannel.invokeMethod(METHOD_SPLIT_SCREEN_STATUS, isSplitScreen)
    }
}

```


今度は、MainActivity.ktファイルに戻って、せっかく作成されたフラグインを登録しておきます。


```kotlin:MainActivity.kt

    private var checkerPlugin = MultiScreenCheckPlugin()

    override
    fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)

        flutterEngine.plugins.add(checkerPlugin)
    }

```

同じく、こちらもそれぞれの必要なライブラリをimportしなければなりません。

一応、MainActivity.ktの全体内容は以下となります。


```kotlin:MainActivity.kt
package jp.co.origon.android_multi_screen_checker

import android.util.Log
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant

class MainActivity: FlutterActivity() {
    private val TAG = "MainActivity"

    private var checkerPlugin = MultiScreenCheckPlugin()

    override
    fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)

        flutterEngine.plugins.add(checkerPlugin)
    }

    override
    fun onMultiWindowModeChanged(isInMutiWindowMode: Boolean) {
        super.onMultiWindowModeChanged(isInMutiWindowMode)

        Log.d(TAG, "onMultiWindowModeChanged $isInMutiWindowMode")
        checkerPlugin.sendSplitScreenStatus(isInMutiWindowMode)
    }

}

```

上記の実装で、Androud Platformからのマルチスクリーン事件の送信は何となくできました。

以下から、Flutter側の受信処理を引き続き、紹介します。


### 3）　Flutter側でマルチスクリーンの変化を感じたい

まずは、flutter_riverpodと言う状態管理ライブラリを導入して実装進めたいと思います。(他にも色々の状態管理ライブラリがありますが、好みに選んでも構いませんよ〜)
Pubspec.yaml に以下のようflutter_riverpodライブラリを登録して、
Pub getを実行します。


```ruby:pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
 ...
 flutter_riverpod: ^2.3.6
 ...
```

次に、Dartのlibフォルダー下にAndroid　Platformから、送信されるイベントを受信する機能の
platform_handler.dartファイルを作成して、PlatformHandlerクラスを実装します。


```dart:platform_handler.dart
...
import 'package:flutter_riverpod/flutter_riverpod.dart';
...
class PlatformHandler {
  final Ref ref;

  PlatformHandler(this.ref) {}
}

```
上記クラス外に、マルチスクリーン状態を保存する、状態プロバイダと
PlatformHandlerを管理するプロバイダをそれぞれ実装しておきます。

```dart:platform_handler.dart
final isSplitScreenMode = StateProvider<bool>((ref) => false);

final platformHandler = Provider<PlatformHandler>((ref) {
  return PlatformHandler(ref);
});
```

Flutter側が受信されるChannelのCallbackメソットを初期化メソットをそれぞれ実装します。

作成したファイルと実装内容の以下になります。


```dart:platform_handler.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final isSplitScreenMode = StateProvider<bool>((ref) => false);

final platformHandler = Provider<PlatformHandler>((ref) {
  return PlatformHandler(ref);
});

const CHANNEL_NAME = "com.example.android_multi_screen_checker/methodchannel";
const METHOD_SPLIT_SCREEN_STATUS = "splitScreenStatus";

class PlatformHandler {
  final Ref ref;

  PlatformHandler(this.ref) {}

  void initialize() {
    debugPrint('PlatformHandler() init');
    const channel = MethodChannel(CHANNEL_NAME);
    channel.setMethodCallHandler(_methodHandle);
  }

  Future<dynamic> _methodHandle(MethodCall call) async {
    debugPrint('_methodHandle() MethodCall: $call');
    if (call.method == METHOD_SPLIT_SCREEN_STATUS) {
      final isSplitScreen = call.arguments as bool;
      ref.read(isSplitScreenMode.notifier).state = isSplitScreen;
      return Future.value(isSplitScreen);
    }
    return null;
  }
}
```

上記実装により、受信された状態値がisSplitScreenModeに保存されるはずです。

これで、上記のisSplitScreenMode変化を監視して、必要な制御が可能になりました。

以下は、AppBarに状態変化により、表示文言を変える実装にしたいと思いまう。

### 4）　Flutter画面にマルチスクリーンの変化を表示させよう

一先ず、flutter_riverpodを投入することで、main.dartファイルに
ProviderScope()定義させる必要があります。

```dart:main.dart
void main() {
  runApp(const ProviderScope(child: MyApp()));
}
```

アプリ起動の時にplatformHandlerの初期化を登録します。

```dart:main.dart
...
class MyApp extends ConsumerWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    ref.read(platformHandler).initialize();
    return MaterialApp(
...

```

以下、アプリタイトルバーに表示される文言がマルチスクリーン状況により変化される実層を追加します。

```dart:main.dart
...
class MyHomePage extends ConsumerWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
        appBar: AppBar(
            centerTitle: true,
            title: Text(ref.watch(isSplitScreenMode)
                ? 'is in Split Screen mode'
                : 'is NOT in Split Screen mode')),
        body: Container(),

...

```

以下、実装完了したmain.dartファイルの全部内容になります。

```dart:main.dart
import 'package:android_multi_screen_checker/platform_handler.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    ref.read(platformHandler).initialize();
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Android Split Screen Checker',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
        appBar: AppBar(
            centerTitle: true,
            title: Text(ref.watch(isSplitScreenMode)
                ? 'is in Split Screen mode'
                : 'is NOT in Split Screen mode')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('Android Split screen mode\n support SDK API 24 later',
                  style: Theme.of(context).textTheme.headlineMedium,
                  textAlign: TextAlign.center),
              const SizedBox(height: 50),
              const Text(
                  'if you any question pls contact me info@origon.co.jp'),
            ],
          ),
        ),
    );
  }
}

```


以上、色々書かれて長文になりましたが、お助けになれれば大変幸いと思います。
実装ソースは以下のGithubにアップロードしましたので、ご確認頂ければと思います。

https://github.com/origonjp/android_multi_screen_checker/tree/main