Skip to content

Commit da93cc4

Browse files
Merge pull request #14 from logicwind/dev
Dev to Master
2 parents 7df0f9b + 5fbc073 commit da93cc4

File tree

7 files changed

+358
-14
lines changed

7 files changed

+358
-14
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
## 1.2.0
2+
3+
- Added funtion to get update details
4+
- Added function to get download progress with flexible update
5+
16
## 1.1.0
27

38
**Breaking Changes:**
9+
410
- Added support for new architecture
511
- Removed steps for native setup
612
- Added method for Javascript/Typescript with update type (immediate, flexible)

README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ yarn add react-native-rn-in-app-update
1818

1919
## Usage
2020

21+
### Function 1: `showUpdatePopup`
22+
2123
Import and use the `showUpdatePopup` function. it supports 2 update type **immediate** and **flexible**
2224

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

31+
### Function 2: `getUpdateInfo`
32+
33+
`getUpdateInfo` is used to get information about the available update.
34+
35+
```tsx md title="App.tsx"
36+
const info = await getUpdateInfo();
37+
```
38+
39+
### Function 3: `startFlexibleUpdateWithProgress`
40+
41+
`startFlexibleUpdateWithProgress` is used to start flexible update while also getting the download percentage.
42+
43+
```tsx md title="App.tsx"
44+
import { startFlexibleUpdateWithProgress } from 'react-native-rn-in-app-update';
45+
46+
<Button
47+
title="Start Flexible Update"
48+
onPress={() => startFlexibleUpdateWithProgress()}
49+
/>;
50+
```
51+
52+
### Function 4: `subscribeToUpdateProgress`
53+
54+
`subscribeToUpdateProgress` is used to get the download percentage when updating app with `startFlexibleUpdateWithProgress`.
55+
56+
```tsx md title="App.tsx"
57+
import { subscribeToUpdateProgress } from 'react-native-rn-in-app-update';
58+
59+
useEffect(() => {
60+
if (Platform.OS !== 'android') return;
61+
62+
const unsubscribe = subscribeToUpdateProgress(
63+
({ bytesDownloaded, totalBytesToDownload }) => {
64+
if (totalBytesToDownload > 0) {
65+
const percent = (bytesDownloaded / totalBytesToDownload) * 100;
66+
console.log({ percent });
67+
}
68+
}
69+
);
70+
71+
return () => {
72+
unsubscribe();
73+
};
74+
}, []);
75+
```
76+
2977
## How to Test In-App Updates on Android
3078

3179
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**.

android/src/main/java/com/rninappupdate/RnInAppUpdateModule.kt

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import android.app.Activity
44
import android.content.Intent
55
import android.content.IntentSender
66
import com.facebook.react.bridge.*
7+
import com.facebook.react.modules.core.DeviceEventManagerModule
78
import com.google.android.gms.tasks.Task
89
import com.google.android.play.core.appupdate.AppUpdateInfo
910
import com.google.android.play.core.appupdate.AppUpdateManager
1011
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
12+
import com.google.android.play.core.install.InstallState
1113
import com.google.android.play.core.install.model.AppUpdateType
14+
import com.google.android.play.core.install.model.InstallStatus
1215
import com.google.android.play.core.install.model.UpdateAvailability
1316

1417
class RnInAppUpdateModule(reactContext: ReactApplicationContext) :
@@ -26,7 +29,7 @@ class RnInAppUpdateModule(reactContext: ReactApplicationContext) :
2629

2730
@ReactMethod
2831
fun showUpdatePopup(updateType: Int, promise: Promise) {
29-
val activity: Activity? = currentActivity
32+
val activity: Activity? = getCurrentActivity()
3033
if (activity == null) {
3134
promise.reject("NO_ACTIVITY", "Current activity is null")
3235
return
@@ -58,6 +61,79 @@ class RnInAppUpdateModule(reactContext: ReactApplicationContext) :
5861
}
5962
}
6063

