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.xcworkspace
in 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.json
and 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_NOTIFICATIONS
permission- 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': 'user@example.com',
},
);
// 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': 'user@example.com',
},
);
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_to
URLs 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:test@example.com
) β 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': 'user@example.com',
},
);
// 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': 'user@example.com',
// },
// );
// 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_NOTIFICATIONS
permission is in AndroidManifest.xml - For Android 13+, permission must be requested at runtime
Firebase issues:
- Verify
google-services.json
is in the correct location - Check that Firebase project is properly configured
- Ensure Google Services plugin is applied
Deep links not working:
- Verify
navigatorKey
is 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.