Skip to content

Commit cb9284a

Browse files
authored
Fix registration lifecycle and simplify API (#14)
Fix registration lifecycle for delayed registration scenarios and simplify API by using getter pattern for device token access. - Add permission checking without prompting for consistent registration - Change initialize() and registerDevice() to return Future<void> - Use PntaFlutter.deviceToken getter to access token - Remove metadata parameter from registerDevice() - Update documentation and examples
1 parent 05c6b93 commit cb9284a

File tree

10 files changed

+104
-49
lines changed

10 files changed

+104
-49
lines changed

README.md

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,10 @@ void main() async {
135135
);
136136
137137
// Optional: Get the device token if you need it for your backend
138-
// final deviceToken = await PntaFlutter.initialize(...);
139-
// if (deviceToken != null) {
140-
// print('Device token: $deviceToken');
141-
// }
138+
final deviceToken = PntaFlutter.deviceToken;
139+
if (deviceToken != null) {
140+
print('Device token: $deviceToken');
141+
}
142142
143143
runApp(MyApp());
144144
}
@@ -163,31 +163,30 @@ For apps that need to ask for permission at a specific time (e.g., after user on
163163
void main() async {
164164
WidgetsFlutterBinding.ensureInitialized();
165165
166-
// Initialize without registering device
166+
// Initialize without registering device, but include metadata for later use
167167
await PntaFlutter.initialize(
168168
'prj_XXXXXXXXX',
169169
registerDevice: false, // Skip device registration
170+
metadata: {
171+
'user_id': '123',
172+
'user_email': 'user@example.com',
173+
},
170174
);
171175
172176
runApp(MyApp());
173177
}
174178
175179
// Later in your app, when ready to register:
176180
Future<void> setupNotifications() async {
177-
await PntaFlutter.registerDevice(
178-
metadata: {
179-
'user_id': '123',
180-
'user_email': 'user@example.com',
181-
},
182-
);
181+
await PntaFlutter.registerDevice();
183182
184183
// Optional: Get the device token if you need it for your backend
185-
// final deviceToken = await PntaFlutter.registerDevice(...);
186-
// if (deviceToken != null) {
187-
// print('Device registered successfully!');
188-
// } else {
189-
// print('Registration failed');
190-
// }
184+
final deviceToken = PntaFlutter.deviceToken;
185+
if (deviceToken != null) {
186+
print('Device registered successfully! Token: $deviceToken');
187+
} else {
188+
print('Registration failed or not completed yet');
189+
}
191190
}
192191
```
193192

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

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

246-
#### `PntaFlutter.registerDevice({Map<String, dynamic>? metadata})`
245+
#### `PntaFlutter.registerDevice()`
247246

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

250-
- `metadata`: Optional device metadata to include during registration
251-
252-
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.**
249+
Returns `Future<void>`. Use `PntaFlutter.deviceToken` getter to access the device token after successful registration.
253250

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

@@ -309,16 +306,18 @@ void main() async {
309306
WidgetsFlutterBinding.ensureInitialized();
310307
311308
// One-line setup - handles permission, registration, and configuration
312-
final deviceToken = await PntaFlutter.initialize(
309+
await PntaFlutter.initialize(
313310
'prj_XXXXXXXXX', // Your project ID from app.pnta.io
314311
metadata: {
315312
'user_id': '123',
316313
'user_email': 'user@example.com',
317314
},
318315
);
319316
317+
// Optional: Get the device token if you need it for your backend
318+
final deviceToken = PntaFlutter.deviceToken;
320319
if (deviceToken != null) {
321-
print('Device registered successfully!');
320+
print('Device registered successfully! Token: $deviceToken');
322321
}
323322
324323
runApp(MyApp());

android/src/main/kotlin/io/pnta/pnta_flutter/PermissionHandler.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,20 @@ object PermissionHandler {
2929
}
3030
}
3131

32+
fun checkNotificationPermission(activity: Activity?, result: Result) {
33+
if (Build.VERSION.SDK_INT >= 33) {
34+
if (activity == null) {
35+
result.error("NO_ACTIVITY", "Activity is null", null)
36+
return
37+
}
38+
val granted = ContextCompat.checkSelfPermission(activity, android.Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
39+
result.success(granted)
40+
} else {
41+
// Permission is automatically granted on older versions
42+
result.success(true)
43+
}
44+
}
45+
3246
fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray): Boolean {
3347
if (requestCode == REQUEST_CODE) {
3448
permissionResult?.success(grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)

android/src/main/kotlin/io/pnta/pnta_flutter/PntaFlutterPlugin.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ class PntaFlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, Plugin
5656
override fun onMethodCall(call: MethodCall, result: Result) {
5757
if (call.method == "requestNotificationPermission") {
5858
PermissionHandler.requestNotificationPermission(activity, result)
59+
} else if (call.method == "checkNotificationPermission") {
60+
PermissionHandler.checkNotificationPermission(activity, result)
5961
} else if (call.method == "getDeviceToken") {
6062
TokenHandler.getDeviceToken(activity, result)
6163
} else if (call.method == "identify") {

example/lib/main.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class _HomeScreenState extends State<HomeScreen> {
5050
}
5151

5252
Future<void> _initPnta() async {
53-
final token = await PntaFlutter.initialize(
53+
await PntaFlutter.initialize(
5454
'prj_k3e0Givq',
5555
metadata: {
5656
'user_id': '123',
@@ -60,7 +60,7 @@ class _HomeScreenState extends State<HomeScreen> {
6060
autoHandleLinks: false, // We'll handle links manually for demo
6161
);
6262
setState(() {
63-
_deviceToken = token;
63+
_deviceToken = PntaFlutter.deviceToken;
6464
_loading = false;
6565
});
6666
}

ios/Classes/PermissionHandler.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,22 @@ class PermissionHandler {
1515
result(true)
1616
}
1717
}
18+
19+
static func checkNotificationPermission(result: @escaping FlutterResult) {
20+
if #available(iOS 10.0, *) {
21+
UNUserNotificationCenter.current().getNotificationSettings { settings in
22+
DispatchQueue.main.async {
23+
let granted = settings.authorizationStatus == .authorized ||
24+
settings.authorizationStatus == .provisional ||
25+
settings.authorizationStatus == .ephemeral
26+
result(granted)
27+
}
28+
}
29+
} else {
30+
// For iOS versions below 10, permissions are granted at app install time
31+
DispatchQueue.main.async {
32+
result(true)
33+
}
34+
}
35+
}
1836
}

ios/Classes/PntaFlutterPlugin.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public class PntaFlutterPlugin: NSObject, FlutterPlugin, UIApplicationDelegate {
1717
switch call.method {
1818
case "requestNotificationPermission":
1919
PermissionHandler.requestNotificationPermission(result: result)
20+
case "checkNotificationPermission":
21+
PermissionHandler.checkNotificationPermission(result: result)
2022
case "getDeviceToken":
2123
TokenHandler.getDeviceToken(result: result)
2224
case "identify":

lib/pnta_flutter.dart

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class PntaFlutter {
2626

2727
// Setup
2828
/// Main initialization - handles everything for most apps
29-
static Future<String?> initialize(
29+
static Future<void> initialize(
3030
String projectId, {
3131
Map<String, dynamic>? metadata,
3232
bool registerDevice = true,
@@ -35,13 +35,13 @@ class PntaFlutter {
3535
}) async {
3636
if (_config != null) {
3737
debugPrint('PNTA: Already initialized.');
38-
return _deviceToken;
38+
return;
3939
}
4040
try {
4141
// Validate project ID
4242
if (!projectId.startsWith('prj_')) {
4343
debugPrint('PNTA: Invalid project ID. Must start with "prj_".');
44-
return null;
44+
return;
4545
}
4646
_config = _PntaFlutterConfig(
4747
projectId: projectId,
@@ -53,27 +53,31 @@ class PntaFlutter {
5353
LinkHandler.initialize(autoHandleLinks: autoHandleLinks);
5454
await PntaFlutterPlatform.instance
5555
.setForegroundPresentationOptions(showSystemUI: showSystemUI);
56-
if (registerDevice) {
57-
return await _performRegistration(metadata: metadata);
58-
} else {
59-
// Delayed registration scenario
60-
return null;
56+
57+
// Always check if permissions are already granted
58+
final permissionsGranted =
59+
await PntaFlutterPlatform.instance.checkNotificationPermission();
60+
61+
if (permissionsGranted) {
62+
// Always register if permissions available, regardless of registerDevice flag
63+
await _performRegistration(metadata: metadata, skipPrompt: true);
64+
} else if (registerDevice) {
65+
// Only prompt for permissions if registerDevice: true
66+
await _performRegistration(metadata: metadata);
6167
}
6268
} catch (e, st) {
6369
debugPrint('PNTA: Initialization error: $e\n$st');
64-
return null;
6570
}
6671
}
6772

6873
// Registration
69-
/// For delayed registration scenarios
70-
static Future<String?> registerDevice(
71-
{Map<String, dynamic>? metadata}) async {
74+
/// For delayed registration scenarios - prompts for permissions and registers if granted
75+
static Future<void> registerDevice() async {
7276
if (_config == null) {
7377
debugPrint('PNTA: Must call initialize() before registering device.');
74-
return null;
78+
return;
7579
}
76-
return await _performRegistration(metadata: metadata);
80+
await _performRegistration(metadata: _config!.metadata);
7781
}
7882

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

121125
// Private
122-
static Future<String?> _performRegistration(
123-
{Map<String, dynamic>? metadata}) async {
126+
static Future<void> _performRegistration({
127+
Map<String, dynamic>? metadata,
128+
bool skipPrompt = false,
129+
}) async {
124130
try {
125-
final granted =
126-
await PntaFlutterPlatform.instance.requestNotificationPermission();
131+
final granted = skipPrompt
132+
? await PntaFlutterPlatform.instance.checkNotificationPermission()
133+
: await PntaFlutterPlatform.instance.requestNotificationPermission();
134+
127135
if (!granted) {
128136
debugPrint('PNTA: Notification permission denied.');
129-
return null;
137+
return;
130138
}
131139

132140
// Pure device registration with SDK version
@@ -139,11 +147,8 @@ class PntaFlutter {
139147
.updateMetadata(_config!.projectId, metadata);
140148
_config!.metadata = metadata;
141149
}
142-
143-
return _deviceToken;
144150
} catch (e, st) {
145151
debugPrint('PNTA: Registration error: $e\n$st');
146-
return null;
147152
}
148153
}
149154
}

lib/pnta_flutter_method_channel.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ class MethodChannelPntaFlutter extends PntaFlutterPlatform {
2525
return result ?? false;
2626
}
2727

28+
@override
29+
Future<bool> checkNotificationPermission() async {
30+
final result =
31+
await methodChannel.invokeMethod<bool>('checkNotificationPermission');
32+
return result ?? false;
33+
}
34+
2835
@override
2936
Future<String?> getDeviceToken() async {
3037
final token = await methodChannel.invokeMethod<String>('getDeviceToken');

lib/pnta_flutter_platform_interface.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ abstract class PntaFlutterPlatform extends PlatformInterface {
2828
'requestNotificationPermission() has not been implemented.');
2929
}
3030

31+
Future<bool> checkNotificationPermission() {
32+
throw UnimplementedError(
33+
'checkNotificationPermission() has not been implemented.');
34+
}
35+
3136
Future<String?> getDeviceToken() {
3237
throw UnimplementedError('getDeviceToken() has not been implemented.');
3338
}

test/pnta_flutter_test.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ class MockPntaFlutterPlatform
1010
@override
1111
Future<bool> requestNotificationPermission() => Future.value(true);
1212

13+
@override
14+
Future<bool> checkNotificationPermission() => Future.value(true);
15+
1316
@override
1417
Future<String?> getDeviceToken() => Future.value('mock_token');
1518

0 commit comments

Comments
 (0)