overlay_pop_up 1.0.6+5 copy "overlay_pop_up: ^1.0.6+5" to clipboard
overlay_pop_up: ^1.0.6+5 copied to clipboard

PlatformAndroid

A new Flutter plugin to display pop ups or screens over other apps in Android even when app is closed or killed.

example/lib/main.dart

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:overlay_pop_up/overlay_pop_up.dart';
import 'package:permission_handler/permission_handler.dart';

// Global listener that persists across widget rebuilds
StreamSubscription? _globalOverlayListener;

void main() {
  // Initialize handler and listener BEFORE runApp
  WidgetsFlutterBinding.ensureInitialized();

  OverlayPopUp.initializeMainAppHandler();
  debugPrint('[MAIN] Handler initialized');

  // Set up global listener that survives widget lifecycle
  final stream = OverlayPopUp.dataListener;
  if (stream != null) {
    _globalOverlayListener = stream.listen(
      (onData) {
        debugPrint('[MAIN] ✅✅✅ GLOBAL listener received: $onData');
      },
      onError: (error) {
        debugPrint('[MAIN] ❌ Error: $error');
      },
      onDone: () {
        debugPrint('[MAIN] ⚠️ Listener done');
      },
      cancelOnError: false,
    );
    debugPrint('[MAIN] Global listener configured');
  }

  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  bool isActive = false;
  bool permissionStatus = false;
  String overlayPosition = '';
  String lastMessageFromOverlay = '';

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);

    _globalOverlayListener?.cancel();
    final stream = OverlayPopUp.dataListener;
    if (stream != null) {
      _globalOverlayListener = stream.listen(
        (onData) async {
          if (mounted) {
            setState(() {
              lastMessageFromOverlay = onData.toString();
            });

            await Future.delayed(Duration(milliseconds: 500), () {
              getOverlayStatus();
            });
          }
        },
        onError: (error) {
          debugPrint('[Main App] ❌ Error in listener: $error');
        },
        onDone: () {
          debugPrint('[Main App] ⚠️ Listener done/closed');
        },
        cancelOnError: false,
      );
      debugPrint(
        '[Main App] Listener subscription hashCode: ${_globalOverlayListener.hashCode}',
      );
    }

    WidgetsBinding.instance.addPostFrameCallback((_) {
      getOverlayStatus();
      getPermissionStatus();
      _requestNotificationPermission();
    });
  }

  Future<void> _requestNotificationPermission() async {
    // Request notification permission for Android 13+
    final status = await Permission.notification.request();
    debugPrint('[Main App] Notification permission status: $status');
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    if (state == AppLifecycleState.resumed) {
      getOverlayStatus();
    }
  }

  Future<void> getOverlayStatus() async {
    isActive = await OverlayPopUp.isActive();
    setState(() {});
  }

  Future<void> getPermissionStatus() async {
    permissionStatus = await OverlayPopUp.checkPermission();
    setState(() {});
  }

  @override
  Widget build(BuildContext context) => MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: const Text(
          'Flutter overlay pop up',
          style: TextStyle(color: Colors.white),
        ),
        backgroundColor: Colors.red[900],
      ),
      body: SizedBox(
        width: double.maxFinite,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            const SizedBox(height: 30),
            Text(
              'Permission status: ${permissionStatus ? 'enabled' : 'disabled'}',
              style: Theme.of(context).textTheme.titleLarge,
            ),
            MaterialButton(
              onPressed: () async {
                permissionStatus = await OverlayPopUp.requestPermission();
                setState(() {});
              },
              color: Colors.red[900],
              child: const Text(
                'Request overlay permission',
                style: TextStyle(color: Colors.white),
              ),
            ),
            const SizedBox(height: 20),
            Text(
              'Is active: $isActive',
              style: Theme.of(context).textTheme.titleMedium,
            ),
            MaterialButton(
              onPressed: () async {
                final permission = await OverlayPopUp.checkPermission();
                if (permission) {
                  if (!await OverlayPopUp.isActive()) {
                    isActive = await OverlayPopUp.showOverlay(
                      width: 400,
                      height: 500,
                      screenOrientation: ScreenOrientation.portrait,
                      closeWhenTapBackButton: true,
                      isDraggable: true,
                      swipeToDismiss: true, // Enable swipe to dismiss!
                      entryPointMethodName: 'customOverlay',
                      notificationIcon: 'ic_launcher', // REQUIRED: Your app's notification icon
                      notificationTitle: 'Overlay Pop Up',
                      notificationText: 'Overlay is showing!',
                    );
                    setState(() {
                      isActive = isActive;
                    });
                    return;
                  } else {
                    final result = await OverlayPopUp.closeOverlay();
                    setState(() {
                      isActive = (result == true) ? false : true;
                    });
                  }
                } else {
                  permissionStatus = await OverlayPopUp.requestPermission();
                  setState(() {});
                }
              },
              color: Colors.red[900],
              child: const Text(
                'Show overlay',
                style: TextStyle(color: Colors.white),
              ),
            ),
            MaterialButton(
              onPressed: () async {
                if (await OverlayPopUp.isActive()) {
                  await OverlayPopUp.sendToOverlay({
                    'mssg': 'Hello from dart!',
                  });
                }
              },
              color: Colors.red[900],
              child: const Text(
                'Send data',
                style: TextStyle(color: Colors.white),
              ),
            ),
            MaterialButton(
              onPressed: () async {
                if (await OverlayPopUp.isActive()) {
                  await OverlayPopUp.updateOverlaySize(width: 800, height: 800);
                }
              },
              color: Colors.red[900],
              child: const Text(
                'Update overlay size',
                style: TextStyle(color: Colors.white),
              ),
            ),
            MaterialButton(
              onPressed: () async {
                if (await OverlayPopUp.isActive()) {
                  final position = await OverlayPopUp.getOverlayPosition();
                  setState(() {
                    overlayPosition = (position?['overlayPosition'] != null)
                        ? position!['overlayPosition'].toString()
                        : '';
                  });
                }
              },
              color: Colors.red[900],
              child: const Text(
                'Get overlay position',
                style: TextStyle(color: Colors.white),
              ),
            ),
            Text('Current position: $overlayPosition'),
            const SizedBox(height: 20),
            const Divider(),
            Text(
              'Messages from Overlay:',
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 10),
            Container(
              padding: const EdgeInsets.all(12),
              margin: const EdgeInsets.symmetric(horizontal: 20),
              decoration: BoxDecoration(
                color: Colors.grey[300],
                borderRadius: BorderRadius.circular(8),
              ),
              child: Text(
                lastMessageFromOverlay.isEmpty
                    ? 'No messages yet'
                    : lastMessageFromOverlay,
                style: const TextStyle(fontSize: 12),
                textAlign: TextAlign.center,
              ),
            ),
          ],
        ),
      ),
    ),
  );
}