64+
@ReactMethod
65+
fun getUpdateInfo(promise: Promise) {
66+
appUpdateManager.appUpdateInfo
67+
.addOnSuccessListener { info ->
68+
val map = Arguments.createMap()
69+
map.putInt("updateAvailability", info.updateAvailability())
70+
map.putBoolean("immediateAllowed", info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE))
71+
map.putBoolean("flexibleAllowed", info.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE))
72+
map.putInt("versionCode", info.availableVersionCode())
73+
info.clientVersionStalenessDays()?.let {
74+
map.putInt("clientVersionStalenessDays", it)
75+
}
76+
map.putDouble("totalBytesToDownload", info.totalBytesToDownload().toDouble())
77+
map.putString("packageName", info.packageName())
78+
promise.resolve(map)
79+
}
80+
.addOnFailureListener { e ->
81+
promise.reject("UPDATE_INFO_FAILED", "Failed to retrieve update info", e)
82+
}
83+
}
84+
85+
@ReactMethod
86+
fun startFlexibleUpdateWithProgress(promise: Promise) {
87+
val activity = getCurrentActivity()
88+
if (activity == null) {
89+
promise.reject("NO_ACTIVITY", "Current activity is null")
90+
return
91+
}
92+
93+
val appUpdateInfoTask = appUpdateManager.appUpdateInfo
94+
95+
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
96+
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE &&
97+
appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
98+
99+
// Register progress listener
100+
appUpdateManager.registerListener { state: InstallState ->
101+
val map = Arguments.createMap()
102+
map.putInt("status", state.installStatus())
103+
map.putDouble("bytesDownloaded", state.bytesDownloaded().toDouble())
104+
map.putDouble("totalBytesToDownload", state.totalBytesToDownload().toDouble())
105+
106+
reactApplicationContext
107+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
108+
.emit("in_app_update_progress", map)
109+
110+
// Automatically call completeUpdate when downloaded
111+
if (state.installStatus() == InstallStatus.DOWNLOADED) {
112+
appUpdateManager.completeUpdate()
113+
}
114+
}
115+
116+
try {
117+
appUpdateManager.startUpdateFlowForResult(
118+
appUpdateInfo,
119+
AppUpdateType.FLEXIBLE,
120+
activity,
121+
REQUEST_CODE
122+
)
123+
promise.resolve("STARTED")
124+
} catch (e: IntentSender.SendIntentException) {
125+
promise.reject("INTENT_ERROR", "Error starting update flow", e)
126+
}
127+
} else {
128+
promise.reject("UPDATE_NOT_AVAILABLE", "No flexible update available")
129+
}
130+
}
131+
132+
appUpdateInfoTask.addOnFailureListener { e ->
133+
promise.reject("UPDATE_ERROR", "Failed to get update info", e)
134+
}
135+
}
136+
61137
override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
62138
// No-op
63139
}

example/src/App.tsx

Lines changed: 164 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,143 @@
1-
import { useEffect } from 'react';
2-
import { Text, View, StyleSheet } from 'react-native';
3-
import { showUpdatePopup } from 'react-native-rn-in-app-update';
1+
import { useEffect, useState } from 'react';
2+
import { Alert, Button, Platform, StyleSheet, Text, View } from 'react-native';
3+
import {
4+
getUpdateInfo,
5+
showUpdatePopup,
6+
startFlexibleUpdateWithProgress,
7+
subscribeToUpdateProgress,
8+
type UpdateInfo,
9+
} from 'react-native-rn-in-app-update';
410

