新機能を入れても気づかれない、という壁に個人開発で何度もぶつかってきました。App Store の「このバージョンの新機能」欄はほとんど読まれませんし、プッシュ通知は許可していない人には届きません。残る確実な接点は、更新後に最初に開いたその瞬間の画面です。リテンションに効く機能ほど、ここで気づいてもらえるかどうかが効いてきます。
ところがこの「新着情報」画面は、作り方を一つ間違えると一気に嫌われます。インストールしたばかりの人にまで「新機能のお知らせ」が出る、開くたびに同じ案内が出る、こうなると体験を損ねるだけです。これは私自身が初期に踏んだ落とし穴でもあります。境界を正しく引いて「更新した人に、一度だけ」を成立させる設計をまとめます。
「更新した人にだけ」をどう判定するか
判定に必要なのは二つの値です。いま動いているアプリの「現在バージョン」と、このユーザーが「前回どのバージョンを使っていたか」。後者を毎回保存しておき、起動時に前者と突き合わせます。
- 保存値が無い → 新規インストール。新着情報は出さない。
- 保存値 < 現在 → 更新された。新着情報を出す候補。
- 保存値 = 現在 → 同じバージョンで再起動しただけ。出さない。
現在バージョンは expo-application から取得します。app.json の version を手で持ち回るより確実です。
// lib/appVersion.ts
import * as Application from "expo-application";
// "1.4.0" のような表示用バージョン。null になり得るのでフォールバックを置く。
export const CURRENT_VERSION = Application.nativeApplicationVersion ?? "0.0.0";
// 文字列バージョンを比較可能な配列にする素朴なセムバー比較。
export function isNewer(a: string, b: string): boolean {
const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
const x = pa[i] ?? 0;
const y = pb[i] ?? 0;
if (x !== y) return x > y;
}
return false;
}前回バージョンの記録と判定をまとめる
判定ロジックは小さな関数一つに閉じ込めます。ここで大事なのは「新着情報を出すか」と「前回バージョンを更新するか」を分けて考えることです。前回バージョンの保存は、新着情報を出したかどうかに関わらず、必ず行います。さもないと、新着情報のデータがまだ無い更新でも、次の本命の更新時に「2バージョン分まとめて」案内が出てしまいます。これが見落としやすい注意点です。
// lib/whatsNew.ts
import AsyncStorage from "@react-native-async-storage/async-storage";
import { CURRENT_VERSION, isNewer } from "./appVersion";
const LAST_VERSION_KEY = "whatsNew.lastVersion";
export type WhatsNewDecision =
| { show: false }
| { show: true; fromVersion: string; toVersion: string };
export async function evaluateWhatsNew(): Promise<WhatsNewDecision> {
const last = await AsyncStorage.getItem(LAST_VERSION_KEY);
// 前回バージョンは常に最新へ更新する(判定の後始末を先に確定)。
const persist = () =>
AsyncStorage.setItem(LAST_VERSION_KEY, CURRENT_VERSION);
// 新規インストール: 記録だけ残して何も出さない。
if (!last) {
await persist();
return { show: false };
}
// 同一 or 何らかの理由で現在が古い: 出さない。
if (!isNewer(CURRENT_VERSION, last)) {
await persist();
return { show: false };
}
// 更新された: 出す候補。fromVersion を呼び出し側に渡す。
const decision: WhatsNewDecision = {
show: true,
fromVersion: last,
toVersion: CURRENT_VERSION,
};
await persist();
return decision;
}新規インストールで persist() だけして show: false を返すのが肝です。これで初回は静かに記録だけ取り、次回以降の更新から正しく一度だけ出せます。この挙動は実機の新規インストールで必ず確認することを推奨します。