A Flutter plugin that makes it easier to make floating/overlay windows for Android with pure Flutter

Overview

flutter_floatwing

Version pub points popularity likes License

A Flutter plugin that makes it easier to make floating/overlay windows for Android with pure Flutter. Android only


Features

  • Pure Flutter: you can write your whole overlay windows in pure Flutter.
  • Simple: at least only 1 line of code to start your overlay window.
  • Auto Resize: just care about your Flutter widget size, it will auto resize for Android view.
  • Multi-window: support create multiple overlay windows in one App, and window can has child windows.
  • Communicable: your main App can talk with windows, and also supported between windows.
  • Event Mechanism: fire the events of window lifecyle and other actions like drag, you can controll your window more flexible.
  • and more features are coming ...

Previews

Night mode Simpe example Assistive touch mock

Installtion

Open the pubspec.yaml file located inside the app folder, and add flutter_floatwing under dependencies.

dependencies:
  flutter_floatwing: 

The latest version is Version

Then you should install it,

  • From the terminal: Run flutter pub get.
  • From Android Studio/IntelliJ: Click Packages get in the action ribbon at the top of pubspec.yaml.
  • From VS Code: Click Get Packages located in right side of the action ribbon at the top of pubspec.yaml.

Or simply add it in your command line:

flutter pub add flutter_floatwing

Quick Started

We use the android's system alert window to display, so need to add the permission in AndroidManifest.xml first:

">
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

Add a route for your widget which will be displayed in the overlay window:

HomePage(), // add a route as entrypoint for your overlay window. "/my-overlay-window": (_) => MyOverlayWindow(), }, ); }">
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      initialRoute: "/",
      routes: {
          "/": (_) => HomePage(),
          // add a route as entrypoint for your overlay window.
          "/my-overlay-window": (_) => MyOverlayWindow(),
      },
    );
  }

Before we start the floating window, we need to check and request the permission, and initialize the flutter_floatwing plugin in initState or any button's callback function:

// check and grant the system alert window permission.
FloatwingPlugin().checkPermission().then((v) {
    if (!v) FloatwingPlugin().openPermissionSetting();
});

// initialize the plugin at first.
FloatwingPlugin().initialize();

Next to start create and start your overlay window:

// define window config and start the window from config.
WindowConfig(route: "/my-overlay-window") 
    .to() // create a window object
    .create(start: true); // create the window and start the overlay window.

Notes:

  • route is one of 3 ways to define entrypoint for overlay window. Please check the Entrypoint section for more information.
  • See Usage section for more functions.

Architecture

Before we see how flutter_floatwing manage windows in detail, we need to know some things about the design of the plugin.

  • id is the unique identifier for the window, and all operations on the window are based on this id, you must provide one before creating.
  • We consider the first engine created by opening the main application as the main engine or plugin engine. The other engines created by service are window engine.
  • Different engine are different threads and cannot communicate directly.
  • Subscribe events of all windows from main engine is allowed, it's also allowed to subscribe events of own and child windows in window engine. But we can not subscribe events of sibling or parent windows.
  • share data is the only way to communicate between window engine or plugin engine, there are no restrictions on it, except that the data needs to be serializable. Which means you can share data from anywhere to anywhere.

A floatwing window object contains: a flutter engine which run a widget by runApp and a view which add to window manager.

floatwing window

The whole view hierarchy like below:

flutter floatwing architecture

Usage

Before we start how to use flutter_floatwing in detail, let's talk about how the flutter_floatwing create a new overlay window:

  • First of all we need to start a service as manager by main app.
  • Then create window request send to the service.
  • In the service, we start the flutter engine with entrypoint.
  • Create a new flutter view and attach it to the flutter engine.
  • Add the view to android window manager.

Window & Config

WindowConfig contains all configuration for window. We can use configuration to create a window like below:

void _createWindow() {
    var config = WindowConfig();
    w = Window(config, id="my-window");
    w.create();
}

If you have no need to register event or data handler, you can just use config to create a window.

void _createWindow() {
    WindowConfig(id="my-window").create();
}

But as you can see, if you want to provide a id for window, must provide in WindowConfig.

If want to register handler, you can use a to() function to turn a config to a window at first, this is every useful when you want to make code simple.

void _createWindow() {
    WindowConfig(id="my-window").to()
        .on(EventType.WindowCreated, (w, _) {})
        .create();
}

Lifecyle of Window

  • created
  • started
  • paused
  • resumed
  • destroy

TODO

Entrypoint

Entrypoint is where the engine execute from. We support 3 modes of configuration:

