Skip to content

Commit 05de767

Browse files
authored
Merge pull request #495 from oddbit/claude/codebase-review-97j9c8
fix: codebase review — Android getApplicationId staleness, clean push-payload errors, 32-bit guard (v0.30.3)
2 parents 68664ca + 1c4dd2b commit 05de767

10 files changed

Lines changed: 111 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 0.30.3
2+
3+
- **Fix (Android):** `getApplicationId` now reads the live `FacebookSdk.getApplicationId()` instead of the app id captured when the plugin attached to the engine, so app-id changes made programmatically after startup are reflected — matching iOS (`Settings.shared.appID`) and the documented behavior.
4+
- **Fix (Android):** `logPushNotificationOpen` now returns a clean `INVALID_ARGUMENT` error when the payload contains a value an Android `Bundle` cannot represent (e.g. a list), instead of an opaque platform exception. The error message names the offending key and suggests JSON-encoding structured values.
5+
- **Fix:** `setDataProcessingOptions` validates in the Dart layer that `country` and `state` fit in a signed 32-bit integer (the type the native APIs take) and throws a `RangeError` otherwise. Out-of-range values previously surfaced as an opaque `ClassCastException` on Android; iOS already rejected them natively.
6+
- Stop sending explicit `null`s over the method channel for omitted arguments of `setDataProcessingOptions` and `logPushNotificationOpen`, consistent with the rest of the API.
7+
18
## 0.30.2
29

310
- **Update Android toolchain** — AGP 8.13.0, Gradle 8.13, Kotlin 2.4.0, `compileSdk`/`targetSdk` 36. No change to `minSdk` or the Facebook Android SDK Maven range (`[18.0,19.0)`), which already resolves to the latest 18.x release (18.3.0); the CocoaPods/SPM `~> 18.0` / `"18.0.0"..<"19.0.0"` iOS pins likewise already cover the latest 18.x release (18.1.0), so no iOS dependency changes were needed this round.

android/src/main/kotlin/id/oddbit/flutter/facebook_app_events/FacebookAppEventsPlugin.kt

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,17 @@ class FacebookAppEventsPlugin: FlutterPlugin, MethodCallHandler {
146146
}
147147

