Skip to content

Commit 069c641

Browse files
committed
Add preferAppsFlyerBackupRules flag for Expo Android manifest
Add opt-in flag to control backup rules handling. Default respects app's rules; when enabled, removes app's backup rules to use AppsFlyer SDK's built-in rules.
1 parent 392877a commit 069c641

File tree

2 files changed

+106
-31
lines changed

2 files changed

+106
-31
lines changed

Docs/RN_ExpoInstallation.md

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ expo install react-native-appsflyer
2525
"react-native-appsflyer",
2626
{
2727
"shouldUseStrictMode": false, // optional – kids-apps strict mode
28-
"shouldUsePurchaseConnector": true // NEW – enables Purchase Connector
28+
"shouldUsePurchaseConnector": true, // optional – enables Purchase Connector
29+
"preferAppsFlyerBackupRules": false // optional – use AppsFlyer SDK backup rules (default: false)
2930
}
3031
]
3132
],
@@ -42,8 +43,40 @@ expo install react-native-appsflyer
4243
],
4344
...
4445
```
45-
### Fix for build failure with RN 0.76 and Expo 52
46-
To ensure seamless integration of the AppsFlyer plugin in your Expo-managed project, it’s essential to handle modifications to the AndroidManifest.xml correctly. Since direct edits to the AndroidManifest.xml aren’t feasible in the managed workflow, you’ll need to create a custom configuration to include the necessary changes.
46+
### Backup Rules Configuration (Android)
47+
48+
The AppsFlyer SDK includes built-in backup rules in its Android manifest to ensure accurate install/reinstall detection. By default, the plugin respects your app's backup rules and does not modify them.
49+
50+
**Default Behavior** (`preferAppsFlyerBackupRules: false` or omitted):
51+
- Your app's `android:dataExtractionRules` and `android:fullBackupContent` attributes are left untouched
52+
- You maintain full control over your app's backup policy
53+
- No manifest merge conflicts occur
54+
55+
**Opt-in Behavior** (`preferAppsFlyerBackupRules: true`):
56+
- If your app defines backup rules, they will be removed to let AppsFlyer SDK's built-in rules take precedence
57+
- This ensures AppsFlyer SDK's backup rules are used, which may improve install/reinstall detection accuracy
58+
- Use this flag if you want AppsFlyer SDK to manage backup rules for you
59+
60+
**When to use `preferAppsFlyerBackupRules: true`:**
61+
- You want AppsFlyer SDK to handle backup rules automatically
62+
- You're experiencing issues with install/reinstall detection that may be related to backup rules
63+
- You don't have specific backup requirements for your app
64+
65+
**Example configuration:**
66+
```json
67+
{
68+
"expo": {
69+
"plugins": [
70+
[
71+
"react-native-appsflyer",
72+
{
73+
"preferAppsFlyerBackupRules": true
74+
}
75+
]
76+
]
77+
}
78+
}
79+
```
4780

4881
### Handling dataExtractionRules Conflict
4982

@@ -123,3 +156,11 @@ Setting `"shouldUsePurchaseConnector": true` will:
123156

124157
* **iOS** – add the `PurchaseConnector` CocoaPod automatically
125158
* **Android** – add `appsflyer.enable_purchase_connector=true` to `gradle.properties`
159+
160+
### Plugin Options Summary
161+
162+
| Option | Type | Default | Description |
163+
|-------|------|---------|-------------|
164+
| `shouldUseStrictMode` | boolean | `false` | Enable strict mode for kids apps |
165+
| `shouldUsePurchaseConnector` | boolean | `false` | Enable Purchase Connector support |
166+
| `preferAppsFlyerBackupRules` | boolean | `false` | Remove app's backup rules to use AppsFlyer SDK's built-in rules (Android only) |

expo/withAppsFlyerAndroid.js

Lines changed: 62 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,53 +13,87 @@ function addPurchaseConnectorFlag(config) {
1313
});
1414
}
1515

16-
// withCustomAndroidManifest.js
17-
function withCustomAndroidManifest(config) {
18-
return withAndroidManifest(config, async (config) => {
16+
// Opt-in backup rules handling
17+
function withCustomAndroidManifest(config, { preferAppsFlyerBackupRules = false } = {}) {
18+
return withAndroidManifest(config, async (cfg) => {
1919
console.log('[AppsFlyerPlugin] Starting Android manifest modifications...');
20-
21-
const androidManifest = config.modResults;
20+
21+
const androidManifest = cfg.modResults;
2222
const manifest = androidManifest.manifest;
23-
23+
24+
if (!manifest || !manifest.$ || !manifest.application || !manifest.application[0]) {
25+
console.warn('[AppsFlyerPlugin] Unexpected manifest structure; skipping modifications');
26+
return cfg;
27+
}
28+
2429
// Ensure xmlns:tools is present in the <manifest> tag
2530
if (!manifest.$['xmlns:tools']) {
2631
manifest.$['xmlns:tools'] = 'http://schemas.android.com/tools';
2732
console.log('[AppsFlyerPlugin] Added xmlns:tools namespace');
2833
}
2934

3035
const application = manifest.application[0];
36+
const appAttrs = application.$ || {};
37+
38+
const hasDataExtractionRules = appAttrs['android:dataExtractionRules'] !== undefined;
39+
const hasFullBackupContent = appAttrs['android:fullBackupContent'] !== undefined;
40+
41+
if (!preferAppsFlyerBackupRules) {
42+
// Default: do not touch backup attributes at all
43+
if (hasDataExtractionRules || hasFullBackupContent) {
44+
console.log(
45+
'[AppsFlyerPlugin] App defines backup attributes; leaving them untouched (preferAppsFlyerBackupRules=false)'
46+
);
47+
} else {
48+
console.log(
49+
'[AppsFlyerPlugin] App does not define backup attributes; no changes required (preferAppsFlyerBackupRules=false)'
50+
);
51+
}
52+
console.log('[AppsFlyerPlugin] Android manifest modifications completed');
53+
return cfg;
54+
}
3155

32-
// Add tools:replace attribute for dataExtractionRules and fullBackupContent
33-
// This allows AppsFlyer SDK's built-in backup rules to take precedence over
34-
// conflicting rules in the app's manifest (see: https://dev.appsflyer.com/hc/docs/install-android-sdk#backup-rules)
35-
const existingReplace = application['$']['tools:replace'];
36-
if (existingReplace) {
37-
// Add to existing tools:replace if not already present
38-
const replaceAttrs = existingReplace.split(',').map(s => s.trim());
39-
if (!replaceAttrs.includes('android:dataExtractionRules')) {
40-
replaceAttrs.push('android:dataExtractionRules');
56+
// preferAppsFlyerBackupRules === true
57+
if (hasDataExtractionRules || hasFullBackupContent) {
58+
// Remove conflicting attributes from app's manifest
59+
// This allows AppsFlyer SDK's built-in backup rules to be used instead.
60+
if (hasDataExtractionRules) {
61+
delete appAttrs['android:dataExtractionRules'];
62+
console.log(
63+
'[AppsFlyerPlugin] Removed android:dataExtractionRules to use AppsFlyer SDK rules (preferAppsFlyerBackupRules=true)'
64+
);
4165
}
42-
if (!replaceAttrs.includes('android:fullBackupContent')) {
43-
replaceAttrs.push('android:fullBackupContent');
66+
67+
if (hasFullBackupContent) {
68+
delete appAttrs['android:fullBackupContent'];
69+
console.log(
70+
'[AppsFlyerPlugin] Removed android:fullBackupContent to use AppsFlyer SDK rules (preferAppsFlyerBackupRules=true)'
71+
);
4472
}
45-
application['$']['tools:replace'] = replaceAttrs.join(', ');
4673
} else {
47-
application['$']['tools:replace'] = 'android:dataExtractionRules, android:fullBackupContent';
74+
console.log(
75+
'[AppsFlyerPlugin] App does not define backup attributes; no conflict with AppsFlyer SDK (preferAppsFlyerBackupRules=true)'
76+
);
4877
}
4978

5079
console.log('[AppsFlyerPlugin] Android manifest modifications completed');
51-
52-
return config;
80+
return cfg;
5381
});
5482
}
5583

56-
module.exports = function withAppsFlyerAndroid(config, { shouldUsePurchaseConnector = false } = {}) {
84+
// Main plugin export
85+
module.exports = function withAppsFlyerAndroid(
86+
config,
87+
{
88+
shouldUsePurchaseConnector = false,
89+
preferAppsFlyerBackupRules = false,
90+
} = {}
91+
) {
5792
if (shouldUsePurchaseConnector) {
5893
config = addPurchaseConnectorFlag(config);
59-
}
60-
// Apply Android manifest modifications to resolve backup rules conflicts with AppsFlyer SDK
61-
// This ensures AppsFlyer SDK's built-in backup rules take precedence (see issue #631)
62-
config = withCustomAndroidManifest(config);
63-
94+
}
95+
96+
config = withCustomAndroidManifest(config, { preferAppsFlyerBackupRules });
97+
6498
return config;
65-
};
99+
};

0 commit comments

Comments
 (0)