LoginSignup
1
2

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

Last updated at Posted at 2023-08-21

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

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

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

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

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

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

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

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

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

1) マルチスクリーン事件の発生を検知したい

Flutter のAndroidディレクトリの MainActivity.ktファイルに以下のソースを追加します。

MainActivity.kt

class MainActivity: FlutterActivity() {

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

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

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

2) Flutter側に通知を送りたい

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

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

MultiScreenCheckPlugin.kt
/** MultiScreenCheckPlugin */

class MultiScreenCheckPlugin : FlutterPlugin, MethodCallHandler {

}

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

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 クラス内に、送信メソットを追加実装します。

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ファイルの全部内容は以下となります。

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ファイルに戻って、せっかく作成されたフラグインを登録しておきます。

MainActivity.kt

    private var checkerPlugin = MultiScreenCheckPlugin()

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

        flutterEngine.plugins.add(checkerPlugin)
    }

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

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

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を実行します。

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

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

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

  PlatformHandler(this.ref) {}
}

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

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

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

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

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

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()定義させる必要があります。

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

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

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(
...

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

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ファイルの全部内容になります。

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にアップロードしましたので、ご確認頂ければと思います。

1
2
0

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
  3. You can use dark theme
What you can do with signing up
1
2