Skip to content

Expo config plugin: programmatically merge backup-rules exclusions into host app manifest #46

@gmaclennan

Description

@gmaclennan

Context

@comapeo/core-react-native is published as an Expo module. The
library currently declares android:dataExtractionRules and
android:fullBackupContent on its <application> element to exclude
the rootkey-bearing SharedPreferences from cloud backup and
device-to-device transfer (see android/src/main/AndroidManifest.xml,
PR #36, comment thread on backup-rules merge conflict).

Host apps that already declare these attributes get a manifest-merger
conflict and must manually:

  1. Merge the library's exclusions into their own backup-rules XML.
  2. Add tools:replace="android:dataExtractionRules,android:fullBackupContent" to their <application> tag.

This is documented in README.md > Configure for Android > Backup-rules merge conflict but it's still a friction point at integration time
and easy to forget when iterating.

Proposal

An Expo config plugin that runs at expo prebuild time and:

  1. Adds the library's backup-rules XML files to the host app's
    res/xml/ directory
    (or makes them resolvable via the library
    AAR — they're already there, but the plugin can verify).
  2. Reads the host app's existing backup-rules XML if any.
    • Default Expo template apps don't set these → plugin sets the
      attributes on the host's <application> and copies our XML files
      in. No conflict because the host had no prior declaration.
    • Host already has rules → plugin parses the host's XML, merges our
      <exclude domain="sharedpref" path="comapeo-core.xml" /> entries
      into the right sections (<cloud-backup>, <device-transfer>,
      <full-backup-content>), writes the merged XML back, and ensures
      the <application> attributes still point at it.
  3. Removes the attributes from the library's own
    AndroidManifest.xml
    so there's never a merge conflict —
    responsibility shifts entirely to the host manifest, which the
    plugin owns at prebuild time.

For non-Expo (bare-RN-without-Expo) consumers, the README path stays
the canonical fix.

Why this is worth doing

  • Eliminates a build-time integration step for the largest class of
    consumers (managed-Expo apps).
  • Removes the manifest-merger conflict for any consumer that has its
    own backup rules — friction point disappears entirely.
  • Keeps secure-by-default: forgetting to register the plugin is the
    only way to lose the exclusion, and that's discoverable via a
    smoke test (the encrypted SharedPreferences ends up in backups).

Cost

Roughly 100–150 LOC of plugin code plus tests:

  • withDangerousMod for the XML file copy / merge step.
  • withAndroidManifest for the <application> attribute add /
    tools:replace handling.
  • An XML-merge helper that's safe against repeated invocations
    (expo prebuild runs are idempotent in the rest of the toolchain;
    this plugin should be too).
  • A test fixture covering: (a) host with no rules, (b) host with
    rules that already have our exclusions, (c) host with rules that
    conflict.

Acceptance

  • Plugin lives in plugins/ (or wherever published Expo plugins
    go in this repo) and is auto-registered when the package is
    installed.
  • expo prebuild --clean on a fresh Expo template + this module
    produces a host manifest with our exclusions, no conflicts.
  • expo prebuild --clean on an Expo template that ALREADY has
    backup rules merges our exclusions in non-destructively.
  • Re-running prebuild is idempotent — no duplicate <exclude>
    entries, no toggling tools:replace.
  • Library's own AndroidManifest.xml no longer declares the
    backup attributes once the plugin path is verified end-to-end.
  • README.md > Configure for Android updates to reflect that
    Expo consumers need do nothing; bare-RN consumers follow the
    manual path.

Open questions

  1. Should the plugin be opt-in (consumer adds it to their app.json
    plugins array) or auto-registered (via expo-module.config.json)?
    Auto is friendlier; opt-in is more explicit.
  2. What should the plugin do if it detects an existing
    tools:replace="android:dataExtractionRules" on the host? Mostly:
    nothing, because the host has explicitly opted out of merging.
    Log a warning suggesting they include our exclusions.
  3. How do bare-RN consumers (no Expo) interact with this? They
    continue to follow the README path; the plugin doesn't apply.

Metadata

Metadata

Assignees

No one assigned

    Labels

    low priorityLow priority issue, possible wontfix candidate

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions