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
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
## 0.28.0

Completeness pass: the Dart API now mirrors more of the native Facebook App Events surface, keeping the 1:1 Dart ↔ Kotlin ↔ Swift mapping. Purely additive except the documented debug-logging change below.

### New native-mirroring methods

- Add `logProductItem(...)` for product-catalog item logging, with type-safe `ProductAvailability` and `ProductCondition` enums (iOS `logProductItem`, Android `AppEventsLogger.logProductItem`).
- Add `setPushNotificationToken(String)` to register a push token for Meta push-campaign measurement (iOS `setPushNotificationsDeviceToken`, Android `setPushNotificationsRegistrationId`).
- Add `setFlushBehavior(FlushBehavior)` / `getFlushBehavior()` to control auto vs explicit-only event flushing.
- Add `getUserData()` and `getUserID()` getters.
- Add `clearUserDataForType(FacebookUserDataField)` — functional on iOS; **no-op on Android** (the Android SDK has no per-field clear; use `clearUserData()`). Documented in the README "Known Limitations".

### Behavior change

- Add explicit `setDebugLoggingEnabled(bool)` (verbose SDK app-event/network logging) on both platforms, and **remove the hidden debug-logging side effect** that `setAdvertiserTracking` previously triggered on Android in debug builds. Call `setDebugLoggingEnabled` explicitly if you relied on that behavior.

### Additional standard-event convenience helpers

- Add Dart shorthands (routed through `logEvent`) for the remaining Meta standard events: `logAchievedLevel`, `logAddedPaymentInfo`, `logCompletedTutorial`, `logSearched`, `logSpentCredits`, `logUnlockedAchievement`, `logContact`, `logCustomizeProduct`, `logDonate`, `logFindLocation`, `logSchedule`, `logSubmitApplication`, plus their `eventName*` constants.

### Internal

- Move the new enums to `lib/src/enums.dart` and the standard-event helpers to `lib/src/standard_events.dart` (re-exported; no import changes for consumers).
- Remove unused `GraphRequest`/`GraphResponse` imports from the Android plugin.
- Add Dart unit tests for all new methods and demonstrate them in the example app.

## 0.27.2

