Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 24 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,10 @@ void main() async {
);

// Optional: Get the device token if you need it for your backend
// final deviceToken = await PntaFlutter.initialize(...);
// if (deviceToken != null) {
// print('Device token: $deviceToken');
// }
final deviceToken = PntaFlutter.deviceToken;
if (deviceToken != null) {
print('Device token: $deviceToken');
}

runApp(MyApp());
}
Expand All @@ -163,31 +163,30 @@ For apps that need to ask for permission at a specific time (e.g., after user on
void main() async {
WidgetsFlutterBinding.ensureInitialized();

// Initialize without registering device
// 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(
metadata: {
'user_id': '123',
'user_email': 'user@example.com',
},
);
await PntaFlutter.registerDevice();

// Optional: Get the device token if you need it for your backend
// final deviceToken = await PntaFlutter.registerDevice(...);
// if (deviceToken != null) {
// print('Device registered successfully!');
// } else {
// print('Registration failed');
// }
final deviceToken = PntaFlutter.deviceToken;
if (deviceToken != null) {
print('Device registered successfully! Token: $deviceToken');
} else {
print('Registration failed or not completed yet');
}
}
```

Expand Down Expand Up @@ -241,15 +240,13 @@ Main initialization method that handles everything for most apps:
- `autoHandleLinks`: Automatically handle `link_to` URLs when notifications are tapped (default: `false`)
- `showSystemUI`: Show system notification banner/sound when app is in foreground (default: `false`)

Returns `Future<String?>` - the device token if device was registered, null otherwise.
Returns `Future<void>`. Use `PntaFlutter.deviceToken` getter to access the device token after successful registration.

#### `PntaFlutter.registerDevice({Map<String, dynamic>? metadata})`
#### `PntaFlutter.registerDevice()`

For delayed registration scenarios. Requests notification permission and registers device. Must be called after `initialize()` with `registerDevice: false`.
For delayed registration scenarios. Requests notification permission and registers device using metadata from `initialize()`. Must be called after `initialize()` with `registerDevice: false`.

- `metadata`: Optional device metadata to include during registration

Returns `Future<String?>` - the device token if permission was granted and device registered successfully, null otherwise. **Note: You can ignore the return value if you don't need the token.**
Returns `Future<void>`. Use `PntaFlutter.deviceToken` getter to access the device token after successful registration.

#### `PntaFlutter.updateMetadata(Map<String, dynamic> metadata)`

Expand Down Expand Up @@ -309,16 +306,18 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized();

// One-line setup - handles permission, registration, and configuration
final deviceToken = await PntaFlutter.initialize(
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!');
print('Device registered successfully! Token: $deviceToken');
}

runApp(MyApp());
Expand Down
14 changes: 14 additions & 0 deletions android/src/main/kotlin/io/pnta/pnta_flutter/PermissionHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ object PermissionHandler {
}
}

fun checkNotificationPermission(activity: Activity?, result: Result) {
if (Build.VERSION.SDK_INT >= 33) {
if (activity == null) {
result.error("NO_ACTIVITY", "Activity is null", null)
return
}
val granted = ContextCompat.checkSelfPermission(activity, android.Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
result.success(granted)
} else {
// Permission is automatically granted on older versions
result.success(true)
}
}

fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray): Boolean {
if (requestCode == REQUEST_CODE) {
permissionResult?.success(grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class PntaFlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, Plugin
override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "requestNotificationPermission") {
PermissionHandler.requestNotificationPermission(activity, result)
} else if (call.method == "checkNotificationPermission") {
PermissionHandler.checkNotificationPermission(activity, result)
} else if (call.method == "getDeviceToken") {
TokenHandler.getDeviceToken(activity, result)
} else if (call.method == "identify") {
Expand Down
4 changes: 2 additions & 2 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class _HomeScreenState extends State<HomeScreen> {
}

Future<void> _initPnta() async {
final token = await PntaFlutter.initialize(
await PntaFlutter.initialize(
'prj_k3e0Givq',
metadata: {
'user_id': '123',
Expand All @@ -60,7 +60,7 @@ class _HomeScreenState extends State<HomeScreen> {
autoHandleLinks: false, // We'll handle links manually for demo
);
setState(() {
_deviceToken = token;
_deviceToken = PntaFlutter.deviceToken;
_loading = false;
});
}
Expand Down
18 changes: 18 additions & 0 deletions ios/Classes/PermissionHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,22 @@ class PermissionHandler {
result(true)
}
}

static func checkNotificationPermission(result: @escaping FlutterResult) {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().getNotificationSettings { settings in
DispatchQueue.main.async {
let granted = settings.authorizationStatus == .authorized ||
settings.authorizationStatus == .provisional ||
settings.authorizationStatus == .ephemeral
result(granted)
}
}
} else {
// For iOS versions below 10, permissions are granted at app install time
DispatchQueue.main.async {
result(true)
}
}
}
}
2 changes: 2 additions & 0 deletions ios/Classes/PntaFlutterPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public class PntaFlutterPlugin: NSObject, FlutterPlugin, UIApplicationDelegate {
switch call.method {
case "requestNotificationPermission":
PermissionHandler.requestNotificationPermission(result: result)
case "checkNotificationPermission":
PermissionHandler.checkNotificationPermission(result: result)
case "getDeviceToken":
TokenHandler.getDeviceToken(result: result)
case "identify":
Expand Down
49 changes: 27 additions & 22 deletions lib/pnta_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class PntaFlutter {

// Setup
/// Main initialization - handles everything for most apps
static Future<String?> initialize(
static Future<void> initialize(
String projectId, {
Map<String, dynamic>? metadata,
bool registerDevice = true,
Expand All @@ -35,13 +35,13 @@ class PntaFlutter {
}) async {
if (_config != null) {
debugPrint('PNTA: Already initialized.');
return _deviceToken;
return;
}
try {
// Validate project ID
if (!projectId.startsWith('prj_')) {
debugPrint('PNTA: Invalid project ID. Must start with "prj_".');
return null;
return;
}
_config = _PntaFlutterConfig(
projectId: projectId,
Expand All @@ -53,27 +53,31 @@ class PntaFlutter {
LinkHandler.initialize(autoHandleLinks: autoHandleLinks);
await PntaFlutterPlatform.instance
.setForegroundPresentationOptions(showSystemUI: showSystemUI);
if (registerDevice) {
return await _performRegistration(metadata: metadata);
} else {
// Delayed registration scenario
return null;

// Always check if permissions are already granted
final permissionsGranted =
await PntaFlutterPlatform.instance.checkNotificationPermission();

if (permissionsGranted) {
// Always register if permissions available, regardless of registerDevice flag
await _performRegistration(metadata: metadata, skipPrompt: true);
} else if (registerDevice) {
// Only prompt for permissions if registerDevice: true
await _performRegistration(metadata: metadata);
}
} catch (e, st) {
debugPrint('PNTA: Initialization error: $e\n$st');
return null;
}
}

// Registration
/// For delayed registration scenarios
static Future<String?> registerDevice(
{Map<String, dynamic>? metadata}) async {
/// For delayed registration scenarios - prompts for permissions and registers if granted
static Future<void> registerDevice() async {
if (_config == null) {
debugPrint('PNTA: Must call initialize() before registering device.');
return null;
return;
}
return await _performRegistration(metadata: metadata);
await _performRegistration(metadata: _config!.metadata);
}

/// Update device metadata
Expand Down Expand Up @@ -119,14 +123,18 @@ class PntaFlutter {
static String? get deviceToken => _deviceToken;

// Private
static Future<String?> _performRegistration(
{Map<String, dynamic>? metadata}) async {
static Future<void> _performRegistration({
Map<String, dynamic>? metadata,
bool skipPrompt = false,
}) async {
try {
final granted =
await PntaFlutterPlatform.instance.requestNotificationPermission();
final granted = skipPrompt
? await PntaFlutterPlatform.instance.checkNotificationPermission()
: await PntaFlutterPlatform.instance.requestNotificationPermission();

if (!granted) {
debugPrint('PNTA: Notification permission denied.');
return null;
return;
}

// Pure device registration with SDK version
Expand All @@ -139,11 +147,8 @@ class PntaFlutter {
.updateMetadata(_config!.projectId, metadata);
_config!.metadata = metadata;
}

return _deviceToken;
} catch (e, st) {
debugPrint('PNTA: Registration error: $e\n$st');
return null;
}
}
}
7 changes: 7 additions & 0 deletions lib/pnta_flutter_method_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ class MethodChannelPntaFlutter extends PntaFlutterPlatform {
return result ?? false;
}

@override
Future<bool> checkNotificationPermission() async {
final result =
await methodChannel.invokeMethod<bool>('checkNotificationPermission');
return result ?? false;
}

@override
Future<String?> getDeviceToken() async {
final token = await methodChannel.invokeMethod<String>('getDeviceToken');
Expand Down
5 changes: 5 additions & 0 deletions lib/pnta_flutter_platform_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ abstract class PntaFlutterPlatform extends PlatformInterface {
'requestNotificationPermission() has not been implemented.');
}

Future<bool> checkNotificationPermission() {
throw UnimplementedError(
'checkNotificationPermission() has not been implemented.');
}

Future<String?> getDeviceToken() {
throw UnimplementedError('getDeviceToken() has not been implemented.');
}
Expand Down
3 changes: 3 additions & 0 deletions test/pnta_flutter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ class MockPntaFlutterPlatform
@override
Future<bool> requestNotificationPermission() => Future.value(true);

@override
Future<bool> checkNotificationPermission() => Future.value(true);

@override
Future<String?> getDeviceToken() => Future.value('mock_token');

Expand Down