Maneesh MK
Maneesh MK

Reputation: 395

How to implement Android playstore in-app update with react-native

immediate flow of play store in-app updates

How can I implement play store in-app update with my react-native app, which is already live on play store?

I have already implemented code push and also internal version check via API calls to force the user to update the app.

With Codepush: code push only works for javascript changes. However, there are many scenarios where we will need the whole app to be updated when there is a native code change.

With API check: We need to watch when the update is getting live, and update the version number kept in the backend to force the user to update the app.

Though both these solutions are kind of patchy, I would like to implement the elegant approach of the play-store in-app update, but couldn't find a clue on how to get it done.

Upvotes: 18

Views: 28465

Answers (5)

Sohel Islam Imran
Sohel Islam Imran

Reputation: 1707

expo-in-app-updates

A lightweight and easy-to-use module for implementing native in-app updates for Android and iOS.

This module uses the Android native in-app updates library on Android and iTunes Search API on iOS.

On Android, it will show a native overlay like the screenshots below but on iOS it opens the app in the App Store on a modal to update the app, since iOS does not have any in-app update solution. You may want to show an alert or custom UI on iOS. See the example at the bottom.

Installation

npm install expo-in-app-updates

For iOS, add your AppStoreID (the id in your app store link, e.g https://apps.apple.com/pl/app/example/id1234567890) in infoPlist in your app.json / app.config.js.

If your app is not available in the US, the default lookup might not find it. To fix this, you can set the AppStoreCountry to a country code where your app is available (e.g pl for Poland) in infoPlist in your app.json / app.config.js.

{
  "expo": {
    "ios": {
      "infoPlist": {
        "AppStoreID": "1234567890",
        "AppStoreCountry": "pl" // Optional, only if the iTunes lookup used by this library doesn't find your app
      }
    }
  }
}

For bare React Native projects, you must ensure that you have installed and configured the expo package before continuing. Run npx pod-install after installing the npm package for iOS.

npx expo run:android | run:ios

Usages

Check for updates

const {
  updateAvailable,
  flexibleAllowed,
  immediateAllowed,
  storeVersion,
  releaseDate,
  daysSinceRelease
} = await ExpoInAppUpdates.checkForUpdate();

Checks if an app update is available. Return a promise that resolves updateAvailable and storeVersion for Android and iOS, flexibleAllowed and immediateAllowed for Android.

  • updateAvailable: If an update is available.
  • flexibleAllowed: If able to start a Flexible Update (Android)
  • immediateAllowed: If able to start an Immediate Update (Android)
  • storeVersion: The latest app version published in the App Store / Play Store. On Android, this is the versionCode that you defined in app.json.
  • releaseDate: The release date of the current version of the app (iOS)
  • daysSinceRelease: The value of the clientVersionStalenessDays. If an update is available or in progress, this will be the number of days since the Google Play Store app on the user's device has learnt about an available update. If update is not available, or if staleness information is unavailable, this will be null. (Android)

Start an in-app update

const isUpdateStarted = await ExpoInAppUpdates.startUpdate();

Starts an in-app update. Return a boolean whether the update was started successfully.

[!NOTE] If you want an Immediate Update that will cover the app with the update overlay, pass true to this function. By default, it will start a Flexible Update. More details : https://developer.android.com/guide/playcore/in-app-updates#update-flows

const isUpdateStarted = await ExpoInAppUpdates.startUpdate(true);

Check and start an in-app update

const isUpdateStarted = await ExpoInAppUpdates.checkAndStartUpdate();

Checks if an app update is available and starts the update process if necessary. Return a boolean whether the update was started successfully.

[!NOTE] If you want an Immediate Update that will cover the app with the update overlay, pass true to this function. By default, it will start a Flexible Update. More details : https://developer.android.com/guide/playcore/in-app-updates#update-flows

const isUpdateStarted = await ExpoInAppUpdates.checkAndStartUpdate(true);

[!TIP] You may want to check for updates and show an alert or custom UI on iOS. Since iOS does not have any in-app update solution, it just opens the app in the App Store on a modal to update the app. See the example below.

Examples

This example will ask the user for update the app if update available on every app startup until the user update the app.

import { useEffect } from "react";
import { Alert, Platform, Text, View } from "react-native";

import * as ExpoInAppUpdates from "expo-in-app-updates";

const useInAppUpdates = () => {
  useEffect(() => {
    if (__DEV__ || Platform.OS === "web") return;

    if (Platform.OS === "android") {
      ExpoInAppUpdates.checkAndStartUpdate(
        // If you want an immediate update that will cover the app with the update overlay, set it to true.
        // More details : https://developer.android.com/guide/playcore/in-app-updates#update-flows
        false
      );
    } else {
      ExpoInAppUpdates.checkForUpdate().then(({ updateAvailable }) => {
        if (!updateAvailable) return;

        Alert.alert(
          "Update available",
          "A new version of the app is available with many improvements and bug fixes. Would you like to update now?",
          [
            {
              text: "Update",
              isPreferred: true,
              async onPress() {
                await ExpoInAppUpdates.startUpdate();
              },
            },
            { text: "Cancel" },
          ]
        );
      });
    }
  }, []);
};

export default function App() {
  // Use this hook in your root app or root layout component
  useInAppUpdates();

  return (
    <View>
      <Text>Native in-app updates for Android and iOS</Text>
    </View>
  );
}

This example will ask the user for update the app if update available and if user don't update or cancel the update, then the user will not be asked again for the update until a new version published again.

import { useEffect } from "react";
import { Alert, Platform } from "react-native";
import AsyncStorage from "expo-sqlite/async-storage";

import * as ExpoInAppUpdates from "expo-in-app-updates";

const useInAppUpdates = () => {
  useEffect(() => {
    if (__DEV__ || Platform.OS === "web") return;

    ExpoInAppUpdates.checkForUpdate().then(
      async ({ updateAvailable, storeVersion }) => {
        if (!updateAvailable) return;

        // Get the last saved storeVersion from your local-storage (AsyncStorage/MMKV)
        const savedStoreVersion = await AsyncStorage.getItem("savedStoreVersion");
        // Check and return from here to prevent asking for updates again for the same storeVersion.
        if (savedStoreVersion === storeVersion) return;

        if (Platform.OS === "android") {
          await ExpoInAppUpdates.startUpdate();
          // Saving the storeVersion after checked for updates, so we can check and ignore asking for updates again for the same storeVersion
          await AsyncStorage.setItem("savedStoreVersion", storeVersion);
          return;
        }

        Alert.alert(
          "Update available",
          "A new version of the app is available with many improvements and bug fixes. Would you like to update now?",
          [
            {
              text: "Update",
              isPreferred: true,
              async onPress() {
                await ExpoInAppUpdates.startUpdate();
                await AsyncStorage.setItem("savedStoreVersion", storeVersion);
              },
            },
            {
              text: "Cancel",
              async onPress() {
                // Saving the storeVersion after checked for updates, so we can check and ignore asking for updates again for the same storeVersion
                await AsyncStorage.setItem("savedStoreVersion", storeVersion);
              },
            },
          ]
        );
      }
    );
  }, []);
};

This example checks and prevents asking for updates for 2 days after release of the app.

import { useEffect } from "react";
import { Alert, Platform } from "react-native";

import * as ExpoInAppUpdates from "expo-in-app-updates";

function getDiffInDays(date) {
  const diffInMs = Math.abs(new Date() - new Date(date)); // Calculate difference in ms
  return diffInMs / (1000 * 60 * 60 * 24); // Convert ms to days
}

const useInAppUpdates = () => {
  useEffect(() => {
    if (__DEV__ || Platform.OS === "web") return;

    ExpoInAppUpdates.checkForUpdate().then(
      async ({ updateAvailable, daysSinceRelease, releaseDate }) => {
        if (!updateAvailable) return;

        // Check and prevent asking for updates for 2 days after release
        if (Platform.OS === "android" && ((daysSinceRelease??0) >= 2)) {
          return await ExpoInAppUpdates.startUpdate();
        }

        // Check and prevent asking for updates for 2 days after release
        if (getDiffInDays(releaseDate) >= 2) {
          Alert.alert(
            "Update available",
            "A new version of the app is available with many improvements and bug fixes. Would you like to update now?",
            [
              {
                text: "Update",
                isPreferred: true,
                async onPress() {
                  await ExpoInAppUpdates.startUpdate();
                },
              },
              { text: "Cancel" },
            ]
          );
        }
      }
    );
  }, []);
};

Testing In-App Updates

Android

Use internal app sharing of Play Console to Test in-app updates

iOS

To test in-app updates on iOS:

  1. First publish your app to App Store at least once. Then make sure you have set the correct AppStoreID in your app.json / app.config.js.
  2. Create a Development/Production/TestFlight build with a lower version number than your App Store version
  3. Install the build on your device
  4. Run the app and the update check should detect the newer App Store version

[!NOTE] The iTunes Search API used for version checking may have some delay in reflecting the latest App Store version. It's recommended to wait a few minutes after publishing a new version before testing.


Upvotes: 0

Avinash
Avinash

Reputation: 1118

You can use sp-react-native-in-app-updates library for inapp update.

npm i sp-react-native-in-app-updates

This is a react-native native module that works on both iOS and Android, and checks the stores (play/app) for a new version of your app and can prompt your user for an update.

It uses embedded in-app-updates via Play-Core on Android (to check & download google play patches natively from within the app), and react-native-siren on iOS (to check & navigate the user to the AppStore).

Upvotes: 1

Edmundo Santos
Edmundo Santos

Reputation: 8287

There's a fairly new package that does that: sp-react-native-in-app-updates.

It also handles the iOS case by prompting the user with a native alert that redirects to the App Store.

Upvotes: 4

Naroju
Naroju

Reputation: 2667

I did not find any react-native package for this, so I had to implement it for my self.

You can check the complete tutorial here and below downloadable files here.

But in short, here is what you need to do.

  1. Open android folder in your react-native project with Android Studio and add implementation 'com.google.android.play:core:1.7.3' at the end of dependencies section of the build.gradle(app) file. Like below,
dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    //noinspection GradleDynamicVersion
    implementation "com.facebook.react:react-native:+"  // From node_modules


    .......
    implementation 'com.google.android.play:core:1.7.3' // add it at the end
}

