pnta_flutter 1.0.0-dev.2
pnta_flutter: ^1.0.0-dev.2 copied to clipboard
Official PNTA Flutter plugin to make push notifications suck less.
PNTA Flutter Plugin #
A Flutter plugin for requesting push notification permissions on iOS and Android.
Usage #
1. Add Dependency #
Add this plugin to your pubspec.yaml
:
dependencies:
pnta_flutter:
path: ../pnta_flutter # or your published version
2. Request Notification Permission #
Call this method from your Dart code (e.g., on app launch):
import 'package:pnta_flutter/pnta_flutter.dart';
final granted = await PntaFlutter.requestNotificationPermission();
if (granted) {
// Permission granted, proceed with notifications
} else {
// Permission denied
}
3. Platform-specific Setup #
iOS
To use this plugin on iOS, make sure your Podfile includes the following configuration (you can copy the whole block if starting fresh):
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. Run `flutter pub get` first."
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found. Try deleting Generated.xcconfig and re-running `flutter pub get`"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
This ensures the plugin integrates correctly and Swift support is enabled.
Android
To use this plugin on Android, you must complete the following steps:
-
Add Firebase to Your Android App
- Go to the Firebase Console.
- Create a new project (or use an existing one).
- Register your Android app (use your app's package name, e.g.,
com.example.your_app
). - Download the
google-services.json
file from the Firebase Console. - Place the file in your Flutter project at:
android/app/google-services.json
-
Update Your Gradle Files
- Project-level
android/build.gradle
:- Make sure the following is present inside the
buildscript { dependencies { ... } }
block:classpath 'com.google.gms:google-services:4.3.15' // or latest version
- Make sure the following is present inside the
- App-level
android/app/build.gradle
:- At the very bottom of the file, add:
apply plugin: 'com.google.gms.google-services'
- At the very bottom of the file, add:
- Project-level
-
AndroidManifest.xml: Add the notification permission (required for Android 13+):
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
4. Get Device Token #
Retrieve the device push notification token (APNs on iOS, FCM on Android):
final token = await PntaFlutter.getDeviceToken();
if (token != null) {
// Use the token for identification or backend registration
}
5. Identify Device #
Send device and app metadata to the backend for identification:
await PntaFlutter.identify(projectId, deviceToken);
projectId
: Your PNTA project IDdeviceToken
: The push notification token (fromgetDeviceToken()
)
This will collect and send the following metadata (with consistent keys across iOS and Android):
- name
- model
- localized_model
- system_name
- system_version
- identifier_for_vendor
- device_token
- region_code
- language_code
- currency_code
- current_locale
- preferred_languages
- current_time_zone
- bundle_identifier
- app_version
- app_build
The data is sent as a JSON payload to https://app.pnta.io/api/v1/identification
via PUT request.
6. Foreground Notification Handling #
This plugin allows you to intercept and handle push notifications when your app is in the foreground, giving you full control over the user experience.
Note: Foreground notifications are always delivered to Dart, and you are responsible for handling any links in the payload. You can use PntaFlutter.handleLink(link)
to handle links in your foreground notification handler.
Dart API
// Listen for foreground notifications
PntaFlutter.foregroundNotifications.listen((payload) {
// Show custom UI, route user, track analytics, etc.
final link = payload['link_to'] as String?;
if (link != null && link.isNotEmpty) {
// Manually handle the link if desired
PntaFlutter.handleLink(link);
}
print('Received foreground notification: $payload');
});
// Configure whether to show the system notification UI (banner, sound, badge) in the foreground
await PntaFlutter.setForegroundPresentationOptions(showSystemUI: false); // default is false
- If
showSystemUI
isfalse
, the system notification UI is suppressed in the foreground and you can show your own UI. - If
showSystemUI
istrue
, the system notification UI is shown as if the app were in the background, and you still receive the payload in Dart.
Platform Behavior
Platform | System UI Option | Custom In-App UI | Dart Stream | User Flexibility |
---|---|---|---|---|
iOS | Yes (configurable) | Yes | Yes | Maximum |
Android | Yes (configurable) | Yes | Yes | Maximum |
- iOS: Uses
UNUserNotificationCenterDelegate
to control presentation and always forwards the payload to Dart. - Android: Uses a custom
FirebaseMessagingService
to intercept foreground messages, show/hide system UI, and forward the payload to Dart.
Android Setup
- Make sure your
AndroidManifest.xml
includes:<service android:name="io.pnta.pnta_flutter.PntaMessagingService" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service>
Android Notification Channel Setup #
For Android 8.0+ (API 26+), you must define a default notification channel for Firebase Cloud Messaging (FCM) background notifications. This ensures notifications are delivered with your desired settings and removes FCM warnings.
Add the following inside your <application>
tag in android/app/src/main/AndroidManifest.xml
:
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="pnta_default" />
This tells FCM to use the pnta_default
channel (created automatically by the plugin) for all background notifications.
iOS Setup
- No extra steps required beyond normal plugin integration.
Example Usage
await PntaFlutter.setForegroundPresentationOptions(showSystemUI: false);
PntaFlutter.foregroundNotifications.listen((payload) {
// Show custom banner, route user, track analytics, etc.
print('Received foreground notification: $payload');
});
7. link_to Push Notification Handling (Deep Links & External URLs) #
This plugin supports push notifications with a link_to
field in the payload, enabling deep linking and external URL handling.
- If the notification is tapped while the app is in the background or terminated, and
autoHandleLinks
is enabled, the plugin will automatically:- Open external URLs (starting with
http
orhttps
) in the system browser (using url_launcher). - Navigate to in-app routes (starting with
/
or any other path) using the app's navigator.
- Open external URLs (starting with
- When the app is in the foreground, the full notification payload is always delivered to Dart via the stream. You are responsible for handling any links or navigation in this case—
autoHandleLinks
does not apply.
Implementation Note:
- All link handling logic is now centralized in
LinkHandler
. TheautoHandleLinks
flag is managed only byLinkHandler
and is set viaPntaFlutter.initialize(autoHandleLinks: ...)
. PntaFlutter.onNotificationTap
will automatically handle links ifautoHandleLinks
is enabled, by delegating toLinkHandler.handleLink
. You can also manually handle links by callingPntaFlutter.handleLink(link)
.
Dart API
// Call once, e.g. in main()
await PntaFlutter.initialize(
autoHandleLinks: false, // default: false
showSystemUI: false, // default: false
);
// Listen for foreground notifications (always delivered)
PntaFlutter.foregroundNotifications.listen((payload) { ... });
// Listen for notification taps (background/terminated)
PntaFlutter.onNotificationTap.listen((payload) { ... });
Example Notification Payload
{
"to": "<device_token>",
"data": {
"title": "Test Link",
"body": "Tap to open a deep link!",
"link_to": "/deep-link"
}
}
Platform-specific Setup for URL Handling
Android:
- Add the following
<queries>
block to yourAndroidManifest.xml
(as a child of<manifest>
, before<application>
):
<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>
- This is required for
url_launcher
to work on Android 11+ (API 30+). - Make sure a browser is installed and set up on your device/emulator.
iOS:
- No extra setup is required for external URLs. The plugin uses
url_launcher
which works out of the box. - For deep links, ensure your app's
MaterialApp
uses the global navigator key:
MaterialApp(
navigatorKey: PntaFlutter.navigatorKey,
// ...
)
Notes
- Foreground notifications are always delivered to Dart, regardless of the
autoHandleLinks
setting. You have full control over how to handle them. - The
autoHandleLinks
feature only applies when the app is launched or resumed from a notification tap (background/terminated state). - If
link_to
is invalid or cannot be handled, an error is logged but the app will not crash. - The MainActivity override (see below) is only required for notification tap (background) events on Android. Foreground notifications do not require any MainActivity changes.
import android.content.Intent
import io.flutter.embedding.android.FlutterActivity
import io.pnta.pnta_flutter.NotificationTapHandler
class MainActivity: FlutterActivity() {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
val extras = intent.extras
if (extras != null && !extras.isEmpty) {
val payload = mutableMapOf<String, Any>()
for (key in extras.keySet()) {
val value = extras.get(key)
when (value) {
is String, is Int, is Boolean, is Double, is Float, is Long -> payload[key] = value
else -> payload[key] = value.toString()
}
}
if (payload.isNotEmpty()) {
NotificationTapHandler.sendTapPayload(payload)
}
}
}
}
8. Link Handling Rules and Deep Linking #
When handling link_to
payloads, the plugin uses the following rule:
- If the link contains
://
(e.g.,http://
,mailto://
,myapp://
), it is treated as an external URI and opened via the OS usingurl_launcher
inLaunchMode.externalApplication
. - Otherwise (e.g.,
/home
,/profile
), the link is treated as an internal Flutter route and is pushed using the globalnavigatorKey
.
Important: Deep Linking for Custom Schemes
If you use custom URI schemes (such as myapp://posts
) in your link_to
payloads, you must set up deep linking on your platform (Android/iOS) for your app to handle these links. If deep linking is not set up, the OS will attempt to open the link externally, and your app may not receive it.
If you have not set up deep linking for your custom schemes, use standard internal route names (like /appointments
) and rely on the navigatorKey
for in-app navigation.
- For more information on deep linking in Flutter, see the Flutter deep linking documentation.
Example #
See the example/
app for a working usage example.