///
/// Is required has `@pragma("vm:entry-point")` and the method name by default is `overlayPopUp`
/// if you change the method name you should pass it as `entryPointMethodName` in showOverlay method
///
@pragma("vm:entry-point")
void customOverlay() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(
    const MaterialApp(debugShowCheckedModeBanner: false, home: OverlayWidget()),
  );
}

class OverlayWidget extends StatefulWidget {
  const OverlayWidget({super.key});

  @override
  State<OverlayWidget> createState() => _OverlayWidgetState();
}

class _OverlayWidgetState extends State<OverlayWidget> {
  @override
  void initState() {
    super.initState();
    OverlayPopUp.initializeOverlayHandler();
  }

  @override
  Widget build(BuildContext context) => Material(
    color: Colors.transparent,
    child: Container(
      padding: const EdgeInsets.all(8),
      decoration: BoxDecoration(
        color: Colors.green[300],
        borderRadius: BorderRadius.circular(16),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withValues(alpha: .2),
            blurRadius: 10,
            spreadRadius: 4,
          ),
        ],
      ),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Container(
            width: 40,
            height: 4,
            margin: const EdgeInsets.only(bottom: 15),
            decoration: BoxDecoration(
              color: Colors.grey[200],
              borderRadius: BorderRadius.circular(2),
            ),
          ),
          Text(
            'Overlay Pop Up',
            style: TextStyle(
              fontSize: 18,
              fontWeight: FontWeight.bold,
              color: Colors.grey[800],
            ),
          ),
          const SizedBox(height: 10),
          Text(
            'Swipe down to dismiss',
            style: TextStyle(fontSize: 12, color: Colors.grey[800]),
          ),
          const SizedBox(height: 15),
          SizedBox(
            child: StreamBuilder(
              stream: OverlayPopUp
                  .overlayDataListener, // Use overlayDataListener in overlay widget
              initialData: null,
              builder: (BuildContext context, AsyncSnapshot snapshot) {
                return Text(
                  snapshot.data?['mssg'] ?? '',
                  style: const TextStyle(fontSize: 14, color: Colors.black87),
                  textAlign: TextAlign.center,
                );
              },
            ),
          ),
          const SizedBox(height: 10),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              FloatingActionButton(
                mini: true,
                shape: const CircleBorder(),
                backgroundColor: Colors.blue,
                elevation: 8,
                onPressed: () async {
                  // Send message FROM overlay TO main app
                  await OverlayPopUp.sendToMainApp({
                    'from': 'overlay',
                    'message': 'Hello from overlay! 👋',
                    'timestamp': DateTime.now().toString(),
                  });
                },
                child: const Icon(Icons.send, color: Colors.white, size: 18),
              ),
              const SizedBox(width: 10),
              FloatingActionButton(
                mini: true,
                shape: const CircleBorder(),
                backgroundColor: Colors.black,
                elevation: 8,
                onPressed: () async {
                  // Notify main app before closing
                  await OverlayPopUp.sendToMainApp({
                    'from': 'overlay',
                    'message': 'Overlay closing...',
                  });
                  await Future.delayed(const Duration(milliseconds: 100));
                  await OverlayPopUp.closeOverlay();
                },
                child: const Icon(Icons.close, color: Colors.white, size: 18),
              ),
            ],
          ),
        ],
      ),
    ),
  );
}
49
likes
160
points
176
downloads

Publisher

unverified uploader

Weekly Downloads

A new Flutter plugin to display pop ups or screens over other apps in Android even when app is closed or killed.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on overlay_pop_up

Packages that implement overlay_pop_up