148148
private fun handleGetApplicationId(call: MethodCall, result: Result) {
149-
result.success(appEventsLogger.applicationId)
149+
// Read the live SDK setting rather than `appEventsLogger.applicationId`,
150+
// which is captured when the logger is created at engine attach and would
151+
// go stale if the app id is changed programmatically afterwards. This
152+
// matches the iOS handler, which reads `Settings.shared.appID`.
153+
result.success(FacebookSdk.getApplicationId())
150154
}
151-
private fun handleGetAnonymousId(call: MethodCall, result: Result) {
155+
156+
private fun handleGetAnonymousId(call: MethodCall, result: Result) {
152157
result.success(anonymousId)
153158
}
154-
159+
155160
private fun handleSetGraphApiVersion(call: MethodCall, result: Result) {
156161
val version = call.arguments as? String
157162
if (version == null) {
@@ -215,7 +220,15 @@ class FacebookAppEventsPlugin: FlutterPlugin, MethodCallHandler {
215220
private fun handlePushNotificationOpen(call: MethodCall, result: Result) {
216221
val action = call.argument<String>("action")
217222
val payload = call.argument<Map<String, Any>>("payload")
218-
val payloadBundle = createBundleFromMap(payload)
223+
// Unlike logEvent parameters, the payload is not validated in the Dart
224+
// layer, so map Bundle-incompatible values (e.g. lists) to a clean error
225+
// instead of an opaque platform exception.
226+
val payloadBundle = try {
227+
createBundleFromMap(payload)
228+
} catch (e: IllegalArgumentException) {
229+
result.error("INVALID_ARGUMENT", e.message, null)
230+
return
231+
}
219232
if (payloadBundle == null) {
220233
result.error("INVALID_ARGUMENT", "Payload is required", null)
221234
return
@@ -266,7 +279,8 @@ class FacebookAppEventsPlugin: FlutterPlugin, MethodCallHandler {
266279
}
267280
}
268281
else -> throw IllegalArgumentException(
269-
"Unsupported value type: ${value::class}")
282+
"Unsupported value type ${value.javaClass.simpleName} for key '$key'; " +
283+
"encode structured values as a JSON string.")
270284
}
271285
}
272286
return bundle

example/lib/main.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import 'package:flutter/material.dart';
44
void main() => runApp(MyApp());
55

66
class MyApp extends StatelessWidget {
7+
const MyApp({super.key});
8+
79
static final facebookAppEvents = FacebookAppEvents();
810
@override
911
Widget build(BuildContext context) {

example/pubspec.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ packages:
4242
source: hosted
4343
version: "1.19.1"
4444
facebook_app_events:
45-
dependency: "direct dev"
45+
dependency: "direct main"
4646
description:
4747
path: ".."
4848
relative: true
4949
source: path
50-
version: "0.30.0"
50+
version: "0.30.3"
5151
fake_async:
5252
dependency: transitive
5353
description:

example/pubspec.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ dependencies:
1010
flutter:
1111
sdk: flutter
1212

13+
facebook_app_events:
14+
path: ../
15+
1316
dev_dependencies:
1417
flutter_test:
1518
sdk: flutter
1619

17-
facebook_app_events:
18-
path: ../
19-
2020
flutter:
2121
uses-material-design: true

ios/facebook_app_events.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'facebook_app_events'
3-
s.version = '0.30.2'
3+
s.version = '0.30.3'
44
s.summary = 'Flutter plugin for Facebook Analytics and App Events'
55
s.description = <<-DESC
66
Flutter plugin for Facebook Analytics and App Events

ios/facebook_app_events/Sources/facebook_app_events/FacebookAppEventsPlugin.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ public class FacebookAppEventsPlugin: NSObject, FlutterPlugin, FlutterSceneLifeC
196196
private func handleGetApplicationId(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
197197
// Settings.shared.appID resolves the Info.plist FacebookAppID by
198198
// default and reflects any app id set programmatically on the SDK,
199-
// matching Android's `appEventsLogger.applicationId`.
199+
// matching Android's `FacebookSdk.getApplicationId()`.
200200
result(Settings.shared.appID)
201201
}
202202

lib/facebook_app_events.dart

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ class FacebookAppEvents {
194194

195195
/// Returns the app ID this logger was configured to log to.
196196
///
197-
/// Maps to `appEventsLogger.applicationId` on Android and
197+
/// Maps to `FacebookSdk.getApplicationId()` on Android and
198198
/// `Settings.shared.appID` on iOS — both resolve the app id from platform
199199
/// configuration (`AndroidManifest.xml` / `Info.plist`) and reflect any app
200200
/// id set programmatically on the SDK.
@@ -260,7 +260,10 @@ class FacebookAppEvents {
260260
'action': action,
261261
};
262262

263-
return _channel.invokeMethod<void>('logPushNotificationOpen', args);
263+
return _channel.invokeMethod<void>(
264+
'logPushNotificationOpen',
265+
_filterOutNulls(args),
266+
);
264267
}
265268

266269
/// Sets a user [id] to associate with all app events.
@@ -422,6 +425,9 @@ class FacebookAppEvents {
422425
/// `setDataProcessingOptions(['LDU'], country: 0, state: 0)`. Passing an
423426
/// empty [options] list disables Limited Data Use.
424427
///
428+
/// [country] and [state] must fit in a signed 32-bit integer (the type the
429+
/// native APIs take); a [RangeError] is thrown otherwise.
430+
///
425431
/// See documentation:
426432
/// - https://developers.facebook.com/docs/development/data-processing-options
427433
/// - [iOS Settings](https://developers.facebook.com/docs/reference/iossdk/current/FBSDKCoreKit/classes/settings.html)
@@ -431,13 +437,19 @@ class FacebookAppEvents {
431437
int? country,
432438
int? state,
433439
}) {
440+
_checkFitsIn32Bits(country, 'country');
441+
_checkFitsIn32Bits(state, 'state');
442+
434443
final args = <String, dynamic>{
435444
'options': options,
436445
'country': country,
437446
'state': state,
438447
};
439448

440-
return _channel.invokeMethod<void>('setDataProcessingOptions', args);
449+
return _channel.invokeMethod<void>(
450+
'setDataProcessingOptions',
451+
_filterOutNulls(args),
452+
);
441453
}
442454

443455
/// Logs a purchase event.
@@ -813,6 +825,18 @@ class FacebookAppEvents {
813825
//
814826
// PRIVATE METHODS BELOW HERE
815827

828+
/// Throws a [RangeError] if [value] does not fit in a signed 32-bit integer.
829+
///
830+
/// The standard method codec delivers larger Dart ints as 64-bit values,
831+
/// which the native handlers cannot pass to SDK APIs typed as 32-bit ints.
832+
static void _checkFitsIn32Bits(int? value, String name) {
833+
const min = -0x80000000;
834+
const max = 0x7FFFFFFF;
835+
if (value != null && (value < min || value > max)) {
836+
throw RangeError.range(value, min, max, name);
837+
}
838+
}
839+
816840
/// Creates a new map containing all of the key/value pairs from [parameters]
817841
/// except those whose value is `null`.
818842
Map<String, dynamic> _filterOutNulls(Map<String, dynamic> parameters) {

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: facebook_app_events
22
description: Flutter plugin for Facebook App Events, an app measurement
33
solution that provides insight on app usage and user engagement in Facebook Analytics.
4-
version: 0.30.2
4+
version: 0.30.3
55
homepage: https://oddb.it/app-events-pubspec
66
repository: https://github.com/oddbit/flutter_facebook_app_events
77

test/facebook_app_events_test.dart

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,38 @@ void main() {
501501
),
502502
);
503503
});
504+
505+
test('setDataProcessingOptions omits null country and state', () async {
506+
await facebookAppEvents.setDataProcessingOptions(['LDU']);
507+
508+
expect(
509+
methodCall,
510+
isMethodCall(
511+
'setDataProcessingOptions',
512+
arguments: <String, dynamic>{
513+
'options': ['LDU'],
514+
},
515+
),
516+
);
517+
});
518+
519+
test('setDataProcessingOptions throws RangeError for values that do not '
520+
'fit in 32 bits', () {
521+
expect(
522+
() => facebookAppEvents.setDataProcessingOptions(
523+
['LDU'],
524+
country: 0x80000000,
525+
),
526+
throwsRangeError,
527+
);
528+
expect(
529+
() => facebookAppEvents.setDataProcessingOptions(
530+
['LDU'],
531+
state: -0x80000001,
532+
),
533+
throwsRangeError,
534+
);
535+
});
504536
});
505537

506538
group('User lifecycle', () {
@@ -559,6 +591,22 @@ void main() {
559591
),
560592
);
561593
});
594+
595+
test('logPushNotificationOpen omits action when null', () async {
596+
await facebookAppEvents.logPushNotificationOpen(
597+
payload: {'campaign': 'spring-sale'},
598+
);
599+
600+
expect(
601+
methodCall,
602+
isMethodCall(
603+
'logPushNotificationOpen',
604+
arguments: <String, dynamic>{
605+
'payload': {'campaign': 'spring-sale'},
606+
},
607+
),
608+
);
609+
});
562610
});
563611

564612
group('Standard event shorthands', () {

0 commit comments

Comments
 (0)