easy_in_app_notify 2.0.0
easy_in_app_notify: ^2.0.0 copied to clipboard
A beautiful Flutter package for displaying notifications as in-app overlays with smooth animations, progress indicators, and customizable styling.
Easy In-App Notify π±π₯ #
A beautiful and customizable Flutter package for displaying notifications as in-app overlays with smooth animations, progress indicators, and intuitive user interactions.
Perfect for showing notifications when your app is in the foreground!
π Version 2.0.0 - Now with zero configuration! No more
init()
ornavigatorKey
setup required. Just passcontext
toshow()
and you're ready!
π· Screenshots #
β¨ Features #
- π― Overlay Notifications - Non-blocking notifications that appear over your app content
- π± Foreground Notifications - Show notifications when app is active/foreground
- π¨ Fully Customizable - Colors, spacing, dimensions, and visual styling
- β±οΈ Auto-Dismiss - Configurable duration with visual countdown progress bar
- π Swipe to Dismiss - Users can swipe notifications away manually
- π Smooth Animations - Slide-in/slide-out transitions with easing curves
- π Platform-Optimized Sounds - Smart platform detection: alert sound on desktop, click sound on mobile/web (no external dependencies)
- π― Material Design - Follows Material Design principles and theming
- π± Safe Area Aware - Respects device safe areas and notches
- π RTL Support - Full right-to-left language support
- π Lightweight - Minimal dependencies and optimized performance
π¦ Installation #
Run this command to add the package to your project:
flutter pub add easy_in_app_notify
Or manually add to your pubspec.yaml
:
dependencies:
easy_in_app_notify: # Latest version from pub.flutter-io.cn
π Quick Start #
1. Zero Configuration Setup #
No setup required! Just import and use:
import 'package:easy_in_app_notify/easy_in_app_notify.dart';
import 'package:flutter/material.dart';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('My App')),
body: Center(
child: ElevatedButton(
onPressed: () {
EasyInAppNotify.show(
context, // Pass context as first parameter
content: EasyInAppNotifyContent(
title: "Hello!",
message: "Welcome to the app.",
),
);
},
child: Text('Show Notification'),
),
),
);
}
}
That's it!
- β Zero setup - no initialization or configuration required
- β No StatefulWidget needed
- β Direct usage - just call with any BuildContext
- β Works everywhere in your app
2. Show Notifications #
Display notifications anywhere in your app:
EasyInAppNotify.show(
context, // Pass your BuildContext
content: EasyInAppNotifyContent(
title: "Success!",
message: "Your changes have been saved successfully.",
),
);
π Usage Examples #
Basic Notification #
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: "Welcome!",
message: "Thanks for using our app.",
),
);
Notification with Custom Icon #
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: "Download Complete",
message: "Your file has been downloaded successfully.",
icon: Icons.download_done,
trailingText: "2m ago",
),
);
Customized Notification #
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: "Error Occurred",
message: "Failed to save your changes. Please try again.",
icon: Icons.error,
trailingText: "Retry",
),
option: EasyInAppNotifyOption(
duration: 8, // Show for 8 seconds
showProgressBar: true,
swipeToDismiss: true,
),
theme: EasyInAppNotifyTheme(
color: Colors.red,
elevation: 10,
radius: 15,
margin: 10,
),
);
Different Notification Types #
// Success notification
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: "Success",
message: "Operation completed successfully!",
icon: Icons.check_circle,
),
theme: EasyInAppNotifyTheme(color: Colors.green),
);
// Warning notification
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: "Warning",
message: "Please check your internet connection.",
icon: Icons.warning,
),
theme: EasyInAppNotifyTheme(color: Colors.orange),
);
// Info notification
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: "Info",
message: "New features are available in settings.",
icon: Icons.info,
),
theme: EasyInAppNotifyTheme(color: Colors.blue),
);
ποΈ Using Without Direct Context Access #
When you need to show notifications from classes that don't have access to BuildContext (like service classes, static methods, or background tasks), here are several patterns you can use:
1. Pass Context as Parameter #
The simplest approach is to pass context as a parameter to methods that need it:
class ApiService {
static void handleError(BuildContext context, String error) {
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: "API Error",
message: error,
),
theme: EasyInAppNotifyTheme(color: Colors.red),
);
}
}
// Usage
ApiService.handleError(context, "Network request failed");
2. Callback Pattern #
Use callbacks to decouple your business logic from UI concerns:
class DataService {
static Function(String)? onError;
static Function(String)? onSuccess;
static void fetchData() {
try {
// ... fetch logic
onSuccess?.call("Data loaded successfully");
} catch (e) {
onError?.call("Failed to fetch data: $e");
}
}
}
// Setup in your widget
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Setup callbacks
DataService.onError = (message) {
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: "Error",
message: message,
),
theme: EasyInAppNotifyTheme(color: Colors.red),
);
};
DataService.onSuccess = (message) {
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: "Success",
message: message,
),
theme: EasyInAppNotifyTheme(color: Colors.green),
);
};
return YourWidget();
}
}
3. Navigator Key Pattern #
Create a global navigator key to access context from anywhere:
class AppNavigator {
static final GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
static void showNotification(String title, String message, {Color? color}) {
final context = navigatorKey.currentContext;
if (context != null) {
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(title: title, message: message),
theme: EasyInAppNotifyTheme(color: color ?? Colors.blue),
);
}
}
}
// In your MaterialApp
MaterialApp(
navigatorKey: AppNavigator.navigatorKey,
home: MyHomePage(),
)
// Usage from anywhere (services, static methods, etc.)
AppNavigator.showNotification(
"Upload Complete",
"Your file has been uploaded successfully!",
color: Colors.green,
);
4. Service Locator Pattern #
Use a service locator or dependency injection pattern:
class NotificationService {
BuildContext? _context;
void setContext(BuildContext context) => _context = context;
void showSuccess(String message) {
_showNotification("Success", message, Colors.green);
}
void showError(String message) {
_showNotification("Error", message, Colors.red);
}
void showInfo(String message) {
_showNotification("Info", message, Colors.blue);
}
void _showNotification(String title, String message, Color color) {
if (_context != null) {
EasyInAppNotify.show(
_context!,
content: EasyInAppNotifyContent(title: title, message: message),
theme: EasyInAppNotifyTheme(color: color),
);
}
}
}
// Register and use
final notificationService = NotificationService();
// In your widget
@override
Widget build(BuildContext context) {
notificationService.setContext(context);
// ... rest of your widget
}
// Usage from anywhere
notificationService.showSuccess("Operation completed!");
5. Provider/Riverpod Pattern #
If you're using state management, you can integrate notifications:
// Using Provider
class NotificationProvider extends ChangeNotifier {
BuildContext? _context;
void setContext(BuildContext context) => _context = context;
void showNotification(String title, String message) {
if (_context != null) {
EasyInAppNotify.show(
_context!,
content: EasyInAppNotifyContent(title: title, message: message),
);
}
}
}
// Usage
Provider.of<NotificationProvider>(context, listen: false)
.showNotification("Hello", "World");
6. Firebase Cloud Messaging (FCM) Integration #
When using Firebase Cloud Messaging, you often need to handle notifications in background handlers where BuildContext isn't available:
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:firebase_core/firebase_core.dart';
class FCMService {
static final GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
static void initialize() {
// Handle background messages
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
// Handle foreground messages when app is active
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
_showFCMNotification(message);
});
// Handle notification opened app
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
_showFCMNotification(message);
});
}
static void _showFCMNotification(RemoteMessage message) {
final context = navigatorKey.currentContext;
if (context != null && message.notification != null) {
// Determine notification type from FCM data
final notificationType = message.data['type'] ?? 'info';
Color notificationColor = _getColorForType(notificationType);
IconData notificationIcon = _getIconForType(notificationType);
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: message.notification!.title ?? 'Notification',
message: message.notification!.body ?? '',
icon: notificationIcon,
trailingText: message.data['timestamp'] ?? 'Now',
),
theme: EasyInAppNotifyTheme(color: notificationColor),
option: EasyInAppNotifyOption(
duration: int.tryParse(message.data['duration'] ?? '5') ?? 5,
),
);
}
}
static Color _getColorForType(String type) {
switch (type.toLowerCase()) {
case 'success': return Colors.green;
case 'error': return Colors.red;
case 'warning': return Colors.orange;
default: return Colors.blue;
}
}
static IconData _getIconForType(String type) {
switch (type.toLowerCase()) {
case 'success': return Icons.check_circle;
case 'error': return Icons.error;
case 'warning': return Icons.warning;
default: return Icons.notifications;
}
}
}
// Background message handler (must be top-level function)
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print('Handling a background message: ${message.messageId}');
// Note: You cannot show in-app notifications when app is in background
// Background messages are handled by the system notification tray
// Use local notifications for background scenarios if needed
}
// Setup in your main app
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Initialize FCM
FCMService.initialize();
runApp(MyApp());
}
// In your MaterialApp
MaterialApp(
navigatorKey: FCMService.navigatorKey,
home: MyHomePage(),
)
Advanced FCM Integration with Custom Routing
class AdvancedFCMService {
static final GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
static void initialize() {
FirebaseMessaging.onMessage.listen(_handleForegroundMessage);
FirebaseMessaging.onMessageOpenedApp.listen(_handleNotificationClick);
}
static void _handleForegroundMessage(RemoteMessage message) {
final messageType = message.data['messageType'] ?? 'general';
switch (messageType) {
case 'chat':
_showChatNotification(message);
break;
case 'order':
_showOrderNotification(message);
break;
case 'promotion':
_showPromotionNotification(message);
break;
default:
_showGeneralNotification(message);
}
}
static void _showChatNotification(RemoteMessage message) {
final context = navigatorKey.currentContext;
if (context != null) {
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: message.data['senderName'] ?? 'New Message',
message: message.notification?.body ?? '',
icon: Icons.chat,
trailingText: _formatTimestamp(message.data['timestamp']),
),
theme: EasyInAppNotifyTheme(color: Colors.green),
option: EasyInAppNotifyOption(duration: 6),
);
}
}
static void _showOrderNotification(RemoteMessage message) {
final context = navigatorKey.currentContext;
if (context != null) {
final status = message.data['orderStatus'] ?? 'updated';
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: 'Order ${status.toUpperCase()}',
message: message.notification?.body ?? '',
icon: _getOrderIcon(status),
trailingText: '#${message.data['orderId'] ?? ''}',
),
theme: EasyInAppNotifyTheme(color: _getOrderColor(status)),
);
}
}
static void _showPromotionNotification(RemoteMessage message) {
final context = navigatorKey.currentContext;
if (context != null) {
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: message.notification?.title ?? 'Special Offer!',
message: message.notification?.body ?? '',
icon: Icons.local_offer,
trailingText: message.data['discount'] ?? 'Limited Time',
),
theme: EasyInAppNotifyTheme(
color: Colors.orange,
elevation: 10,
),
option: EasyInAppNotifyOption(duration: 8),
);
}
}
static void _showGeneralNotification(RemoteMessage message) {
final context = navigatorKey.currentContext;
if (context != null) {
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: message.notification?.title ?? 'Notification',
message: message.notification?.body ?? '',
icon: Icons.notifications,
),
theme: EasyInAppNotifyTheme(color: Colors.blue),
);
}
}
static void _handleNotificationClick(RemoteMessage message) {
// Handle navigation when notification is clicked
final route = message.data['route'];
if (route != null) {
navigatorKey.currentState?.pushNamed(route);
}
}
static String _formatTimestamp(String? timestamp) {
if (timestamp == null) return 'Now';
try {
final dateTime = DateTime.fromMillisecondsSinceEpoch(int.parse(timestamp));
final now = DateTime.now();
final difference = now.difference(dateTime);
if (difference.inMinutes < 1) return 'Now';
if (difference.inHours < 1) return '${difference.inMinutes}m ago';
if (difference.inDays < 1) return '${difference.inHours}h ago';
return '${difference.inDays}d ago';
} catch (e) {
return 'Now';
}
}
static IconData _getOrderIcon(String status) {
switch (status.toLowerCase()) {
case 'confirmed': return Icons.check_circle;
case 'shipped': return Icons.local_shipping;
case 'delivered': return Icons.done_all;
case 'cancelled': return Icons.cancel;
default: return Icons.shopping_cart;
}
}
static Color _getOrderColor(String status) {
switch (status.toLowerCase()) {
case 'confirmed': return Colors.green;
case 'shipped': return Colors.blue;
case 'delivered': return Colors.purple;
case 'cancelled': return Colors.red;
default: return Colors.orange;
}
}
}
ποΈ Configuration #
EasyInAppNotifyContent #
Configure the notification content:
Property | Type | Description | Default |
---|---|---|---|
title |
String |
Main notification title (required) | - |
message |
String |
Notification message (required) | - |
icon |
IconData? |
Optional icon to display | CupertinoIcons.bell |
trailingText |
String? |
Optional trailing text (e.g., timestamp) | null |
EasyInAppNotifyOption #
Configure notification behavior:
Property | Type | Description | Default |
---|---|---|---|
duration |
int |
Auto-dismiss duration in seconds | 5 |
showProgressBar |
bool |
Show countdown progress bar | true |
swipeToDismiss |
bool |
Enable swipe-to-dismiss | true |
EasyInAppNotifyTheme #
Configure visual appearance:
Property | Type | Description | Default |
---|---|---|---|
color |
Color? |
Primary accent color | App's primary color |
margin |
double |
Margin from screen edges | 5.0 |
padding |
double |
Internal padding | 10.0 |
radius |
double |
Border radius | 10.0 |
elevation |
double |
Card elevation/shadow | 5.0 |
iconSize |
double |
Icon size | 20.0 |
π¨ Theming #
Using App Theme Colors #
The package automatically uses your app's theme colors when no custom colors are specified:
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: "Themed Notification",
message: "This uses your app's primary color!",
),
// No theme specified - uses app's primary color
);
Custom Theme #
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: "Custom Theme",
message: "This notification has custom styling.",
),
theme: EasyInAppNotifyTheme(
color: Color(0xFF6366F1), // Custom purple color
elevation: 12,
radius: 20,
margin: 15,
padding: 15,
iconSize: 24,
),
);
π± Platform-Specific Behavior #
Sound Support #
EasyInAppNotify uses platform-optimized system sounds based on Flutter's platform support:
- π₯οΈ Desktop (Windows, macOS, Linux): Uses
SystemSoundType.alert
- proper notification alert sound - π± Mobile (iOS, Android): Uses
SystemSoundType.click
- brief button press sound - π Web: Attempts
SystemSoundType.click
- may work in some browsers, silently ignored if not supported
When Sounds May Not Play
Notification sounds may not play in these situations:
- π΄ Silent Mode: Device is in silent/vibrate mode
- π System Settings: User has disabled system sounds
- π΅ Focus Modes: Device is in Do Not Disturb or Focus mode
- π Volume: Media/notification volume is set to zero
- π Web Permissions: Browser blocks auto-play audio
This is normal behavior and matches how system notifications work on each platform.
Platform Compatibility #
Platform | Sound Support | Overlay Support | RTL Support |
---|---|---|---|
Android | β Click | β Full | β Yes |
iOS | β Click | β Full | β Yes |
Web | β οΈ Click* | β Full | β Yes |
macOS | β Alert | β Full | β Yes |
Windows | β Alert | β Full | β Yes |
Linux | β Alert | β Full | β Yes |
*Web sound support: Varies by browser and user settings; may be silently ignored
π Sound Behavior Notes
- Mobile:
SystemSoundType.click
provides a brief, subtle notification sound - Desktop:
SystemSoundType.alert
provides a proper system notification alert - Web: Attempts
SystemSoundType.click
but depends on browser audio policies - All Platforms: Respects user's silent mode and system sound settings
π§ Advanced Usage #
Conditional Progress Bar #
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: "Processing...",
message: "Please wait while we process your request.",
),
option: EasyInAppNotifyOption(
duration: 10,
showProgressBar: true, // Show progress for long operations
swipeToDismiss: false, // Prevent dismissal during processing
),
);
Quick Notification Helper #
Create helper methods for common notification types:
class NotificationHelper {
static void showSuccess(BuildContext context, String title, String message) {
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: title,
message: message,
icon: Icons.check_circle,
),
theme: EasyInAppNotifyTheme(color: Colors.green),
option: EasyInAppNotifyOption(duration: 3),
);
}
static void showError(BuildContext context, String title, String message) {
EasyInAppNotify.show(
context,
content: EasyInAppNotifyContent(
title: title,
message: message,
icon: Icons.error,
),
theme: EasyInAppNotifyTheme(color: Colors.red),
option: EasyInAppNotifyOption(duration: 6),
);
}
}
// Usage
NotificationHelper.showSuccess("Saved!", "Your profile has been updated.");
NotificationHelper.showError("Error", "Failed to connect to server.");
π± Platform Support #
- β Android
- β iOS
- β Web
- β macOS
- β Windows
- β Linux
Note: Sound notifications are only supported on Android and iOS platforms. On other platforms, notifications will display without sound.
π€ Contributing #
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
Development Setup #
- Fork the repository
- Clone your fork:
git clone https://github.com/mohamedmaher-dev/easy_in_app_notify.git
- Create a feature branch:
git checkout -b feature/amazing-feature
- Make your changes and add tests
- Commit your changes:
git commit -m 'Add amazing feature'
- Push to the branch:
git push origin feature/amazing-feature
- Open a Pull Request
π Issues #
If you encounter any issues or have feature requests, please file them on the GitHub Issues page.
π License #
This project is licensed under the MIT License - see the LICENSE file for details.
π Acknowledgments #
- Flutter team for the amazing framework
- Material Design for the design guidelines
- The Flutter community for inspiration and feedback
Made with β€οΈ for the Flutter community