Skip to content

Commit 8c30bbe

Browse files
committed
Implement async methods for version checking; add getStoreUrl, getLatestVersion, and needsUpdate to both Android and iOS native implementations. Update README and example usage in App.tsx to reflect new functionality.
1 parent 40c3433 commit 8c30bbe

21 files changed

Lines changed: 704 additions & 14 deletions

README.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,48 @@ bun add react-native-nitro-version-check react-native-nitro-modules
1111
## Usage
1212

1313
```tsx
14-
import VersionCheck from "react-native-nitro-version-check";
14+
import { VersionCheck } from "react-native-nitro-version-check";
1515

16+
// Static properties
1617
VersionCheck.version; // "1.0.0"
1718
VersionCheck.buildNumber; // "42"
1819
VersionCheck.packageName; // "com.example.app"
1920
VersionCheck.getCountry(); // "US"
21+
22+
// Async methods
23+
const storeUrl = await VersionCheck.getStoreUrl();
24+
const latest = await VersionCheck.getLatestVersion();
25+
26+
if (await VersionCheck.needsUpdate()) {
27+
Linking.openURL(await VersionCheck.getStoreUrl());
28+
}
2029
```
2130

2231
Or import individually:
2332

2433
```tsx
25-
import { version, buildNumber, packageName, getCountry } from "react-native-nitro-version-check";
34+
import { version, buildNumber, getCountry, needsUpdate } from "react-native-nitro-version-check";
2635
```
2736

2837
## API
2938

39+
### Properties
40+
3041
| API | Type | Description |
3142
|-----|------|-------------|
3243
| `version` | `string` | App version |
3344
| `buildNumber` | `string` | Build number |
3445
| `packageName` | `string` | Bundle ID / package name |
35-
| `getCountry()` | `string` | Current device country |
46+
47+
### Methods
48+
49+
| API | Returns | Description |
50+
|-----|---------|-------------|
51+
| `getCountry()` | `string` | Current device country code |
52+
| `getStoreUrl()` | `Promise<string>` | App Store / Play Store URL |
53+
| `getLatestVersion()` | `Promise<string>` | Latest version from the store |
54+
| `needsUpdate()` | `Promise<boolean>` | Whether an update is available |
3655

3756
## License
3857

39-
MIT
58+
MIT

example/App.tsx

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,61 @@
11
import { StatusBar } from "expo-status-bar";
2-
import { StyleSheet, Text, View } from "react-native";
3-
import VersionCheck from "react-native-nitro-version-check";
2+
import { useEffect, useState } from "react";
3+
import { ActivityIndicator, Linking, StyleSheet, Text, TouchableOpacity, View } from "react-native";
4+
import { getLatestVersion, getStoreUrl, needsUpdate, VersionCheck } from "react-native-nitro-version-check";
45

56
export default function App() {
7+
const [loading, setLoading] = useState(true);
8+
const [updateAvailable, setUpdateAvailable] = useState(false);
9+
const [storeUrl, setStoreUrl] = useState<string | null>(null);
10+
const [latestVersion, setLatestVersion] = useState<string | null>(null);
11+
const [error, setError] = useState<string | null>(null);
12+
13+
// Fetch all async data on mount
14+
useEffect(() => {
15+
const fetchAll = async () => {
16+
try {
17+
const [update, url, latest] = await Promise.all([needsUpdate(), getStoreUrl(), getLatestVersion()]);
18+
setUpdateAvailable(update);
19+
setStoreUrl(url);
20+
setLatestVersion(latest);
21+
} catch {
22+
setError("App not found in store");
23+
}
24+
setLoading(false);
25+
};
26+
fetchAll();
27+
}, []);
28+
629
return (
730
<View style={styles.container}>
8-
<Text>Version: {VersionCheck.version}</Text>
9-
<Text>Build: {VersionCheck.buildNumber}</Text>
10-
<Text>Package: {VersionCheck.packageName}</Text>
11-
<Text>Country: {VersionCheck.getCountry()}</Text>
31+
{/* Sync properties — available immediately */}
32+
<Text style={styles.label}>Version: {VersionCheck.version}</Text>
33+
<Text style={styles.label}>Build: {VersionCheck.buildNumber}</Text>
34+
<Text style={styles.label}>Package: {VersionCheck.packageName}</Text>
35+
<Text style={styles.label}>Country: {VersionCheck.getCountry()}</Text>
36+
37+
<View style={styles.divider} />
38+
39+
{/* Async data — shows loading spinner while fetching */}
40+
{loading ? (
41+
<ActivityIndicator style={styles.loader} />
42+
) : error ? (
43+
<Text style={styles.errorText}>{error}</Text>
44+
) : (
45+
<>
46+
<Text style={styles.label}>Latest: {latestVersion}</Text>
47+
<Text style={styles.label}>Store URL: {storeUrl}</Text>
48+
49+
{updateAvailable && storeUrl && (
50+
<TouchableOpacity onPress={() => Linking.openURL(storeUrl)}>
51+
<Text style={styles.updateText}>Update available — tap to open store</Text>
52+
</TouchableOpacity>
53+
)}
54+
55+
{!updateAvailable && <Text style={styles.upToDate}>App is up to date</Text>}
56+
</>
57+
)}
58+
1259
<StatusBar style="auto" />
1360
</View>
1461
);
@@ -20,5 +67,35 @@ const styles = StyleSheet.create({
2067
backgroundColor: "#fff",
2168
alignItems: "center",
2269
justifyContent: "center",
70+
padding: 24,
71+
},
72+
label: {
73+
fontSize: 15,
74+
marginVertical: 2,
75+
color: "#333",
76+
},
77+
divider: {
78+
height: 1,
79+
backgroundColor: "#eee",
80+
alignSelf: "stretch",
81+
marginVertical: 16,
82+
},
83+
loader: {
84+
marginTop: 8,
85+
},
86+
updateText: {
87+
color: "#007AFF",
88+
marginTop: 12,
89+
fontSize: 16,
90+
fontWeight: "600",
91+
},
92+
upToDate: {
93+
color: "#34C759",
94+
marginTop: 12,
95+
fontSize: 15,
96+
},
97+
errorText: {
98+
color: "#999",
99+
fontSize: 14,
23100
},
24-
});
101+
});

