Changelark for React Native
Show What's new after every update, with votes and anonymous comments, in React Native apps on iOS and Android. It works with Expo and bare React Native.
Install
npm install changelark-react-native @react-native-async-storage/async-storage expo-applicationThe app version is read from your native build, so you never type it. expo-application does that in Expo apps; in bare React Native, install react-native-device-info instead. In Expo Go, pass appVersion yourself, since Expo Go reports its own version.
No npm access? Download the package and run npm install ./changelark-react-native-0.1.0.tgz.
Wrap your app
Add the provider once, around your navigation. Use the public app key from the Install tab in the dashboard.
import { ChangelarkProvider } from "changelark-react-native";
export default function App() {
return (
<ChangelarkProvider appKey="clk_…">
<RootNavigator />
</ChangelarkProvider>
);
}When the sheet appears
- First install: the version is recorded quietly. Nobody is interrupted.
- New version: its notes show once, the first time the app opens.
- Seen or older version: skipped.
- Notes not published yet: it waits and tries again on the next launch (up to 3 days).
- You replied to someone: on their next launch without new notes, the sheet opens on the feedback chat so they see your answer. Once per reply.
Set treatFirstLaunchAsUpdate to show the notes on first install too. The sheet follows the phone's light or dark appearance; in Expo, set "userInterfaceStyle": "automatic" in app.json, because the default template locks apps to light.
Votes and comments
People vote on the update and on comments without an account. They accept the community guidelines once before commenting, and can reply, report, block someone, or delete their own comment. You reply as the developer from the dashboard.
Bug reports
Under the reactions there's a Report a bug link with a short private form. Reports land in the Bugs tab of your inbox. To attach a screenshot of your app, pass captureScreenshot; with react-native-view-shot it's one line:
import { captureRef } from "react-native-view-shot";
const root = useRef(null);
<ChangelarkProvider
appKey="clk_…"
captureScreenshot={() => captureRef(root, { format: "jpg", quality: 0.6, result: "base64" })}
>
<View ref={root} collapsable={false} style={{ flex: 1 }}>
<RootNavigator />
</View>
</ChangelarkProvider>Without it, reports are text only. You can also show the form in your own screen with <BugReport onBack={…} />, or send one with client.reportBug({ text, screenshot }).
Languages
The package sends the phone's language. If you added a translation for it in the dashboard, the sheet shows that; otherwise your original notes.
Is the app key safe to ship?
Yes. It's a public key (it starts with clk_), so think of it as a web address, not a password. Anyone can read it out of your app, and that's fine: it only picks which app's notes to load and where anonymous comments and votes go. It can't log into the dashboard, change releases, see private data or moderate.
- The package refuses any key that doesn't start with
clk_, so a secret key can't slip in by mistake. - Spam from a copied key is caught by reports and moderation, and the server allows each installation five comments a minute and each network thirty.
- For the strongest protection, turn on App proofs for the app in the dashboard and pass
getAppProof. Every vote and comment then needs an Apple App Attest assertion or a Google Play Integrity token, checked by the server, so only genuine copies of your app can post. The public web page becomes read-only for that app. - If a key is abused, choose Make a new key in the Install tab and ship an update.
<ChangelarkProvider
appKey="clk_…"
getAppProof={async (challenge, { attest }) => {
// From your App Attest / Play Integrity native module (not part of this package).
if (Platform.OS === "android") {
return { kind: "play", token: await PlayIntegrity.requestToken({ nonce: challenge }) };
}
const clientDataHash = sha256(challenge);
return attest
? { kind: "apple", keyId, attestation: await AppAttest.attestKey(keyId, clientDataHash) }
: { kind: "apple", keyId, assertion: await AppAttest.generateAssertion(keyId, clientDataHash) };
}}
>Open it yourself
import { useChangelark } from "changelark-react-native";
function WhatsNewButton({ release }) {
const { open } = useChangelark();
return <Button title="What's new" onPress={() => open(release)} />;
}