overlay_pop_up 1.0.6+5
overlay_pop_up: ^1.0.6+5 copied to clipboard
A new Flutter plugin to display pop ups or screens over other apps in Android even when app is closed or killed.
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),
),
],
),
],
),
),
);
}