ホーム画面でアプリアイコンを長押しすると、「新規作成」「続きから」のようなメニューが下に開きます。これがクイックアクション(iOS の Home Screen Quick Actions、Android の App Shortcuts)です。よく使う操作をアプリを開く前の一手に短縮できる、地味ですが効く導線です。
私自身、個人開発で運営している記録系アプリにこれを足したとき、最初の実装では「アイコンから新規作成を選んでもホーム画面が開くだけ」という挙動になりました。アプリが起動していない状態(コールド起動)で項目を選ぶと、アクションの情報がアプリのルーター(画面遷移の仕組み)が立ち上がる前に届いてしまい、誰も受け取れないまま消えていたのです。この取りこぼしをどう防ぐかが、クイックアクション設計の本体でした。
実装の段取りを手順で押さえる
細かいコードに入る前に、本番投入までの段取りを並べておきます。
- 常に出す項目(新規作成など)を静的、状態依存の項目(続きから)を動的として整理する
expo-quick-actions で iOS/Android 共通の登録口を用意する
- コールド起動の初期アクションを保留し、ルーター準備後に再生する受け取り側を組む
- 初期アクションとリスナーの重複発火を ID で抑える
- ログアウトやデータ削除に追随して動的項目を後始末する
この五手のうち、つまずくのは 3 です。私はここを甘く見て、最初は取りこぼしの解決に丸一日かけました。残りは素直に組めます。
静的項目と動的項目の使い分け
クイックアクションには二種類あります。
- 静的項目: アプリのビルドに埋め込む固定のメニュー。アプリを一度も起動していなくても表示されます
- 動的項目: アプリ実行中にコードで差し替えるメニュー。利用者の状態に応じて変えられます
「新規作成」のように常に出したいものは静的、「○○の続きから」のように状態依存のものは動的、という棲み分けを推奨します。私の場合、未ログイン時は静的の「新規作成」だけを出し、ログイン後に「最後に開いた記録」を動的で追加する、という形に落ち着きました。
iOS は最大 4 項目まで表示します。Android も実用上は 4 つ程度に抑えるのが無難です。多く登録しても下に隠れて押されないので、本当に使うものだけに絞ったほうが効きます。
expo-quick-actions で iOS と Android を一本化する
標準の Rork アプリは React Native(Expo)で生成されるため、expo-quick-actions を使うと iOS の UIApplicationShortcutItem と Android の App Shortcuts を同じ API で扱えます。まず動的項目の登録です。
// quickActions.ts
import * as QuickActions from 'expo-quick-actions';
import { Platform } from 'react-native';
export async function setQuickActions(isLoggedIn: boolean) {
const items = [
{
id: 'new-entry',
title: '新規作成',
icon: Platform.OS === 'ios' ? 'symbol:square.and.pencil' : 'shortcut_new',
params: { route: '/entry/new' }, // 遷移先をパラメータで持たせる
},
];
if (isLoggedIn) {
items.push({
id: 'resume-last',
title: '続きから',
icon: Platform.OS === 'ios' ? 'symbol:arrow.uturn.left' : 'shortcut_resume',
params: { route: '/entry/last' },
});
}
await QuickActions.setItems(items);
}
params に遷移先のルートを持たせておくのが肝です。アクションの種別ごとに switch で分岐するより、項目自身に「どこへ行くか」を書いておくほうが、項目が増えても受け取り側がシンプルに保てます。
コールド起動の取りこぼしを保留と再生で防ぐ
ここが設計の中心です。アクションには二つの届き方があります。
| 起動状態 | アクションの届き方 | 注意点 |
| コールド起動(アプリ未起動) | 初期アクションとして起動時に一度だけ取得 | ルーター準備前に届くため保留が必要 |
| ウォーム起動(バックグラウンドから復帰) | リスナー経由でリアルタイムに届く | すぐ遷移してよい |
コールド起動では QuickActions.initial に最初のアクションが入っています。これを起動時に読みますが、この瞬間にはまだ画面遷移の準備ができていないことが多いです。そこで「保留して、ルーターが準備できたら再生する」形にします。これが取りこぼしの解決策です。
// useQuickActionRouting.ts
import { useEffect, useRef } from 'react';
import * as QuickActions from 'expo-quick-actions';
import { router } from 'expo-router';
export function useQuickActionRouting(isRouterReady: boolean) {
const pending = useRef<string | null>(null);
// 受け取ったアクションを保留 or 即実行する共通処理
const handle = (action: QuickActions.Action | null) => {
const route = action?.params?.route as string | undefined;
if (!route) return;
if (isRouterReady) {
router.push(route);
} else {
pending.current = route; // まだ遷移できないので保留
}
};
// コールド起動時の初期アクション(一度だけ)
useEffect(() => {
handle(QuickActions.initial ?? null);
const sub = QuickActions.addListener(handle); // ウォーム起動
return () => sub.remove();
}, []);
// ルーター準備が整った瞬間に保留分を再生する
useEffect(() => {
if (isRouterReady && pending.current) {
router.push(pending.current);
pending.current = null;
}
}, [isRouterReady]);
}
isRouterReady には、認証状態の確定やレイアウトのマウント完了など「遷移しても安全な状態」を渡します。コールド起動で届いたアクションを pending に握っておき、準備完了の useEffect で一度だけ再生するのが、取りこぼしを防ぐ確実な形でした。
重複発火と二重遷移を抑える
実装してみると、コールド起動で initial を読んだ直後にリスナーも同じアクションを流してきて、同じ画面へ二回遷移する本番障害が起きました。これを防ぐには、処理済みのアクション ID を覚えておきます。
const consumed = useRef<Set<string>>(new Set());
const handle = (action: QuickActions.Action | null) => {
if (!action) return;
const key = `${action.id}:${action.params?.route}`;
if (consumed.current.has(key)) return; // 既に処理済みなら無視
consumed.current.add(key);
// ...遷移処理
};
ID だけだと、同じ項目を続けて二度押したときに二回目が無視されてしまうので、route と組み合わせたキーにしておくと安全でした。ウォーム起動での連続実行も自然に扱えます。
古い動的項目を残さない後始末
動的項目は、アプリの状態が変わったら更新する必要があります。私が運用していて効いたのは次の二点です。
- ログアウト時に動的項目を消す: 「続きから」が残ったまま別アカウントでログインすると、前の利用者のデータへ飛んでしまいます。ログアウト処理で
setItems を静的相当の最小構成に戻します
- データ削除に追随する: 「続きから」が指す記録を削除したら、その動的項目も外すか、遷移先で「もう存在しない」場合のフォールバックを用意します
// ログアウト時に動的項目を初期状態へ戻す
async function onLogout() {
await setQuickActions(false); // ログイン依存項目を外す
}
クイックアクションは派手な機能ではありませんが、よく使う操作を一手で呼べると利用頻度が静かに上がります。実装の難所は機能そのものより、コールド起動で届くアクションを取りこぼさない受け取り側の設計に集約されます。保留と再生、重複の抑制、状態に応じた後始末――この三つを丁寧に組めば、起動前の一手として安定して働く導線になります。同じ課題に取り組む方の参考になれば幸いです。