pnta_flutter 1.0.0-dev.5
pnta_flutter: ^1.0.0-dev.5 copied to clipboard
Official PNTA Flutter plugin to make push notifications suck less.
PNTA Flutter Plugin #
A Flutter plugin for requesting push notification permissions and handling notifications on iOS and Android with deep linking support.
📖 Full Documentation | 🌐 PNTA Dashboard
Requirements #
- iOS 12.0+
- Android API 21+
- Flutter 3.3.0+
Table of Contents #
Installation & Setup #
Add the plugin to your pubspec.yaml:
dependencies:
pnta_flutter: ^latest_version
Then run:
flutter pub get
iOS Setup #
1. Xcode Configuration
- Open
ios/Runner.xcworkspacein Xcode - Select your app target and go to "Signing & Capabilities"
- Add "Push Notifications" capability
- Add "Background Modes" capability and enable "Remote notifications"
Your ios/Runner/Info.plist should include:
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
2. Plugin Integration
Automatic! The plugin integrates automatically when you run:
flutter pub get
cd ios && pod install
Note: No manual Podfile configuration needed - Flutter generates the standard Podfile automatically.
Android Setup #
1. Firebase Configuration
- Go to the Firebase Console
- Create a new project or select an existing one
- Register your Android app using your package name (e.g.,
com.example.your_app) - Download
google-services.jsonand place it atandroid/app/google-services.json
2. Gradle Configuration
Project-level android/build.gradle:
Add to the buildscript { dependencies { ... } } block:
classpath 'com.google.gms:google-services:4.3.15' // or latest version
App-level android/app/build.gradle:
Add at the very bottom:
apply plugin: 'com.google.gms.google-services'
3. AndroidManifest.xml Updates (Optional)
Most configuration is handled automatically by the plugin! You only need to add the following if your app opens external URLs from notifications:
<!-- For opening external URLs (optional - only if your notifications contain links) -->
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
</queries>
Note: The plugin automatically handles:
POST_NOTIFICATIONSpermission- Firebase messaging service registration
- Default notification channel setup
Quick Start Guide #
1. One-Line Setup #
The simplest way to get started - this handles everything for most apps:
import 'package:pnta_flutter/pnta_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// One line setup - requests permission and registers device
await PntaFlutter.initialize(
'prj_XXXXXXXXX', // Your project ID from app.pnta.io
metadata: {
'user_id': '123',
'user_email': '[email protected]',
},
);
// Optional: Get the device token if you need it for your backend
final deviceToken = PntaFlutter.deviceToken;
if (deviceToken != null) {
print('Device token: $deviceToken');
}
runApp(MyApp());
}
2. Setup Navigation Key #
Ensure your MaterialApp uses the global navigator key for deep linking:
MaterialApp(
navigatorKey: PntaFlutter.navigatorKey, // Required for internal route navigation
// ... rest of your app
)
3. Advanced: Delayed Registration Flow #
For apps that need to ask for permission at a specific time (e.g., after user onboarding):
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize without registering device, but include metadata for later use
await PntaFlutter.initialize(
'prj_XXXXXXXXX',
registerDevice: false, // Skip device registration
metadata: {
'user_id': '123',
'user_email': '[email protected]',
},
);
runApp(MyApp());
}
// Later in your app, when ready to register:
Future<void> setupNotifications() async {
await PntaFlutter.registerDevice();
// Optional: Get the device token if you need it for your backend
final deviceToken = PntaFlutter.deviceToken;
if (deviceToken != null) {
print('Device registered successfully! Token: $deviceToken');
} else {
print('Registration failed or not completed yet');
}
}
4. Handle Notifications #
Foreground Notifications
PntaFlutter.foregroundNotifications.listen((payload) {
print('Received foreground notification: ${payload['title']}');
// Show custom UI (snackbar, dialog, etc.)
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('${payload['title']}: ${payload['body']}')),
);
// Manually handle links if needed
final link = payload['link_to'] as String?;
if (link != null && link.isNotEmpty) {
PntaFlutter.handleLink(link);
}
});
// Remember to cancel subscriptions in dispose() to avoid memory leaks
Background/Terminated Notifications
PntaFlutter.onNotificationTap.listen((payload) {
print('User tapped notification: ${payload['title']}');
// Track analytics, show specific screen, etc.
// Links are auto-handled if autoHandleLinks is true
});
// Remember to cancel subscriptions in dispose() to avoid memory leaks
API Reference #
Core Methods #
PntaFlutter.initialize(String projectId, {Map<String, dynamic>? metadata, bool registerDevice, bool autoHandleLinks, bool showSystemUI})
Main initialization method that handles everything for most apps:
projectId: Your PNTA project ID (format:prj_XXXXXXXXX) from app.pnta.iometadata: Optional device metadata to include during registrationregisterDevice: Whether to register device immediately (default:true)autoHandleLinks: Automatically handlelink_toURLs when notifications are tapped (default:false)showSystemUI: Show system notification banner/sound when app is in foreground (default:false)
Returns Future<void>. Use PntaFlutter.deviceToken getter to access the device token after successful registration.
PntaFlutter.registerDevice()
For delayed registration scenarios. Requests notification permission and registers device using metadata from initialize(). Must be called after initialize() with registerDevice: false.
Returns Future<void>. Use PntaFlutter.deviceToken getter to access the device token after successful registration.
PntaFlutter.updateMetadata(Map<String, dynamic> metadata)
Updates device metadata without re-registering. Must be called after successful initialization. Returns Future<void>.
PntaFlutter.handleLink(String link)
Manually handles a link using the plugin's routing logic.
Properties #
PntaFlutter.navigatorKey
Global navigator key for internal route navigation. Must be assigned to your MaterialApp.
PntaFlutter.foregroundNotifications
Stream of notification payloads received when app is in foreground.
PntaFlutter.onNotificationTap
Stream of notification payloads when user taps a notification from background/terminated state.
Link Handling Rules #
The plugin automatically routes links based on these rules:
- Contains
://(e.g.,http://example.com,mailto:[email protected]) → Opens externally via system browser/app - No
://(e.g.,/profile,/settings) → Navigates internally using Flutter's Navigator
Metadata Best Practices #
Store your metadata in one place and use it consistently:
class UserMetadata {
static Map<String, dynamic> get current => {
'user_id': getCurrentUserId(),
'app_version': getAppVersion(),
'subscription_tier': getSubscriptionTier(),
'last_active': DateTime.now().toIso8601String(),
};
}
// Use everywhere
await PntaFlutter.initialize('prj_XXXXXXXXX', metadata: UserMetadata.current);
await PntaFlutter.updateMetadata(UserMetadata.current);
Simple Example #
import 'package:flutter/material.dart';
import 'package:pnta_flutter/pnta_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// One-line setup - handles permission, registration, and configuration
await PntaFlutter.initialize(
'prj_XXXXXXXXX', // Your project ID from app.pnta.io
metadata: {
'user_id': '123',
'user_email': '[email protected]',
},
);
// Optional: Get the device token if you need it for your backend
final deviceToken = PntaFlutter.deviceToken;
if (deviceToken != null) {
print('Device registered successfully! Token: $deviceToken');
}
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: PntaFlutter.navigatorKey, // Required for deep linking
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
void initState() {
super.initState();
_setupNotifications();
}
void _setupNotifications() async {
// If you used initialize() with requestPermission: true, this is already done!
// Otherwise, for delayed permission scenarios:
// await PntaFlutter.requestPermission(
// metadata: {
// 'user_id': '123',
// 'user_email': '[email protected]',
// },
// );
// Listen for foreground notifications
PntaFlutter.foregroundNotifications.listen((payload) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Received: ${payload['title']}')),
);
});
// Listen for background notification taps
PntaFlutter.onNotificationTap.listen((payload) {
print('User tapped notification: ${payload['title']}');
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('PNTA Example')),
body: Center(child: Text('Ready for notifications!')),
);
}
}
For a complete working example with all features, see the example/ app in the plugin repository.
Troubleshooting #
Common Issues #
Permission not granted on Android:
- Ensure
POST_NOTIFICATIONSpermission is in AndroidManifest.xml - For Android 13+, permission must be requested at runtime
Firebase issues:
- Verify
google-services.jsonis in the correct location - Check that Firebase project is properly configured
- Ensure Google Services plugin is applied
Deep links not working:
- Verify
navigatorKeyis assigned to MaterialApp - Check that routes are properly defined
- For external URLs, ensure
<queries>block is in AndroidManifest.xml
iOS build issues:
- Clean and rebuild:
flutter clean && flutter pub get - Update Podfile and run
cd ios && pod install
For more examples and advanced usage, see the example/ directory in the plugin repository.