511
const App = () => {
12+
const [updateInfo, setUpdateInfo] = useState<null | UpdateInfo>(null);
13+
const [progress, setProgress] = useState<null | { percent: number }>({
14+
percent: 0,
15+
});
16+
617
useEffect(() => {
7-
showUpdatePopup();
18+
const checkUpdate = async () => {
19+
if (Platform.OS !== 'android') return;
20+
21+
try {
22+
const info = await getUpdateInfo();
23+
setUpdateInfo(info);
24+
} catch (err: any) {
25+
Alert.alert(
26+
'Update Error',
27+
err?.message || 'Could not check or start update'
28+
);
29+
}
30+
};
31+
32+
checkUpdate();
833
}, []);
934

35+
useEffect(() => {
36+
if (Platform.OS !== 'android') return;
37+
38+
const unsubscribe = subscribeToUpdateProgress(
39+
({ bytesDownloaded, totalBytesToDownload }) => {
40+
if (totalBytesToDownload > 0) {
41+
const percent = (bytesDownloaded / totalBytesToDownload) * 100;
42+
setProgress({ percent });
43+
}
44+
}
45+
);
46+
47+
return () => {
48+
unsubscribe();
49+
};
50+
}, []);
51+
52+
const handleCheckFlexibleUpdate = async () => {
53+
try {
54+
await showUpdatePopup('flexible');
55+
} catch (err: any) {
56+
Alert.alert(
57+
'Update Error',
58+
err?.message || 'Could not start flexible update'
59+
);
60+
}
61+
};
62+
63+
const handleCheckImmediateUpdate = async () => {
64+
try {
65+
await showUpdatePopup('immediate');
66+
} catch (err: any) {
67+
Alert.alert(
68+
'Update Error',
69+
err?.message || 'Could not start immediate update'
70+
);
71+
}
72+
};
73+
74+
const handleFlexibleUpdate = async () => {
75+
try {
76+
await startFlexibleUpdateWithProgress();
77+
} catch (err: any) {
78+
Alert.alert(
79+
'Update Error',
80+
err?.message || 'Could not start flexible update'
81+
);
82+
}
83+
};
84+
1085
return (
1186
<View style={styles.container}>
12-
<Text>App</Text>
87+
<Text style={styles.title}>In-App Update Demo</Text>
88+
89+
{Platform.OS === 'android' ? (
90+
<View style={styles.innerContainer}>
91+
<Button
92+
title="Check for Flexible Update"
93+
onPress={handleCheckFlexibleUpdate}
94+
/>
95+
<Button
96+
title="Check for Immediate Update"
97+
onPress={handleCheckImmediateUpdate}
98+
/>
99+
<Button
100+
title="Start Flexible Update"
101+
onPress={handleFlexibleUpdate}
102+
/>
103+
104+
{progress && progress.percent > 0 && progress.percent < 100 && (
105+
<Text style={styles.progress}>
106+
Downloading Update: {progress.percent.toFixed(1)}%
107+
</Text>
108+
)}
109+
110+
{updateInfo && (
111+
<View style={styles.infoBox}>
112+
<Text style={styles.infoTitle}>Update Info:</Text>
113+
<Text>
114+
Available: {updateInfo.updateAvailability === 2 ? 'Yes' : 'No'}
115+
</Text>
116+
<Text>
117+
Immediate Allowed: {updateInfo.immediateAllowed ? 'Yes' : 'No'}
118+
</Text>
119+
<Text>
120+
Flexible Allowed: {updateInfo.flexibleAllowed ? 'Yes' : 'No'}
121+
</Text>
122+
<Text>Version Code: {updateInfo.versionCode}</Text>
123+
{updateInfo.clientVersionStalenessDays !== undefined && (
124+
<Text>
125+
Staleness (days): {updateInfo.clientVersionStalenessDays}
126+
</Text>
127+
)}
128+
<Text>
129+
Download Size:{' '}
130+
{(updateInfo.totalBytesToDownload / (1024 * 1024)).toFixed(2)}{' '}
131+
MB
132+
</Text>
133+
</View>
134+
)}
135+
</View>
136+
) : (
137+
<Text style={styles.note}>
138+
In-app updates are only supported on Android.
139+
</Text>
140+
)}
13141
</View>
14142
);
15143
};
@@ -19,7 +147,37 @@ export default App;
19147
const styles = StyleSheet.create({
20148
container: {
21149
flex: 1,
22-
alignItems: 'center',
150+
padding: 24,
23151
justifyContent: 'center',
152+
alignItems: 'center',
153+
backgroundColor: '#fff',
154+
},
155+
title: {
156+
fontSize: 20,
157+
marginBottom: 20,
158+
fontWeight: '600',
159+
},
160+
innerContainer: {
161+
rowGap: 20,
162+
width: '100%',
163+
},
164+
progress: {
165+
color: '#007bff',
166+
fontWeight: '500',
167+
},
168+
infoBox: {
169+
padding: 10,
170+
borderRadius: 8,
171+
backgroundColor: '#f3f3f3',
172+
width: '100%',
173+
},
174+
infoTitle: {
175+
fontWeight: 'bold',
176+
marginBottom: 8,
177+
},
178+
note: {
179+
marginTop: 20,
180+
fontStyle: 'italic',
181+
color: 'gray',
24182
},
25183
});

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-native-rn-in-app-update",
3-
"version": "1.1.0",
3+
"version": "1.2.0",
44
"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.",
55
"source": "./src/index.tsx",
66
"main": "./lib/commonjs/index.js",

0 commit comments

Comments
 (0)