Cick sync after adding the dependency.

  1. Download InAppUpdateModule.java and InAppUpdatePackage.java files and place in them in the same directory of MainActivity.java(android/app/src/main/java/<package>/)
  2. Change the package names in both InAppUpdateModule.java and InAppUpdatePackage.java to your project package name.
  3. Now Open MainApplication.java and add our InAppUpdatePackage into getPackages method like below,
        @Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();
          // Packages that cannot be autolinked yet can be added manually here, for example:
           //  packages.add(new MyReactNativePackage());
            packages.add(new InAppUpdatePackage());
          return packages;
        }
  1. Download InAppUpdate.js and place it into your react-native project.
  2. Import the InAppUpdate.js in any js file, wherever you want to use. And use it like below.
  componentDidMount () {
    InAppUpdate.checkUpdate() // this is how you check for update 
  }
  1. That's it.

You can download all the files from here

Upvotes: 16

Maneesh MK
Maneesh MK

Reputation: 395

One more option (Better than the APi check): Check the latest version from play store and act accordingly, with https://github.com/kimxogus/react-native-version-check

VersionCheck.needUpdate()
  .then(async res => {
    console.log(res.isNeeded);    // true
    if (res.isNeeded) {
      Linking.openURL(await VersionCheck.getStoreUrl());  // open store if update is needed.
    }
  });

Upvotes: 0

Related Questions