- Tighten the AGP 9 Kotlin-plugin guard introduced in 0.27.1. The previous check skipped `apply plugin: "kotlin-android"` for *any* AGP 9 build, but users running AGP 9 with `android.builtInKotlin=false` (opting out of built-in Kotlin) still need the plugin applied. Now keyed on both the AGP major version and the `android.builtInKotlin` Gradle property (PR [#485](https://github.com/oddbit/flutter_facebook_app_events/pull/485)).
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ This is a plugin-specific workaround for a [known upstream issue in the iOS SDK]

`setDataProcessingOptions` is **functional on Android** but is a **no-op on iOS** (a warning is printed). Meta removed the underlying API from the Facebook iOS SDK in the 18.x series; there is no native replacement callable from the SDK. If you need to configure data use on iOS, use Meta's [Data Use Checkup](https://developers.facebook.com/docs/development/data-processing-options) tooling in the app dashboard instead.

### `clearUserDataForType` on Android

`clearUserDataForType` is **functional on iOS** but is a **no-op on Android** (a warning is logged). The Android `AppEventsLogger` exposes no per-field clear; call `clearUserData()` to clear all previously-set user data fields at once.

### Facebook Event Manager "Please Upgrade SDK" Warning

When setting up codeless events in Facebook Event Manager, you may encounter a warning message stating:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@ import android.os.Bundle
import android.util.Log
import com.facebook.FacebookSdk
import com.facebook.appevents.AppEventsLogger
import com.facebook.GraphRequest
import com.facebook.GraphResponse
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import java.math.BigDecimal
import java.util.Currency
import com.facebook.LoggingBehavior

Expand Down Expand Up @@ -74,6 +73,14 @@ class FacebookAppEventsPlugin: FlutterPlugin, MethodCallHandler {
"logPurchase" -> handlePurchased(call, result)
"setAdvertiserTracking" -> handleSetAdvertiserTracking(call, result)
"setGraphApiVersion" -> handleSetGraphApiVersion(call, result)
"logProductItem" -> handleLogProductItem(call, result)
"setPushNotificationToken" -> handleSetPushNotificationToken(call, result)
"setFlushBehavior" -> handleSetFlushBehavior(call, result)
"getFlushBehavior" -> handleGetFlushBehavior(call, result)
"getUserData" -> handleGetUserData(call, result)
"getUserID" -> handleGetUserId(call, result)
"clearUserDataForType" -> handleClearUserDataForType(call, result)
"setDebugLoggingEnabled" -> handleSetDebugLoggingEnabled(call, result)

else -> result.notImplemented()
}
Expand Down Expand Up @@ -152,12 +159,6 @@ class FacebookAppEventsPlugin: FlutterPlugin, MethodCallHandler {

FacebookSdk.setAdvertiserIDCollectionEnabled(enabled && collectId)

if (BuildConfig.DEBUG) {
FacebookSdk.setIsDebugEnabled(true && enabled)
FacebookSdk.addLoggingBehavior(LoggingBehavior.APP_EVENTS)
FacebookSdk.addLoggingBehavior(LoggingBehavior.REQUESTS)
}

result.success(null)
}

Expand Down Expand Up @@ -274,4 +275,133 @@ class FacebookAppEventsPlugin: FlutterPlugin, MethodCallHandler {
appEventsLogger.logPurchase(amount, currency, parameterBundle)
result.success(null)
}

private fun handleLogProductItem(call: MethodCall, result: Result) {
val itemId = call.argument<String>("itemId")
val availabilityToken = call.argument<String>("availability")
val conditionToken = call.argument<String>("condition")
val description = call.argument<String>("description")
val imageLink = call.argument<String>("imageLink")
val link = call.argument<String>("link")
val title = call.argument<String>("title")
val priceAmount = call.argument<Double>("priceAmount")
val currencyCode = call.argument<String>("currency")
val gtin = call.argument<String>("gtin")
val mpn = call.argument<String>("mpn")
val brand = call.argument<String>("brand")

if (itemId == null || availabilityToken == null || conditionToken == null ||
description == null || imageLink == null || link == null || title == null ||
priceAmount == null || currencyCode == null) {
result.error("INVALID_ARGUMENT", "Missing required logProductItem fields", null)
return
}
if (gtin == null && mpn == null && brand == null) {
result.error("INVALID_ARGUMENT", "At least one of gtin, mpn or brand is required", null)
return
}

val availability = productAvailabilityFromToken(availabilityToken)
val condition = productConditionFromToken(conditionToken)
if (availability == null || condition == null) {
result.error("INVALID_ARGUMENT", "Invalid availability or condition value", null)
return
}

val parameters = call.argument<Map<String, Any>>("parameters")
val parameterBundle = createBundleFromMap(parameters) ?: Bundle()

appEventsLogger.logProductItem(
itemId,
availability,
condition,
description,
imageLink,
link,
title,
BigDecimal.valueOf(priceAmount),
Currency.getInstance(currencyCode),
gtin,
mpn,
brand,
parameterBundle
)
result.success(null)
}

private fun productAvailabilityFromToken(token: String): AppEventsLogger.ProductAvailability? {
return when (token) {
"inStock" -> AppEventsLogger.ProductAvailability.IN_STOCK
"outOfStock" -> AppEventsLogger.ProductAvailability.OUT_OF_STOCK
"preorder" -> AppEventsLogger.ProductAvailability.PREORDER
// Note: the Android SDK enum constant is historically misspelled.
"availableForOrder" -> AppEventsLogger.ProductAvailability.AVALIABLE_FOR_ORDER
"discontinued" -> AppEventsLogger.ProductAvailability.DISCONTINUED
else -> null
}
}

private fun productConditionFromToken(token: String): AppEventsLogger.ProductCondition? {
return when (token) {
"newItem" -> AppEventsLogger.ProductCondition.NEW
"refurbished" -> AppEventsLogger.ProductCondition.REFURBISHED
"used" -> AppEventsLogger.ProductCondition.USED
else -> null
}
}

private fun handleSetPushNotificationToken(call: MethodCall, result: Result) {
val token = call.arguments as? String
if (token == null) {
result.error("INVALID_ARGUMENT", "Push notification token is required", null)
return
}
AppEventsLogger.setPushNotificationsRegistrationId(token)
result.success(null)
}

private fun handleSetFlushBehavior(call: MethodCall, result: Result) {
val behavior = when (call.arguments as? String) {
"explicitOnly" -> AppEventsLogger.FlushBehavior.EXPLICIT_ONLY
else -> AppEventsLogger.FlushBehavior.AUTO
}
AppEventsLogger.setFlushBehavior(behavior)
result.success(null)
}

private fun handleGetFlushBehavior(call: MethodCall, result: Result) {
val token = when (AppEventsLogger.getFlushBehavior()) {
AppEventsLogger.FlushBehavior.EXPLICIT_ONLY -> "explicitOnly"
else -> "auto"
}
result.success(token)
}

private fun handleGetUserData(call: MethodCall, result: Result) {
result.success(AppEventsLogger.getUserData())
}

private fun handleGetUserId(call: MethodCall, result: Result) {
result.success(AppEventsLogger.getUserID())
}

private fun handleClearUserDataForType(call: MethodCall, result: Result) {
// Android's AppEventsLogger has no per-field clear; this is intentionally a
// no-op. Use clearUserData() to clear all fields. See README "Known Limitations".
Log.w(logTag, "clearUserDataForType is not supported on Android; use clearUserData() to clear all fields.")
result.success(null)
}

private fun handleSetDebugLoggingEnabled(call: MethodCall, result: Result) {
val enabled = call.arguments as? Boolean ?: false
FacebookSdk.setIsDebugEnabled(enabled)
if (enabled) {
FacebookSdk.addLoggingBehavior(LoggingBehavior.APP_EVENTS)
FacebookSdk.addLoggingBehavior(LoggingBehavior.REQUESTS)
} else {
FacebookSdk.removeLoggingBehavior(LoggingBehavior.APP_EVENTS)
FacebookSdk.removeLoggingBehavior(LoggingBehavior.REQUESTS)
}
result.success(null)
}
}
4 changes: 2 additions & 2 deletions example/android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ pluginManagement {

plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.1.1" apply false
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
id "com.android.application" version "8.9.2" apply false
id "org.jetbrains.kotlin.android" version "2.3.10" apply false
}

include ":app"
Loading
Loading