package/android/src/main/java/com/margelo/nitro/nitroversioncheck/HybridVersionCheck.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.margelo.nitro.nitroversioncheck
22

33
import com.margelo.nitro.NitroModules
4+
import com.margelo.nitro.core.Promise
5+
import java.net.URL
46

57

68
class HybridVersionCheck: HybridVersionCheckSpec(){
@@ -24,4 +26,36 @@ class HybridVersionCheck: HybridVersionCheckSpec(){
2426
}
2527

2628
public override val packageName= packageInfo?.packageName ?: "unknown"
29+
30+
override fun getStoreUrl(): Promise<String> {
31+
return Promise.async {
32+
"https://play.google.com/store/apps/details?id=$packageName"
33+
}
34+
}
35+
36+
override fun getLatestVersion(): Promise<String> {
37+
return Promise.async {
38+
try {
39+
val url = URL("https://play.google.com/store/apps/details?id=$packageName&hl=en")
40+
val html = url.readText()
41+
val regex = Regex("""\[\[\["(\d+\.\d+[\.\d+]*)"\]\]""")
42+
val match = regex.find(html)
43+
match?.groupValues?.get(1)
44+
?: throw Exception("Could not parse latest version from Play Store page")
45+
} catch (e: Exception) {
46+
throw Exception("Failed to fetch latest version: ${e.message}", e)
47+
}
48+
}
49+
}
50+
51+
override fun needsUpdate(): Promise<Boolean> {
52+
return Promise.async {
53+
try {
54+
val latest = getLatestVersion().await()
55+
version != latest
56+
} catch (e: Exception) {
57+
throw Exception("Failed to check for updates: ${e.message}", e)
58+
}
59+
}
60+
}
2761
}

package/ios/HybridVersionCheck.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,44 @@ class HybridVersionCheck: HybridVersionCheckSpec {
1010
func getCountry() throws -> String {
1111
Locale.current.regionCode ?? "unknown"
1212
}
13+
14+
func getStoreUrl() throws -> Promise<String> {
15+
return Promise.async {
16+
let bundleId = Bundle.main.bundleIdentifier ?? ""
17+
let url = URL(string: "https://itunes.apple.com/lookup?bundleId=\(bundleId)")!
18+
let (data, _) = try await URLSession.shared.data(from: url)
19+
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
20+
let results = json?["results"] as? [[String: Any]]
21+
guard let trackViewUrl = results?.first?["trackViewUrl"] as? String else {
22+
throw NSError(domain: "VersionCheck", code: 1, userInfo: [NSLocalizedDescriptionKey: "App not found on App Store"])
23+
}
24+
return trackViewUrl
25+
}
26+
}
27+
28+
func getLatestVersion() throws -> Promise<String> {
29+
return Promise.async {
30+
let bundleId = Bundle.main.bundleIdentifier ?? ""
31+
let url = URL(string: "https://itunes.apple.com/lookup?bundleId=\(bundleId)")!
32+
let (data, _) = try await URLSession.shared.data(from: url)
33+
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
34+
let results = json?["results"] as? [[String: Any]]
35+
36+
guard let latestVersion = results?.first?["version"] as? String else {
37+
throw NSError(domain: "VersionCheck", code: 2, userInfo: [NSLocalizedDescriptionKey: "App not found on App Store"])
38+
}
39+
return latestVersion
40+
}
41+
}
42+
43+
func needsUpdate() throws -> Promise<Bool> {
44+
return Promise.async { [self] in
45+
do {
46+
let latest = try await self.getLatestVersion().await()
47+
return self.version != latest
48+
} catch {
49+
throw NSError(domain: "VersionCheck", code: 3, userInfo: [NSLocalizedDescriptionKey: "Failed to check for updates: \(error.localizedDescription)"])
50+
}
51+
}
52+
}
1353
}

package/nitrogen/generated/android/c++/JHybridVersionCheckSpec.cpp

Lines changed: 50 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package/nitrogen/generated/android/c++/JHybridVersionCheckSpec.hpp

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroversioncheck/HybridVersionCheckSpec.kt

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package/nitrogen/generated/ios/NitroVersionCheck-Swift-Cxx-Bridge.cpp

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)