Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
## 1.2.0

- Added funtion to get update details
- Added function to get download progress with flexible update

## 1.1.0

**Breaking Changes:**

- Added support for new architecture
- Removed steps for native setup
- Added method for Javascript/Typescript with update type (immediate, flexible)
Expand Down
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ yarn add react-native-rn-in-app-update

## Usage

### Function 1: `showUpdatePopup`

Import and use the `showUpdatePopup` function. it supports 2 update type **immediate** and **flexible**

```tsx md title="App.tsx"
Expand All @@ -26,6 +28,52 @@ import { showUpdatePopup } from 'react-native-rn-in-app-update';
<Button title="Get Update" onPress={() => showUpdatePopup('immediate')} />;
```

### Function 2: `getUpdateInfo`

`getUpdateInfo` is used to get information about the available update.

```tsx md title="App.tsx"
const info = await getUpdateInfo();
```

### Function 3: `startFlexibleUpdateWithProgress`

`startFlexibleUpdateWithProgress` is used to start flexible update while also getting the download percentage.

```tsx md title="App.tsx"
import { startFlexibleUpdateWithProgress } from 'react-native-rn-in-app-update';

<Button
title="Start Flexible Update"
onPress={() => startFlexibleUpdateWithProgress()}
/>;
```

### Function 4: `subscribeToUpdateProgress`

`subscribeToUpdateProgress` is used to get the download percentage when updating app with `startFlexibleUpdateWithProgress`.

```tsx md title="App.tsx"
import { subscribeToUpdateProgress } from 'react-native-rn-in-app-update';

useEffect(() => {
if (Platform.OS !== 'android') return;

const unsubscribe = subscribeToUpdateProgress(
({ bytesDownloaded, totalBytesToDownload }) => {
if (totalBytesToDownload > 0) {
const percent = (bytesDownloaded / totalBytesToDownload) * 100;
console.log({ percent });
}
}
);

return () => {
unsubscribe();
};
}, []);
```

## How to Test In-App Updates on Android

To test this package correctly, you must publish your app to the Play Store (even if only in **Internal Testing**) — the in-app update API only works when your app is installed via **Google Play**.
Expand Down
76 changes: 76 additions & 0 deletions android/src/main/java/com/rninappupdate/RnInAppUpdateModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import android.app.Activity
import android.content.Intent
import android.content.IntentSender
import com.facebook.react.bridge.*
import com.facebook.react.modules.core.DeviceEventManagerModule
import com.google.android.gms.tasks.Task
import com.google.android.play.core.appupdate.AppUpdateInfo
import com.google.android.play.core.appupdate.AppUpdateManager
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
import com.google.android.play.core.install.InstallState
import com.google.android.play.core.install.model.AppUpdateType
import com.google.android.play.core.install.model.InstallStatus
import com.google.android.play.core.install.model.UpdateAvailability

class RnInAppUpdateModule(reactContext: ReactApplicationContext) :
Expand Down Expand Up @@ -58,6 +61,79 @@ class RnInAppUpdateModule(reactContext: ReactApplicationContext) :
}
}

@ReactMethod
fun getUpdateInfo(promise: Promise) {
appUpdateManager.appUpdateInfo
.addOnSuccessListener { info ->
val map = Arguments.createMap()
map.putInt("updateAvailability", info.updateAvailability())
map.putBoolean("immediateAllowed", info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE))
map.putBoolean("flexibleAllowed", info.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE))
map.putInt("versionCode", info.availableVersionCode())
info.clientVersionStalenessDays()?.let {
map.putInt("clientVersionStalenessDays", it)
}
map.putDouble("totalBytesToDownload", info.totalBytesToDownload().toDouble())
map.putString("packageName", info.packageName())
promise.resolve(map)
}
.addOnFailureListener { e ->
promise.reject("UPDATE_INFO_FAILED", "Failed to retrieve update info", e)
}
}

@ReactMethod
fun startFlexibleUpdateWithProgress(promise: Promise) {
val activity = currentActivity
if (activity == null) {
promise.reject("NO_ACTIVITY", "Current activity is null")
return
}

val appUpdateInfoTask = appUpdateManager.appUpdateInfo

appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE &&
appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {

// Register progress listener
appUpdateManager.registerListener { state: InstallState ->
val map = Arguments.createMap()
map.putInt("status", state.installStatus())
map.putDouble("bytesDownloaded", state.bytesDownloaded().toDouble())
map.putDouble("totalBytesToDownload", state.totalBytesToDownload().toDouble())

reactApplicationContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit("in_app_update_progress", map)

// Automatically call completeUpdate when downloaded
if (state.installStatus() == InstallStatus.DOWNLOADED) {
appUpdateManager.completeUpdate()
}
}

try {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.FLEXIBLE,
activity,
REQUEST_CODE
)
promise.resolve("STARTED")
} catch (e: IntentSender.SendIntentException) {
promise.reject("INTENT_ERROR", "Error starting update flow", e)
}
} else {
promise.reject("UPDATE_NOT_AVAILABLE", "No flexible update available")
}
}

