diff --git a/README.md b/README.md index 7a1584d3..306c9af9 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,9 @@ void overlayMain() { /// closes overlay if open await FlutterOverlayWindow.closeOverlay(); - /// broadcast data to and from overlay app + /// Broadcast data to and from overlay app. + /// This method may return `false` when invoked from the overlay while the application is closed. + /// Returns `true` if the data was sent successfully, otherwise `false`. await FlutterOverlayWindow.shareData("Hello from the other side"); /// streams message shared between overlay and main app @@ -117,6 +119,15 @@ void overlayMain() { log("Current Event: $event"); }); + /// Overlay status stream. + /// Emit `true` when overlay is showing, and `false` when overlay is closed. + /// Emit value only once for every state change. + /// Doesn't emit a change when the overlay is already showing and [showOverlay] is called, + /// as in this case the overlay will almost immediately reopen. + FlutterOverlayWindow.overlayStatusListener.listen((event) { + print("Overlay status: $event"); + }); + /// use [OverlayFlag.focusPointer] when you want to use fields that show keyboards await FlutterOverlayWindow.showOverlay(flag: OverlayFlag.focusPointer); diff --git a/android/src/main/java/flutter/overlay/window/flutter_overlay_window/CachedMessageChannels.java b/android/src/main/java/flutter/overlay/window/flutter_overlay_window/CachedMessageChannels.java new file mode 100644 index 00000000..a26d4e43 --- /dev/null +++ b/android/src/main/java/flutter/overlay/window/flutter_overlay_window/CachedMessageChannels.java @@ -0,0 +1,11 @@ +package flutter.overlay.window.flutter_overlay_window; + +import androidx.annotation.Nullable; +import io.flutter.plugin.common.MethodChannel; + +public abstract class CachedMessageChannels { + @Nullable + public static MethodChannel mainAppMessageChannel; + @Nullable + public static MethodChannel overlayMessageChannel; +} diff --git a/android/src/main/java/flutter/overlay/window/flutter_overlay_window/FlutterOverlayWindowPlugin.java b/android/src/main/java/flutter/overlay/window/flutter_overlay_window/FlutterOverlayWindowPlugin.java index 64f193bd..919ac198 100644 --- a/android/src/main/java/flutter/overlay/window/flutter_overlay_window/FlutterOverlayWindowPlugin.java +++ b/android/src/main/java/flutter/overlay/window/flutter_overlay_window/FlutterOverlayWindowPlugin.java @@ -1,12 +1,12 @@ package flutter.overlay.window.flutter_overlay_window; import android.app.Activity; -import android.app.NotificationManager; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.provider.Settings; + import android.service.notification.StatusBarNotification; import android.util.Log; import android.view.WindowManager; @@ -16,18 +16,19 @@ import androidx.annotation.RequiresApi; import androidx.core.app.NotificationManagerCompat; +import java.util.Objects; import java.util.Map; import io.flutter.FlutterInjector; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngineCache; import io.flutter.embedding.engine.FlutterEngineGroup; +import io.flutter.embedding.engine.FlutterEngineGroupCache; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import io.flutter.plugin.common.BasicMessageChannel; -import io.flutter.plugin.common.JSONMessageCodec; +import io.flutter.plugin.common.JSONMethodCodec; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; @@ -35,28 +36,30 @@ import io.flutter.plugin.common.PluginRegistry; public class FlutterOverlayWindowPlugin implements - FlutterPlugin, ActivityAware, BasicMessageChannel.MessageHandler, MethodCallHandler, + FlutterPlugin, ActivityAware, MethodCallHandler, PluginRegistry.ActivityResultListener { private MethodChannel channel; private Context context; private Activity mActivity; - private BasicMessageChannel messenger; private Result pendingResult; final int REQUEST_CODE_FOR_OVERLAY_PERMISSION = 1248; + @Nullable + FlutterPluginBinding flutterBinding; + @Nullable + ActivityPluginBinding activityPluginBinding; + boolean isMainAppEngine; @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { + flutterBinding = flutterPluginBinding; this.context = flutterPluginBinding.getApplicationContext(); channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), OverlayConstants.CHANNEL_TAG); channel.setMethodCallHandler(this); - messenger = new BasicMessageChannel(flutterPluginBinding.getBinaryMessenger(), OverlayConstants.MESSENGER_TAG, - JSONMessageCodec.INSTANCE); - messenger.setMessageHandler(this); - - WindowSetup.messenger = messenger; - WindowSetup.messenger.setMessageHandler(this); + FlutterEngineGroup overlayEngineGroup = ensureEngineGroupCreated(context); + isMainAppEngine = flutterBinding.getEngineGroup() != overlayEngineGroup; + registerMessageChannel(isMainAppEngine); } @RequiresApi(api = Build.VERSION_CODES.N) @@ -78,6 +81,9 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { result.error("PERMISSION", "overlay permission is not enabled", null); return; } + + ensureEngineCreated(context); + Integer height = call.argument("height"); Integer width = call.argument("width"); String alignment = call.argument("alignment"); @@ -131,50 +137,121 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { } else { result.notImplemented(); } - } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { channel.setMethodCallHandler(null); - WindowSetup.messenger.setMessageHandler(null); + flutterBinding = null; + unregisterMessageChannel(isMainAppEngine); + FlutterEngineGroupCache.getInstance().remove(OverlayConstants.CACHED_TAG); } @Override public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + activityPluginBinding = binding; mActivity = binding.getActivity(); - if (FlutterEngineCache.getInstance().get(OverlayConstants.CACHED_TAG) == null) { - FlutterEngineGroup enn = new FlutterEngineGroup(context); - DartExecutor.DartEntrypoint dEntry = new DartExecutor.DartEntrypoint( - FlutterInjector.instance().flutterLoader().findAppBundlePath(), - "overlayMain"); - FlutterEngine engine = enn.createAndRunEngine(context, dEntry); - FlutterEngineCache.getInstance().put(OverlayConstants.CACHED_TAG, engine); - } + + ensureEngineCreated(context); + + binding.addActivityResultListener(this); } @Override public void onDetachedFromActivityForConfigChanges() { + onDetachedFromActivity(); } @Override public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { - this.mActivity = binding.getActivity(); + onAttachedToActivity(binding); } @Override public void onDetachedFromActivity() { + Objects.requireNonNull(activityPluginBinding).removeActivityResultListener(this); + activityPluginBinding = null; + mActivity = null; } - @Override - public void onMessage(@Nullable Object message, @NonNull BasicMessageChannel.Reply reply) { - BasicMessageChannel overlayMessageChannel = new BasicMessageChannel( - FlutterEngineCache.getInstance().get(OverlayConstants.CACHED_TAG) - .getDartExecutor(), - OverlayConstants.MESSENGER_TAG, JSONMessageCodec.INSTANCE); - overlayMessageChannel.send(message, reply); + private void registerMessageChannel(boolean isMainAppEngine) { + io.flutter.plugin.common.BinaryMessenger binaryMessenger = Objects.requireNonNull(flutterBinding).getBinaryMessenger(); + if(isMainAppEngine) { + registerMainAppMessageChannel(binaryMessenger); + } else { + registerOverlayMessageChannel(binaryMessenger); + } + } + + private void unregisterMessageChannel(boolean isMainAppEngine) { + if(isMainAppEngine) { + if (CachedMessageChannels.mainAppMessageChannel == null) return; + CachedMessageChannels.mainAppMessageChannel.setMethodCallHandler(null); + CachedMessageChannels.mainAppMessageChannel = null; + } else { + if(CachedMessageChannels.overlayMessageChannel == null) return; + CachedMessageChannels.overlayMessageChannel.setMethodCallHandler(null); + CachedMessageChannels.overlayMessageChannel = null; + } } + private void registerOverlayMessageChannel(io.flutter.plugin.common.BinaryMessenger overlyEngineBinaryMessenger) { + MethodChannel overlayMessageChannel = new MethodChannel(overlyEngineBinaryMessenger, OverlayConstants.MESSENGER_TAG, JSONMethodCodec.INSTANCE); + overlayMessageChannel.setMethodCallHandler((call, result) -> { + if (CachedMessageChannels.mainAppMessageChannel == null) { + result.success(false); + return; + } + CachedMessageChannels.mainAppMessageChannel.invokeMethod("message", call.arguments); + result.success(true); + }); + CachedMessageChannels.overlayMessageChannel = overlayMessageChannel; + } + + private void registerMainAppMessageChannel(io.flutter.plugin.common.BinaryMessenger mainAppEngineBinaryMessenger) { + MethodChannel mainAppMessageChannel = new MethodChannel(mainAppEngineBinaryMessenger, OverlayConstants.MESSENGER_TAG, JSONMethodCodec.INSTANCE); + mainAppMessageChannel.setMethodCallHandler((call, result) -> { + if (CachedMessageChannels.overlayMessageChannel == null) { + result.success(false); + return; + } + CachedMessageChannels.overlayMessageChannel.invokeMethod("message", call.arguments); + result.success(true); + }); + CachedMessageChannels.mainAppMessageChannel = mainAppMessageChannel; + } + + private FlutterEngineGroup ensureEngineGroupCreated(android.content.Context context) { + FlutterEngineGroup enn = FlutterEngineGroupCache.getInstance().get(OverlayConstants.CACHED_TAG); + + if(enn == null) { + enn = new FlutterEngineGroup(context); + FlutterEngineGroupCache.getInstance().put(OverlayConstants.CACHED_TAG, enn); + } + + return enn; + } + private void ensureEngineCreated(android.content.Context context) { + FlutterEngine engine = FlutterEngineCache.getInstance().get(OverlayConstants.CACHED_TAG); + if(engine == null) { + FlutterEngineGroup enn = ensureEngineGroupCreated(context); + DartExecutor.DartEntrypoint dEntry = new DartExecutor.DartEntrypoint( + FlutterInjector.instance().flutterLoader().findAppBundlePath(), + "overlayMain"); + engine = Objects.requireNonNull(enn).createAndRunEngine(context, dEntry); + FlutterEngineCache.getInstance().put(OverlayConstants.CACHED_TAG, engine); + engine.addEngineLifecycleListener(new FlutterEngine.EngineLifecycleListener() { + @Override + public void onPreEngineRestart() { + + } + @Override + public void onEngineWillDestroy() { + FlutterEngineCache.getInstance().remove(OverlayConstants.CACHED_TAG); + } + }); + } + } private boolean checkOverlayPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return Settings.canDrawOverlays(context); diff --git a/android/src/main/java/flutter/overlay/window/flutter_overlay_window/OverlayService.java b/android/src/main/java/flutter/overlay/window/flutter_overlay_window/OverlayService.java index 265421ec..c543ed5c 100644 --- a/android/src/main/java/flutter/overlay/window/flutter_overlay_window/OverlayService.java +++ b/android/src/main/java/flutter/overlay/window/flutter_overlay_window/OverlayService.java @@ -37,8 +37,6 @@ import io.flutter.embedding.android.FlutterView; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngineCache; -import io.flutter.plugin.common.BasicMessageChannel; -import io.flutter.plugin.common.JSONMessageCodec; import io.flutter.plugin.common.MethodChannel; public class OverlayService extends Service implements View.OnTouchListener { @@ -56,7 +54,6 @@ public class OverlayService extends Service implements View.OnTouchListener { private WindowManager windowManager = null; private FlutterView flutterView; private MethodChannel flutterChannel = new MethodChannel(FlutterEngineCache.getInstance().get(OverlayConstants.CACHED_TAG).getDartExecutor(), OverlayConstants.OVERLAY_TAG); - private BasicMessageChannel overlayMessageChannel = new BasicMessageChannel(FlutterEngineCache.getInstance().get(OverlayConstants.CACHED_TAG).getDartExecutor(), OverlayConstants.MESSENGER_TAG, JSONMessageCodec.INSTANCE); private int clickableFlag = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; @@ -79,15 +76,19 @@ public IBinder onBind(Intent intent) { @Override public void onDestroy() { Log.d("OverLay", "Destroying the overlay window service"); - if (windowManager != null) { + isRunning = false; + if(windowManager != null && flutterView != null) { windowManager.removeView(flutterView); windowManager = null; + } + if(flutterView != null) { flutterView.detachFromFlutterEngine(); flutterView = null; } - isRunning = false; + OverlayStatusEmitter.emitIsShowing(false); NotificationManager notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.cancel(OverlayConstants.NOTIFICATION_ID); + instance = null; } @@ -99,20 +100,16 @@ public int onStartCommand(Intent intent, int flags, int startId) { int startY = intent.getIntExtra("startY", OverlayConstants.DEFAULT_XY); boolean isCloseWindow = intent.getBooleanExtra(INTENT_EXTRA_IS_CLOSE_WINDOW, false); if (isCloseWindow) { - if (windowManager != null) { - windowManager.removeView(flutterView); - windowManager = null; - flutterView.detachFromFlutterEngine(); - stopSelf(); - } - isRunning = false; + stopSelf(); return START_STICKY; } - if (windowManager != null) { + if(windowManager != null && flutterView != null) { windowManager.removeView(flutterView); windowManager = null; + } + if(flutterView != null) { flutterView.detachFromFlutterEngine(); - stopSelf(); + flutterView = null; } isRunning = true; Log.d("onStartCommand", "Service started"); @@ -139,9 +136,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { resizeOverlay(width, height, enableDrag, result); } }); - overlayMessageChannel.setMessageHandler((message, reply) -> { - WindowSetup.messenger.send(message); - }); + windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { @@ -174,6 +169,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { flutterView.setOnTouchListener(this); windowManager.addView(flutterView, params); moveOverlay(dx, dy, null); + OverlayStatusEmitter.emitIsShowing(true); return START_STICKY; } diff --git a/android/src/main/java/flutter/overlay/window/flutter_overlay_window/OverlayStatusEmitter.java b/android/src/main/java/flutter/overlay/window/flutter_overlay_window/OverlayStatusEmitter.java new file mode 100644 index 00000000..9e60b2c8 --- /dev/null +++ b/android/src/main/java/flutter/overlay/window/flutter_overlay_window/OverlayStatusEmitter.java @@ -0,0 +1,17 @@ +package flutter.overlay.window.flutter_overlay_window; + +public abstract class OverlayStatusEmitter { + static private final String methodName = "isShowingOverlay"; + static private boolean lastEmittedStatus; + + static void emitIsShowing(boolean isShowing) { + if(isShowing == lastEmittedStatus) return; + lastEmittedStatus = isShowing; + if(CachedMessageChannels.mainAppMessageChannel != null) { + CachedMessageChannels.mainAppMessageChannel.invokeMethod(methodName, isShowing); + } + if(CachedMessageChannels.overlayMessageChannel != null) { + CachedMessageChannels.overlayMessageChannel.invokeMethod(methodName, isShowing); + } + } +} diff --git a/android/src/main/java/flutter/overlay/window/flutter_overlay_window/WindowSetup.java b/android/src/main/java/flutter/overlay/window/flutter_overlay_window/WindowSetup.java index 3563a1ca..1cfdeb08 100644 --- a/android/src/main/java/flutter/overlay/window/flutter_overlay_window/WindowSetup.java +++ b/android/src/main/java/flutter/overlay/window/flutter_overlay_window/WindowSetup.java @@ -3,18 +3,14 @@ import android.view.Gravity; import android.view.WindowManager; - import androidx.core.app.NotificationCompat; -import io.flutter.plugin.common.BasicMessageChannel; - public abstract class WindowSetup { static int height = WindowManager.LayoutParams.MATCH_PARENT; static int width = WindowManager.LayoutParams.MATCH_PARENT; static int flag = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; static int gravity = Gravity.CENTER; - static BasicMessageChannel messenger = null; static String overlayTitle = "Overlay is activated"; static String overlayContent = "Tap to edit settings or disable"; static String positionGravity = "none"; diff --git a/example/lib/home_page.dart b/example/lib/home_page.dart index 9080a7af..108c140c 100644 --- a/example/lib/home_page.dart +++ b/example/lib/home_page.dart @@ -1,6 +1,5 @@ +import 'dart:async'; import 'dart:developer'; -import 'dart:isolate'; -import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_overlay_window/flutter_overlay_window.dart'; @@ -13,29 +12,26 @@ class HomePage extends StatefulWidget { } class _HomePageState extends State { - static const String _kPortNameOverlay = 'OVERLAY'; - static const String _kPortNameHome = 'UI'; - final _receivePort = ReceivePort(); - SendPort? homePort; + StreamSubscription? _overlaySubscription; String? latestMessageFromOverlay; @override void initState() { super.initState(); - if (homePort != null) return; - final res = IsolateNameServer.registerPortWithName( - _receivePort.sendPort, - _kPortNameHome, - ); - log("$res: OVERLAY"); - _receivePort.listen((message) { - log("message from OVERLAY: $message"); + _overlaySubscription = FlutterOverlayWindow.overlayListener.listen((message) { + if(!mounted) return; setState(() { latestMessageFromOverlay = 'Latest Message From Overlay: $message'; }); }); } + @override + void dispose() { + _overlaySubscription?.cancel(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -110,9 +106,7 @@ class _HomePageState extends State { const SizedBox(height: 20.0), TextButton( onPressed: () { - homePort ??= - IsolateNameServer.lookupPortByName(_kPortNameOverlay); - homePort?.send('Send to overlay: ${DateTime.now()}'); + FlutterOverlayWindow.shareData('Send to overlay: ${DateTime.now()}'); }, child: const Text("Send message to overlay"), ), diff --git a/example/lib/overlays/messanger_chathead.dart b/example/lib/overlays/messanger_chathead.dart index 87f2b4c3..7c36c905 100644 --- a/example/lib/overlays/messanger_chathead.dart +++ b/example/lib/overlays/messanger_chathead.dart @@ -1,6 +1,4 @@ -import 'dart:developer'; -import 'dart:isolate'; -import 'dart:ui'; +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_overlay_window/flutter_overlay_window.dart'; @@ -13,31 +11,28 @@ class MessangerChatHead extends StatefulWidget { } class _MessangerChatHeadState extends State { + StreamSubscription? _overlaySubscription; Color color = const Color(0xFFFFFFFF); BoxShape _currentShape = BoxShape.circle; - static const String _kPortNameOverlay = 'OVERLAY'; - static const String _kPortNameHome = 'UI'; - final _receivePort = ReceivePort(); - SendPort? homePort; String? messageFromOverlay; @override void initState() { super.initState(); - if (homePort != null) return; - final res = IsolateNameServer.registerPortWithName( - _receivePort.sendPort, - _kPortNameOverlay, - ); - log("$res : HOME"); - _receivePort.listen((message) { - log("message from UI: $message"); + _overlaySubscription = FlutterOverlayWindow.overlayListener.listen((message) { + if(!mounted) return; setState(() { messageFromOverlay = 'message from UI: $message'; }); }); } + @override + void dispose() { + _overlaySubscription?.cancel(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Material( @@ -80,10 +75,7 @@ class _MessangerChatHeadState extends State { backgroundColor: Colors.black, ), onPressed: () { - homePort ??= IsolateNameServer.lookupPortByName( - _kPortNameHome, - ); - homePort?.send('Date: ${DateTime.now()}'); + FlutterOverlayWindow.shareData('Date: ${DateTime.now()}'); }, child: const Text("Send message to UI"), ), diff --git a/example/pubspec.lock b/example/pubspec.lock index c7278dbf..e0e9f20c 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -100,7 +100,7 @@ packages: path: ".." relative: true source: path - version: "0.4.3" + version: "0.4.4" flutter_test: dependency: "direct dev" description: flutter diff --git a/lib/src/overlay_window.dart b/lib/src/overlay_window.dart index fef7d45d..00e8ba70 100644 --- a/lib/src/overlay_window.dart +++ b/lib/src/overlay_window.dart @@ -8,13 +8,13 @@ import 'package:flutter_overlay_window/src/overlay_config.dart'; class FlutterOverlayWindow { FlutterOverlayWindow._(); - static final StreamController _controller = StreamController(); - static const MethodChannel _channel = - MethodChannel("x-slayer/overlay_channel"); - static const MethodChannel _overlayChannel = - MethodChannel("x-slayer/overlay"); - static const BasicMessageChannel _overlayMessageChannel = - BasicMessageChannel("x-slayer/overlay_messenger", JSONMessageCodec()); + static final _controller = StreamController.broadcast(); + static final _controllerOverlayStatus = StreamController.broadcast(); + + static const _channel = MethodChannel("x-slayer/overlay_channel"); + static const _overlayChannel = MethodChannel("x-slayer/overlay"); + static const _overlayMessageChannel = + MethodChannel("x-slayer/overlay_messenger", JSONMethodCodec()); /// Open overLay content /// @@ -95,20 +95,40 @@ class FlutterOverlayWindow { return _res; } - /// Broadcast data to and from overlay app - static Future shareData(dynamic data) async { - return await _overlayMessageChannel.send(data); + /// Broadcast [data] to and from overlay app. + /// + /// If `true` is returned, it indicates that the [data] was sent. However, this doesn't mean + /// that the [data] has already reached the listeners of the [overlayListener] stream. + /// + /// If `false` is returned, it indicates that the [data] was not sent. + /// + /// This method may return `false` when invoked from the overlay while the application is closed. + /// + /// Returns `true` if the [data] was sent successfully, otherwise `false`. + static Future shareData(dynamic data) async { + final isSent = await _overlayMessageChannel.invokeMethod('', data); + return isSent as bool; } /// Streams message shared between overlay and main app static Stream get overlayListener { - _overlayMessageChannel.setMessageHandler((message) async { - _controller.add(message); - return message; - }); + _registerOverlayMessageHandler(); return _controller.stream; } + /// Overlay status stream. + /// + /// Emit `true` when overlay is showing, and `false` when overlay is closed. + /// + /// Emit value only once for every state change. + /// + /// Doesn't emit a change when the overlay is already showing and [showOverlay] is called, + /// as in this case the overlay will almost immediately reopen. + static Stream get overlayStatusListener { + _registerOverlayMessageHandler(); + return _controllerOverlayStatus.stream; + } + /// Update the overlay flag while the overlay in action static Future updateFlag(OverlayFlag flag) async { final bool? _res = await _overlayChannel @@ -162,8 +182,23 @@ class FlutterOverlayWindow { return _res ?? false; } - /// Dispose overlay stream - static void disposeOverlayListener() { - _controller.close(); + static void _registerOverlayMessageHandler() { + _overlayMessageChannel.setMethodCallHandler((call) async { + switch (call.method) { + case 'isShowingOverlay': + _controllerOverlayStatus.add(call.arguments as bool); + break; + case 'message': + _controller.add(call.arguments); + break; + } + }); + } + + /// Dispose overlay stream. + /// + /// Once disposed, only a complete restart of the application will re-initialize the listener. + static Future disposeOverlayListener() { + return _controller.close(); } }