Name Config How to use
route WindowConfig(route: "/my-overlay") - Add a route for overlay window in your main routes
- Start window with config: WindowConfig(route: "/my-overlay")
staic function WindowConfig(callback: myOverlayMain) - Define a static function void Function() which calling runApp to start a widget.
- Start window with config: WindowConfig(callback: myOverlayMain)
entry-point WindowConfig(entry: "myOverlayMain") - First step is same as staic function.
- Add @pragma("vm:entry-point") above the static function.
- Start window with config: WindowConfig(entry: "myOverlayMain")
- like static function, but use string of function name as parameter

Example for route

  1. Add route for your overlay widget in the main application.
HomePage(), // add a route as entrypoint for your overlay window. "/my-overlay-window": (_) => MyOverlayWindow(), }, ); }">
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      initialRoute: "/",
      routes: {
          "/": (_) => HomePage(),
          // add a route as entrypoint for your overlay window.
          "/my-overlay-window": (_) => MyOverlayWindow(),
      },
    );
  }
  1. Start window with route as config.
    void _startWindow() {
        // define window config and start the window from config.
        WindowConfig(route: "/my-overlay-window") 
            .to() // create a window object
            .create(start: true); // create the window and start the overlay window.
    }

Example for static function

  1. Define a static function which called runApp
void myOverlayMain() {
    runApp(MaterialApp(
        home: AssistivePannel(),
    ));
    // or simply use `floatwing` method to inject `MaterialApp`
    // runApp(AssistivePannel().floatwing(app: true));
}
  1. Start window with callback as config.
    void _startWindow() {
        // define window config and start the window from config.
        WindowConfig(callback: myOverlayMain) 
            .to() // create a window object
            .create(start: true); // create the window and start the overlay window.
    }

Example for entry-point

  1. Define as static function which called runApp and add prama
@pragma("vm:entry-point")
void myOverlayMain() {
    runApp(MaterialApp(
        home: AssistivePannel(),
    ));
    // or simply use `floatwing` method to inject `MaterialApp`
    // runApp(AssistivePannel().floatwing(app: true));
}
  1. Start window with entry as config.
    void _startWindow() {
        // define window config and start the window from config.
        WindowConfig(entry: "myOverlayMain") 
            .to() // create a window object
            .create(start: true); // create the window and start the overlay window.
    }

Wrap your widget

For simple widget, you have no need to do with your widget. But if you want more functions and make your code clean, we provide a injector for your widget.

For now there are some functions listed below,

  • Auto resize the window view.
  • Auto sync and ensure the window.
  • Wrap a MaterialApp
  • more features are coming

Before, we write our overlay main function, like below,

void overlayMain() {
  runApp(MaterialApp(
    home: MyOverView(),
  ))
}

Now, we can code simply,

void overlayMain() {
  runApp(MyOverView().floatwing(app: true)))
}

We can wrap to a Widget and a WidgetBuilder, wrap the WidgetBuilder, we can access the window instance with Window.of(context), while FloatwingPlugin().currentWindow is the only to get window instance for wrap Widget.

If we want to access the window with Window.of(context), change the code like below,

void overlayMain() {
  runApp(((_)=>MyOverView()).floatwing(app: true).make()))
}

Access window in overlay window

In your window engine, you can access the window object in 2 ways:

  • Directly access the cache field of plugin: FloatwingPlugin().currentWindow.
  • If widget injected by .floatwing(), you can take window with Window.of(context).

FloatwingPlugin().currentWindow will return null unless initialize has been completed.

If you inject WidgetBuilder with .floatwing(), then you can access the current window. It will always return non-value, unless you enable debug with .floatwing(debug: true).

For example, if we want to get the id of current window, we can do it like below:

/// ...
import 'package:flutter_floatwing/flutter_floatwing.dart';

class _ExampleViewState extends State<ExampleView> {
    Window? w;

    @override
    void initState() {
        super.initState();
        SchedulerBinding.instance?.addPostFrameCallback((_) {
            w = Window.of(context);
            print("my window id is ${w.id}");
        });
    }
}

Subscribe events

