sp-react-native-in-app-updates

repository·master·Indexed 20 days ago

https://github.com/sudoplz/sp-react-native-in-app-updates

A React Native native module for iOS and Android that checks app stores for new versions and prompts users to update. It provides embedded in-app updates on Android via Play-Core and utilizes the iTunes Search API on iOS. The library supports flexible and immediate update types on Android, regional filtering via ISO 3166-1 country codes on iOS, and integration with Expo via development builds.

Tokens
5.8K
Snippets
21
Records
23
Agent score
68%

What's inside sp-react-native-in-app-updates

  1. Install sp-react-native-in-app-updates

    master

    Install the package using npm to add it to your React Native project:

    $ npm install sp-react-native-in-app-updates --save

    Important: This project uses react-native-device-info internally. You must install react-native-device-info in your project to ensure correct functionality.

  2. Publish the Android library as a Maven dependency

    master

    If you are a maintainer or need to publish the Android portion of this library as a Maven dependency before publishing a new version to npm, follow these steps:

    1. Ensure the Android SDK and NDK are installed on your system.
    2. Create or update a local.properties file in the android directory to point to your SDK and NDK locations.
    3. Delete the existing maven folder to ensure a clean build.
    4. Execute the Gradle task to install archives.
    5. Verify that the newly generated files appear in the maven folder with the expected version number.
    # Example local.properties configuration
    ndk.dir=/Users/{username}/Library/Android/sdk/ndk-bundle
    sdk.dir=/Users/{username}/Library/Android/sdk
    # Run the installation command
    ./gradlew installArchives
  3. Use sp-react-native-in-app-updates with Expo

    master

    This library contains native code and does not work in Expo Go. It requires a development build, production/EAS build, or expo prebuild workflow.

    Because the library depends on react-native-device-info, you must provide a mock/alias for it in Expo to avoid errors.

    1. Create a react-native-device-info.js file in your project root (requires expo-constants).
    2. If targeting iOS, ensure a bundleIdentifier is set in your Expo config.
    3. Configure a Babel alias in babel.config.js to point react-native-device-info to your new file.
    // 1. Create react-native-device-info.js in root
    import Constants from "expo-constants"
    
    export const getBundleId = () => {
        return Constants.expoConfig?.ios?.bundleIdentifier ?? '';
    }
    export const getVersion = () => {
        return Constants.expoConfig?.version
    }
    export default {
        getBundleId,
        getVersion,
    };
    
    // 2. Update babel.config.js
    plugins: [
      [
        'module-resolver',
        {
          root: ['.'],
          alias: {
            'react-native-device-info': './react-native-device-info.js'
          }
        }
      ],
      ...
    ]
  4. Configure Expo for iOS App Store deep links

    master

    If you are using Expo, add the LSApplicationQueriesSchemes configuration to your app.json or app.config.json to enable App Store deep linking. After updating the config, rebuild your native files using npx expo prebuild --clean && eas build -p ios.

    "ios": {
          "infoPlist": {
            "LSApplicationQueriesSchemes": ["itms-apps"]
          }
        },
  5. Analyze the NeedsUpdateResponse structure

    master

    The NeedsUpdateResponse is the unified return type from an update check. It is a discriminated union of IosNeedsUpdateResponse and AndroidNeedsUpdateResponse.

    Both types share a base structure:

    • shouldUpdate: Boolean indicating if an update is required.
    • storeVersion: The version found in the store.
    • reason: A string explaining why the update is being suggested.

    Android specific data is found in the other field as AndroidInAppUpdateExtras:

    • updateAvailability: AndroidAvailabilityStatus.
    • versionCode: The numeric version code.
    • isFlexibleUpdateAllowed: Boolean.
    • isImmediateUpdateAllowed: Boolean.
    • updatePriority: A numeric priority.

    iOS specific data is found in the other field as IosPerformCheckResponse (which extends IosITunesResponse), containing metadata from the iTunes Search API like releaseNotes, trackViewUrl, and description.

  6. Debug and troubleshoot in-app updates

    master

    In-app updates are difficult to test. Follow these guidelines for successful debugging:

    Debugging Tips

    • Use a REAL device.
    • Enable debug logs: Pass true to the SpInAppUpdates constructor.
    • Avoid Debug builds: In-app updates do not work with debug builds. You must use a release build signed with the same key used for the Play Store.

    Android Testing Workflow

    1. Enable internal app sharing on your device.
    2. Create a release APK/AAB with a lower version (e.g., 100).
    3. Create a release APK/AAB with a higher version (e.g., 101).
    4. Upload both to internal app sharing.
    5. Install version 100 on your device.
    6. Open the internal app sharing link for version 101. Ensure the button says UPDATE (not Install).
    7. Open your app (version 100) and verify the update popup appears.

    Common Troubleshooting

    • Android Version: Requires Android 5.0 (API level 21) or higher.
    • Account Eligibility: The Google Play account must have downloaded the app at least once.
    • Version Logic: Google Play only updates to a higher version code. Ensure your test version is lower than the update version.
    • Play Store Cache: If the update isn't appearing, try closing the Play Store app and checking the 'My Apps & Games' tab manually. If it doesn't show there, it won't show via code.
  7. Usage with app updates for specific country (iOS only)

    master

    On iOS, you can filter update checks by a specific country using the country option (an ISO 3166-1 country code). This can be applied both during the checkNeedsUpdate phase and within the startUpdate options to ensure the user is directed to the correct regional App Store version.

    //                              👇🏻 (optional)
    inAppUpdates.checkNeedsUpdate({ country: 'it' }).then(result => {
      if (result.shouldUpdate) {
        const updateOptions: StartUpdateOptions = Platform.select({
          ios: {
            title: 'Update available',
            message: "There is a new version of the app available on the App Store, do you want to update it?”,
            buttonUpgradeText: 'Update',
            buttonCancelText: 'Cancel',
            country: 'it', // 👈🏻 the country code for the specific version to lookup for (optional),
          },
          android: {
            updateType: IAUUpdateKind.IMMEDIATE,
          },
        });
        inAppUpdates.startUpdate(updateOptions);
      }
    });
  8. Basic usage of sp-react-native-in-app-updates

    master

    To check for and initiate an update, instantiate SpInAppUpdates and call checkNeedsUpdate. If shouldUpdate is true, call startUpdate with the appropriate platform options. On Android, you must specify an updateType (either FLEXIBLE or IMMEDIATE). On iOS, the library will prompt the user to visit the App Store.

    Note: If curVersion is not provided to checkNeedsUpdate, the library attempts to retrieve it automatically using react-native-device-info.

    import SpInAppUpdates, {
      NeedsUpdateResponse,
      IAUUpdateKind,
      StartUpdateOptions,
    } from 'sp-react-native-in-app-updates';
    
    const inAppUpdates = new SpInAppUpdates(
      false // isDebug
    );
    
    // curVersion is optional if you don't provide it will automatically take from the app using react-native-device-info
    inAppUpdates.checkNeedsUpdate({ curVersion: '0.0.8' }).then((result) => {
      if (result.shouldUpdate) {
        let updateOptions: StartUpdateOptions = {};
        if (Platform.OS === 'android') {
          // android only, on iOS the user will be promped to go to your app store page
          updateOptions = {
            updateType: IAUUpdateKind.FLEXIBLE,
          };
        }
        inAppUpdates.startUpdate(updateOptions); // https://github.com/SudoPlz/sp-react-native-in-app-updates/blob/master/src/types.ts#L78
      }
    });
  9. Initiate an update with startUpdate()

    master

    The startUpdate method shows a prompt asking the user if they want to download the update. It returns a Promise.

    StartUpdateOptions parameters:

    Android Only:

    • updateType (required, IAUUpdateKind): Either IAUUpdateKind.FLEXIBLE or IAUUpdateKind.IMMEDIATE.

    iOS Only:

    • title (optional, String): Title of the alert (default: Update Available).
    • message (optional, String): Content of the alert (default: There is an updated version available on the App Store. Would you like to upgrade?).
    • buttonUpgradeText (optional, String): Text for the confirmation button (default: Upgrade ).
    • buttonCancelText (optional, String): Text for the cancel button (default: Cancel).
    • forceUpgrade (optional, Boolean): If true, the user cannot cancel the upgrade (default: false).
    • bundleId (optional, String): The app's identifier (e.g., com.apple.mobilesafari). If undefined, it uses react-native-device-info.
    • country (optional, String): ISO 3166-1 country code to filter the update.
    • versionSpecificOptions (optional, Array): Rules that apply dynamically based on the current device version.
    • iosStrategy (optional, String): 'itunes' (default) or 'siren' (requires react-native-siren).
    // Signature
    startUpdate(updateOptions: StartUpdateOptions) : Promise
  10. Track Android update status with addStatusUpdateListener()

    master

    On Android, you can track the progress of an update download using addStatusUpdateListener.

    StatusUpdateEvent object:

    • status (AndroidInstallStatus): The current installation status.
    • bytesDownloaded (int): Number of bytes already downloaded.
    • totalBytesToDownload (int): Total size of the update in bytes.

    Use removeStatusUpdateListener(callback) to stop listening.

    // Signatures
    addStatusUpdateListener(callback: (status: StatusUpdateEvent) : void) : void
    removeStatusUpdateListener(callback: (status: StatusUpdateEvent) : void): void