Skip to content

Commit f44be3f

Browse files
committed
fix(examples): broaden transient send failure detection
1 parent b67a677 commit f44be3f

4 files changed

Lines changed: 58 additions & 40 deletions

File tree

examples/demo/lib/services/onesignal_api_service.dart

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,13 @@ class OneSignalApiService {
7676
const maxAttempts = 5;
7777
int backoffMs(int n) => 2000 * (1 << (n - 1));
7878

79-
// Retry on `invalid_player_ids` to absorb the brief race where the
80-
// subscription has been created locally but is not yet visible to the
81-
// /notifications endpoint.
79+
// Retry while the OneSignal backend hasn't yet indexed the freshly
80+
// created subscription. The /notifications endpoint reports this race in a
81+
// few different shapes, all of which return HTTP 200:
82+
// - {"errors":{"invalid_player_ids":[...]}}
83+
// - {"id":"","errors":["All included players are not subscribed"]}
84+
// - {"id":"","errors":[...]}
85+
// Treat any 200 response without a real notification id as transient.
8286
for (var attempt = 1; attempt <= maxAttempts; attempt++) {
8387
try {
8488
final response = await http.post(
@@ -96,23 +100,13 @@ class OneSignalApiService {
96100
}
97101

98102
final decoded = jsonDecode(response.body);
99-
if (decoded is Map<String, dynamic>) {
100-
final errors = decoded['errors'];
101-
if (errors is Map<String, dynamic>) {
102-
final invalidIds = errors['invalid_player_ids'];
103-
if (invalidIds is List && invalidIds.isNotEmpty) {
104-
if (attempt < maxAttempts) {
105-
await Future<void>.delayed(
106-
Duration(milliseconds: backoffMs(attempt)),
107-
);
108-
continue;
109-
}
110-
debugPrint(
111-
'Send notification failed: invalid_player_ids $invalidIds',
112-
);
113-
return false;
114-
}
103+
if (_isTransientSendFailure(decoded)) {
104+
if (attempt < maxAttempts) {
105+
await Future<void>.delayed(Duration(milliseconds: backoffMs(attempt)));
106+
continue;
115107
}
108+
debugPrint('Send notification failed: ${response.body}');
109+
return false;
116110
}
117111

118112
return true;
@@ -125,6 +119,17 @@ class OneSignalApiService {
125119
return false;
126120
}
127121

122+
bool _isTransientSendFailure(dynamic decoded) {
123+
if (decoded is! Map<String, dynamic>) return false;
124+
final id = decoded['id'];
125+
final errors = decoded['errors'];
126+
final hasErrors =
127+
(errors is List && errors.isNotEmpty) ||
128+
(errors is Map && errors.isNotEmpty);
129+
final missingId = id is! String || id.isEmpty;
130+
return hasErrors || missingId;
131+
}
132+
128133
Future<bool> updateLiveActivity(
129134
String activityId,
130135
Map<String, dynamic> eventUpdates,

examples/demo/lib/viewmodels/app_viewmodel.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,11 @@ class AppViewModel extends ChangeNotifier {
6969

7070
// Push state
7171
String? _pushSubscriptionId;
72-
String? get pushSubscriptionId => _pushSubscriptionId;
72+
// The native bridge can hand back an empty string before the subscription
73+
// id is provisioned. Treat that as "no id yet" so the UI's `?? '—'`
74+
// fallback renders the placeholder instead of an empty cell.
75+
String? get pushSubscriptionId =>
76+
(_pushSubscriptionId?.isEmpty ?? true) ? null : _pushSubscriptionId;
7377

7478
bool _pushEnabled = false;
7579
bool get pushEnabled => _pushEnabled;

examples/demo_pods/lib/services/onesignal_api_service.dart

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,13 @@ class OneSignalApiService {
7676
const maxAttempts = 5;
7777
int backoffMs(int n) => 2000 * (1 << (n - 1));
7878

79-
// Retry on `invalid_player_ids` to absorb the brief race where the
80-
// subscription has been created locally but is not yet visible to the
81-
// /notifications endpoint.
79+
// Retry while the OneSignal backend hasn't yet indexed the freshly
80+
// created subscription. The /notifications endpoint reports this race in a
81+
// few different shapes, all of which return HTTP 200:
82+
// - {"errors":{"invalid_player_ids":[...]}}
83+
// - {"id":"","errors":["All included players are not subscribed"]}
84+
// - {"id":"","errors":[...]}
85+
// Treat any 200 response without a real notification id as transient.
8286
for (var attempt = 1; attempt <= maxAttempts; attempt++) {
8387
try {
8488
final response = await http.post(
@@ -96,23 +100,13 @@ class OneSignalApiService {
96100
}
97101

98102
final decoded = jsonDecode(response.body);
99-
if (decoded is Map<String, dynamic>) {
100-
final errors = decoded['errors'];
101-
if (errors is Map<String, dynamic>) {
102-
final invalidIds = errors['invalid_player_ids'];
103-
if (invalidIds is List && invalidIds.isNotEmpty) {
104-
if (attempt < maxAttempts) {
105-
await Future<void>.delayed(
106-
Duration(milliseconds: backoffMs(attempt)),
107-
);
108-
continue;
109-
}
110-
debugPrint(
111-
'Send notification failed: invalid_player_ids $invalidIds',
112-
);
113-
return false;
114-
}
103+
if (_isTransientSendFailure(decoded)) {
104+
if (attempt < maxAttempts) {
105+
await Future<void>.delayed(Duration(milliseconds: backoffMs(attempt)));
106+
continue;
115107
}
108+
debugPrint('Send notification failed: ${response.body}');
109+
return false;
116110
}
117111

118112
return true;
@@ -125,6 +119,17 @@ class OneSignalApiService {
125119
return false;
126120
}
127121

122+
bool _isTransientSendFailure(dynamic decoded) {
123+
if (decoded is! Map<String, dynamic>) return false;
124+
final id = decoded['id'];
125+
final errors = decoded['errors'];
126+
final hasErrors =
127+
(errors is List && errors.isNotEmpty) ||
128+
(errors is Map && errors.isNotEmpty);
129+
final missingId = id is! String || id.isEmpty;
130+
return hasErrors || missingId;
131+
}
132+
128133
Future<bool> updateLiveActivity(
129134
String activityId,
130135
Map<String, dynamic> eventUpdates,

examples/demo_pods/lib/viewmodels/app_viewmodel.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,11 @@ class AppViewModel extends ChangeNotifier {
6464

6565
// Push state
6666
String? _pushSubscriptionId;
67-
String? get pushSubscriptionId => _pushSubscriptionId;
67+
// The native bridge can hand back an empty string before the subscription
68+
// id is provisioned. Treat that as "no id yet" so the UI's `?? '—'`
69+
// fallback renders the placeholder instead of an empty cell.
70+
String? get pushSubscriptionId =>
71+
(_pushSubscriptionId?.isEmpty ?? true) ? null : _pushSubscriptionId;
6872

6973
bool _pushEnabled = false;
7074
bool get pushEnabled => _pushEnabled;

0 commit comments

Comments
 (0)