We can subscribe events from windows, and trigger actions when events fired. Events of window will be sent to main engine, self window engine and the parent `window engine. Which means you can subscribe events of window from flutter of main application, overlay window and parent overlay window.

Currently we support events for window lifecycle and drag action.

enum EventType {
  WindowCreated,
  WindowStarted,
  WindowPaused,
  WindowResumed,
  WindowDestroy,

  WindowDragStart,
  WindowDragging,
  WindowDragEnd,
}

More events type are coming, and contributtions are welcome!

For example, we want to do something when the window is started, we can code like below:

  @override
  void initState() {
    super.initState();

    SchedulerBinding.instance?.addPostFrameCallback((_) {
      w = Window.of(context);
      w?.on(EventType.WindowStarted, (window, _) {
          print("$w has been started.");
      }).on(EventType.WindowDestroy, (window, data) {
          // data is a boolean value, which means that the window
          // are destroyed force or not.
          print("$w has been destroy, force $data");
      });
    });
  }

Share data with windows

Sharing data is the only way to communicate with windows. We provide a simple way to do this: window.share(data).

For example, if you want to share data to overlay window from main application.

First get the target window in main application, usually the created one can be used or you can get one from windows cache by id,

    Window w;

    void _startWindow() {
        w = WindowConfig(route: "/my-overlay-window").to();
    }

    void _shareData(dynamic data) {
        w.share(data).then((value) {
            // and window can return value.
        });
        // or just take one from cache
        // FloatwingPlugin().windows["default"]?.share(data);
    }

If you want to share data with a name, yon can add the name parameter: ``w.share(data, name="name-1")`.

And then you should listen the data in window by register the data handler.

  @override
  void initState() {
    super.initState();

    SchedulerBinding.instance?.addPostFrameCallback((_) {
      w = Window.of(context);
      w?.onData((source, name, data) async {
          print("get $name data from $source: $data");
      });
    });
  }

The function signature of handler is Future Function(String? source, String? name, dynamic data).

  • source is where the data comes from, null if from main application, from window will be the id of window.
  • name is the data name, you can share data for different purposes.
  • data is the data that you get.
  • return some value if you want to do.

There are restrictions for directions of communication, unless you send data to self, which will not be allowed. Which means you can send data as long as you know the id of window. Currently share to main application is not implemented.

Note: The data you are sharing should be serializable.

API References

FloatwingPlugin instance

FloatwingPlugin is a singleton class that returns the same instance every time it called FloatwingPlugin() factory method.

WindowConfig Object

TODO

Window Object

TODO

Events

Window lifecycle

Action

More events type are coming, and comtributions are welcome!

Support

Did you find this plugin useful? Please consider to make a donation to help improve it!

Contributing

Contributions are always welcome!