appUpdateInfoTask.addOnFailureListener { e ->
promise.reject("UPDATE_ERROR", "Failed to get update info", e)
}
}

override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
// No-op
}
Expand Down
170 changes: 164 additions & 6 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,143 @@
import { useEffect } from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { showUpdatePopup } from 'react-native-rn-in-app-update';
import { useEffect, useState } from 'react';
import { Alert, Button, Platform, StyleSheet, Text, View } from 'react-native';
import {
getUpdateInfo,
showUpdatePopup,
startFlexibleUpdateWithProgress,
subscribeToUpdateProgress,
type UpdateInfo,
} from 'react-native-rn-in-app-update';

const App = () => {
const [updateInfo, setUpdateInfo] = useState<null | UpdateInfo>(null);
const [progress, setProgress] = useState<null | { percent: number }>({
percent: 0,
});

useEffect(() => {
showUpdatePopup();
const checkUpdate = async () => {
if (Platform.OS !== 'android') return;

try {
const info = await getUpdateInfo();
setUpdateInfo(info);
} catch (err: any) {
Alert.alert(
'Update Error',
err?.message || 'Could not check or start update'
);
}
};

checkUpdate();
}, []);

useEffect(() => {
if (Platform.OS !== 'android') return;

const unsubscribe = subscribeToUpdateProgress(
({ bytesDownloaded, totalBytesToDownload }) => {
if (totalBytesToDownload > 0) {
const percent = (bytesDownloaded / totalBytesToDownload) * 100;
setProgress({ percent });
}
}
);

return () => {
unsubscribe();
};
}, []);

const handleCheckFlexibleUpdate = async () => {
try {
await showUpdatePopup('flexible');
} catch (err: any) {
Alert.alert(
'Update Error',
err?.message || 'Could not start flexible update'
);
}
};

const handleCheckImmediateUpdate = async () => {
try {
await showUpdatePopup('immediate');
} catch (err: any) {
Alert.alert(
'Update Error',
err?.message || 'Could not start immediate update'
);
}
};

const handleFlexibleUpdate = async () => {
try {
await startFlexibleUpdateWithProgress();
} catch (err: any) {
Alert.alert(
'Update Error',
err?.message || 'Could not start flexible update'
);
}
};

return (
<View style={styles.container}>
<Text>App</Text>
<Text style={styles.title}>In-App Update Demo</Text>

{Platform.OS === 'android' ? (
<View style={styles.innerContainer}>
<Button
title="Check for Flexible Update"
onPress={handleCheckFlexibleUpdate}
/>
<Button
title="Check for Immediate Update"
onPress={handleCheckImmediateUpdate}
/>
<Button
title="Start Flexible Update"
onPress={handleFlexibleUpdate}
/>

{progress && progress.percent > 0 && progress.percent < 100 && (
<Text style={styles.progress}>
Downloading Update: {progress.percent.toFixed(1)}%
</Text>
)}

{updateInfo && (
<View style={styles.infoBox}>
<Text style={styles.infoTitle}>Update Info:</Text>
<Text>
Available: {updateInfo.updateAvailability === 2 ? 'Yes' : 'No'}
</Text>
<Text>
Immediate Allowed: {updateInfo.immediateAllowed ? 'Yes' : 'No'}
</Text>
<Text>
Flexible Allowed: {updateInfo.flexibleAllowed ? 'Yes' : 'No'}
</Text>
<Text>Version Code: {updateInfo.versionCode}</Text>
{updateInfo.clientVersionStalenessDays !== undefined && (
<Text>
Staleness (days): {updateInfo.clientVersionStalenessDays}
</Text>
)}
<Text>
Download Size:{' '}
{(updateInfo.totalBytesToDownload / (1024 * 1024)).toFixed(2)}{' '}
MB
</Text>
</View>
)}
</View>
) : (
<Text style={styles.note}>
In-app updates are only supported on Android.
</Text>
)}
</View>
);
};
Expand All @@ -19,7 +147,37 @@ export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
padding: 24,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
title: {
fontSize: 20,
marginBottom: 20,
fontWeight: '600',
},
innerContainer: {
rowGap: 20,
width: '100%',
},
progress: {
color: '#007bff',
fontWeight: '500',
},
infoBox: {
padding: 10,
borderRadius: 8,
backgroundColor: '#f3f3f3',
width: '100%',
},
infoTitle: {
fontWeight: 'bold',
marginBottom: 8,
},
note: {
marginTop: 20,
fontStyle: 'italic',
color: 'gray',
},
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-rn-in-app-update",
"version": "1.1.0",
"version": "1.2.0",
"description": "A minimal React Native module that displays the native Android in-app update popup using the Play Core library. Supports both immediate and flexible update types.",
"source": "./src/index.tsx",
"main": "./lib/commonjs/index.js",
Expand Down
Loading