Comments
  • fix int to long conversion

    fix int to long conversion

    opened by psovit 6
  • Compiling error

    Compiling error

    flutter_floatwing-main\android\src\main\kotlin\im\zoe\labs\flutter_floatwing\FloatwingService.kt: (128, 32): Type mismatch: inferred type is Map<*, >? but Map<, *> was expected

    bug 
    opened by VolodymyrMaiakov 4
  • 无法获取window

    无法获取window

    你好,我的App通过entry-point方式开启悬浮窗,悬浮窗包含在GetMaterialApp中。 在悬浮窗的SchedulerBinding.instance.addPostFrameCallback调用Window.of(context)和FloatwingPlugin().currentWindow都返回null。 调用syncWindows()后再通过windows["xxxID"]可以获取window对象,但不会回调onData。

    版本信息 flutter_floatwing: ^0.2.1 Flutter: 3.0.5

    opened by LondonX 2
  • Cannot `start` Window with `entry-point`

    Cannot `start` Window with `entry-point`

    Thanks for the great package!

    Specs:

    Flutter: 3.3.10 (stable) Android SDK: 31.0.0-rc4 flutter_floatwing: ^0.2.1

    Issue:

    I'm trying to run a simple test, where I show an overlay with the entry-point. But the window doesn't start/create, instead null is returned. The followings are the logs -

    Logs
    Launching lib\main.dart on Android SDK built for x86 in debug mode...
    lib\main.dart:1
    √  Built build\app\outputs\flutter-apk\app-debug.apk.
    Connecting to VM Service at ws://127.0.0.1:61440/5XyFBiJrhLk=/ws
    D/FloatwingPlugin( 9392): [plugin] pixel radio already exits
    D/FloatwingPlugin( 9392): [plugin] system config already exits: {"pixelRadio":2,"screen":{"width":1080,"height":2208}}
    [log] [plugin] initialize result: {permission_grated: true, pixel_radio_updated: false, system_config_updated: false, service_running: false, windows: null}
    [log] [plugin] there are 0 windows already started
    D/FloatwingService( 9392): [service] wait for service created
    I/FloatwingService( 9392): [service] create a window: default {entry=myOverlayMain}
    D/FloatwingService( 9392): [service] wait for service created
    I/flutter ( 9392): [ DEBUG ]: window null
    

    I also noticed that awaiting the openPermissionSetting() doesn't actually wait for permission from the user/settings.

    await FloatwingPlugin().openPermissionSetting();
    

    Steps to reproduce:

    Run the following code -

    main.dart
    import 'package:flutter/material.dart';
    import 'package:flutter_floatwing/flutter_floatwing.dart';
    
    void main() {
      WidgetsFlutterBinding.ensureInitialized();
      FloatwingPlugin().initialize();
      runApp(const RootApp());
    }
    
    @pragma("vm:entry-point")
    void myOverlayMain() {
      WidgetsFlutterBinding.ensureInitialized();
      runApp(
        MaterialApp(
          debugShowCheckedModeBanner: false,
          home: Material(
            color: Colors.transparent,
            elevation: 0.0,
            child: Container(
              width: 100,
              height: 100,
              color: Colors.black,
            ),
          ),
        ),
      );
    }
    
    class RootApp extends StatelessWidget {
      const RootApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          home: Scaffold(
            appBar: AppBar(
              title: const Text("Floatwing Test"),
            ),
            body: Center(
              child: TextButton(
                onPressed: _startWindow,
                child: const Text("Start"),
              ),
            ),
          ),
        );
      }
    
      void _startWindow() async {
        // check permission and service status
        final bool hasPermission = await FloatwingPlugin().checkPermission();
        final bool serviceStarted = await FloatwingPlugin().isServiceRunning();
    
        if (!hasPermission) {
          await FloatwingPlugin().openPermissionSetting();
          // ☝️ it doesn't wait for the value returned by permissions
        }
        if (!serviceStarted) {
          await FloatwingPlugin().startService();
        }
    
        // start overlay window
        final Window? window = await WindowConfig(
          entry: "myOverlayMain",
        ).to().create(start: true);
    
        debugPrint("[ DEBUG ]: window $window");
      }
    }
    
    opened by mulaRahul 0
  • EventType.WindowDestroy returned data is null

    EventType.WindowDestroy returned data is null

    The WindowDestroy event which gets called when closing a window is not returning any value in the data i.e. forceClose value.

    I am listening to the event in this manner: _window?.on(EventType.WindowDestroy, (Window window, dynamic data)

    The event call gets notified but data is empty.

    Also seeing an error in the console:

    W/FlutterJNI(17960): Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. 
    Could not send. Channel: im.zoe.labs/flutter_floatwing/window_msg. Response ID: 9
    
    opened by psovit 0
  • MissingPluginException while running Demo

    MissingPluginException while running Demo

    Hi,I have a problem running the application E/flutter (18685): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: MissingPluginException(No implementation found for method window.start on channel im.zoe.labs/flutter_floatwing/window) E/flutter (18685): #0 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:175:7) E/flutter (18685): <asynchronous suspension> E/flutter (18685): #1 Window.start (package:flutter_floatwing/src/window.dart:114:12) E/flutter (18685): <asynchronous suspension> E/flutter (18685):

    opened by nduc86686 1
  • Error in debug mode and no response to anything

    Error in debug mode and no response to anything

    ../../../flutter/.pub-cache/hosted/pub.dartlang.org/flutter_floatwing-0.2.0/lib/src/provider.dart:143:30: Warning: Operand of null-aware operation '?.' has type 'SchedulerBinding' which excludes null.

    • 'SchedulerBinding' is from 'package:flutter/src/scheduler/binding.dart' ('../../../flutter/packages/flutter/lib/src/scheduler/binding.dart'). SchedulerBinding.instance?.addPostFrameCallback(postFrameCallback); 20220706-errors

    Screen shows a scrollable windows, but appears to not respond to anything. flutter_01

    Flutter 3.0.3 • channel stable • https://github.com/flutter/flutter.git Framework • revision 676cefaaff (13 days ago) • 2022-06-22 11:34:49 -0700 Engine • revision ffe7b86a1e Tools • Dart 2.17.5 • DevTools 2.12.2

    Flutter Doctor: all ok

    question 
    opened by brianoh 1
  • Can not work fine with some plugin which calling dart from Android.

    Can not work fine with some plugin which calling dart from Android.

    This may be a common issue. Plugins register method channels and message channels in onAttachEngine, While the window engine started, those channels will be registered again. That will cause the call from the main engine(the app) missing response.

    Known issues: #6

    Currently I don't know how to handle this issue. Discussion is welcome!

    bug help wanted 
    opened by jiusanzhou 2
Owner
Zoe
Cloud Native. Try to be an Indie hacker, running @wellwellwork + bootstrapping my own products. INTJ.
Zoe
A draggable Flutter widget that makes implementing a Sliding up and fully-stretchable much easier.

Draggable Home A draggable Flutter widget that makes implementing a Sliding up and fully-stretchable much easier! Based on the Scaffold and Sliver. Us

Devs On Flutter 106 Dec 12, 2022
Custom Flutter widgets that makes Checkbox and Radio Buttons much cleaner and easier.

custom_radio_grouped_button Custom Radio Buttons and Grouped Check Box Button Custom Flutter widgets that makes Checkbox and Radio Buttons much cleane

Ketan Choyal 144 Sep 23, 2022
A basic flutter loading overlay

A basic loading overlay Features Creates a new scope where the user cannot leave until you programmatically pop it. Usage import 'package:flutter/mate

null 0 Nov 8, 2021
Flutter widget to show text in popup or overlay container

flutter_show_more_text_popup Flutter widget to show text in popup or overlay container Installation Add this to your package's pubspec.yaml file depen

Sanjay Sharma 44 Jul 5, 2022
Flutter overlay loading dialog example

flutter_overlay_loading_dialog_example Demo

Javeed Ishaq 4 Mar 24, 2022
A Flutter package to show beautiful animated snackbars directly using overlay

Easily show beautiful snack bars directly using overlays. Create custom snack bars and show them with awesome animations.

Sajad Abdollahi 11 Dec 27, 2022
An alternative to Overlay which allows you to easily render and hit test a widget outside its parent bounds

An alternative to Overlay which allows you to easily render and hit test a widget outside its parent bounds. Based on the original idea by @shrouxm he

gskinner team 26 Dec 31, 2022
AdvFAB - An Advanced floating action button that expands itself to reveal its hidden widget

AdvFAB (More Than Just A Floating Action Button) AdvFAB is An Advanced floating action button that expands itself to reveal its hidden widget. It can

null 19 Nov 4, 2022
Pure Dart and Flutter package for Android,IOS and Web

Fancy Flutter Alert Dialog Pure Dart and Flutter package for Android,IOS and Web A flutter Package to show custom alert Dialog,you can choose between

Dokkar Rachid Reda 119 Sep 23, 2022
A Flutter plugin which makes it straightforward to show the native equivalent of a CupertinoAlertDialog or CupertinoActionSheet dialog

A Flutter plugin which makes it straightforward to show the native equivalent of a CupertinoAlertDialog or CupertinoActionSheet dialog

Christoph Krassnigg 9 Dec 9, 2022
Flutter Package for Easier Creation of Home Screen Widgets

Home Widget HomeWidget is a Plugin to make it easier to create HomeScreen Widgets on Android and iOS. HomeWidget does not allow writing Widgets with F

Anton Borries 405 Dec 31, 2022
Make your native android Dialog Fancy and Gify.

Make your native android Dialog Fancy and Gify. A library that takes the standard Android Dialog to the next level with a variety of styling options and Gif's. Style your dialog from code.

Shashank Singhal 522 Jan 2, 2023
Math rendering and editing in pure Flutter.

Flutter Math Math equation rendering in pure Dart & Flutter. This project aims to achieve maximum compatibility and fidelity with regard to the KaTeX

null 109 Dec 16, 2022
A pure flutter toast library

oktoast A library for flutter. A pure dart toast Library. You can completely customize the style of toast. 中文博客介绍 Screenshot Default Custom GIF Versio

OpenFlutter 438 Dec 24, 2022
A light-weight Emoji 📦 for Dart & Flutter with all up-to-date emojis written in pure Dart 😄 . Made from 💯% ☕ with ❤️!

dart_emoji ?? A light-weight Emoji ?? for Dart & Flutter with all up-to-date emojis written in pure Dart ?? . Made from ?? % ☕ with ❤️ ! This is a for

Gatch 18 Mar 22, 2022
Smartwatch widget for Windows made with Flutter.

Smart Watch Widget Smartwatch widget for Windows made with Flutter. This open-source project was created as an example of what we can do with Flutter

Yehuda Kremer 18 Oct 12, 2022
A Flutter Widget to make interactive timeline widget.

Bubble Timeline Package A Flutter Widget to make interactive timeline widget. This widget can be used to make Event Timelines, or Timelines for certai

Vansh Goel 12 Sep 22, 2022
Flutter custom widget to make a group buttons. Included Radio and CheckBox buttons.

Flutter widget to create a group of buttons fast ?? Included Radio and CheckBox buttons models with custom groping types ?? Show some ❤️ and star the

Stanislav Ilin 162 Dec 26, 2022
Widget, that can make any static located widget hidable

Installing See the official installing guidline from hidable/install Usage & Overview To start using Hidable widget, we have to create a ScrollControl

Anon 18 Dec 16, 2022