◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Expo SDK 58 が 10/15 に正式リリースされました(Beta は 09/15 から)。上げる前に依存の対応状況を確認しましょう●SDK 58 ISSUE — Android ウィジェットが定期更新されない、Stripe が静的リンクになるなど初期報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Expo SDK 58 が 10/15 に正式リリースされました(Beta は 09/15 から)。上げる前に依存の対応状況を確認しましょう●SDK 58 ISSUE — Android ウィジェットが定期更新されない、Stripe が静的リンクになるなど初期報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/開発ツール
⬡ 開発ツール/2026-10-21中級

iOS では時刻どおりに変わるウィジェットが、Android では「Initial」のまま動かない

expo-widgets の updateTimeline は Android では何も起こさず、生成される XML の updatePeriodMillis も 0 です。症状の見分け方、updateSnapshot で補える範囲、設計を切り替える判断と確認スクリプトをまとめました。

expo-widgetsExpo SDK 583Android58ウィジェット8トラブルシューティング90

ホーム画面にウィジェットを置いて、一分おきに表示が変わるはずのタイムラインを予約し、画面を眺めて待ちます。iOS では一分後にきちんと切り替わるのに、Android のエミュレータでは「Initial」のまま、五分たっても動きません。

コードを疑い、ビルドキャッシュを疑い、端末の省電力設定を疑って、一時間ほど手が止まる——そういう夜が起こりうる不具合です。いま GitHub の Expo リポジトリで報告されている内容を、切り分けの順番に並べ直しました。私自身は Android 実機でこの不具合を再現しておらず、以下は Issue の本文と、Expo 側の回答、そして手元で動かした確認スクリプトに基づいています。

最初にお伝えしたいのは、これは設定ミスではなく Android では仕様として動かない という点です。

何が起きているのか

報告では、SDK 58 の開発ビルドで次のような状況でした。

  • ウィジェット追加直後は「Initial」の表示
  • アプリ側から updateTimeline() を呼び、一分間隔の五つのエントリを予約
  • 待っても表示は変わらないまま

Expo 側の調査コメントによると、原因は二つあります。updateTimeline() は Android では冒頭で何もせずに戻るため、予約したエントリは捨てられます。そして config plugin が生成する appwidget-provider の XML には android:updatePeriodMillis="0" が固定で書かれており、変更する設定項目もありません。

つまり Android のウィジェットは、アプリが自分で updateSnapshot() を呼ばない限り、自分では表示を変えられません。Expo のメンテナーは「Android にはタイムライン機能がない、不足している機能であって回帰ではない」という趣旨で回答し、ドキュメントに「Android では updateTimeline は効果がない」という注記を足す変更が別途提案されています(記事執筆時点で未マージのため、取り込まれた後の文言は公式ドキュメントでご確認ください)。

切り分けの順番

手が止まる前に、次の順で確認していただければ数分で済みます。

  1. 同じコードを iOS で動かして、切り替わるかを見ます。iOS で動けば、コードの論理は正しい可能性が高いです
  2. Platform.OS === "android" の分岐に updateTimeline を置いていないかを見ます。置いていれば、その呼び出しは今のところ空振りです
  3. 生成された XML の updatePeriodMillis を読みます。0 なら、システムによる定期更新はありません
  4. アプリを開いた状態で updateSnapshot() を呼び、表示が変わるかを見ます。変わるなら、描画の経路は健全です

三番を飛ばして省電力設定ばかり疑うと、遠回りになりがちです。いま思えば、私も似た順番で疑いかけていた気がします。端末の設定を一つずつ変える前に、生成物の XML を読むほうが、はるかに早く済みます。

生成された XML を機械的に確かめる

npx expo prebuild -p android のあとで、android ディレクトリ以下の appwidget-provider を拾い、更新間隔を一覧にする小さなスクリプトを書きました。モックの XML では想定どおりに「定期更新なし」と出力されることを確かめています。実際の生成物に対する動作は、手元のプロジェクトでご確認ください。

// android/ 以下の appwidget-provider を探し、updatePeriodMillis を一覧にします。
import { readdirSync, readFileSync, statSync } from "node:fs";
import { join } from "node:path";
 
function* walk(dir) {
  for (const name of readdirSync(dir)) {
    if (name === "build" || name === ".gradle") continue;
    const p = join(dir, name);
    if (statSync(p).isDirectory()) yield* walk(p);
    else if (p.endsWith(".xml")) yield p;
  }
}
 
let stuck = 0;
for (const file of walk(process.argv[2] ?? "android")) {
  const xml = readFileSync(file, "utf8");
  if (!xml.includes("<appwidget-provider")) continue;
  const m = xml.match(/android:updatePeriodMillis="(\d+)"/);
  const ms = m ? Number(m[1]) : null;
  // 0 は「システムによる定期更新なし」。30 分未満の値は Android 側で切り上げられます。
  const note = ms === 0 ? "定期更新なし" : ms === null ? "指定なし" : `${ms / 60000} 分間隔`;
  if (ms === 0 || ms === null) stuck++;
  console.log(`${file}: ${note}`);
}
console.log(stuck ? `定期更新のないウィジェット: ${stuck} 件` : "すべて定期更新あり");

出力が 定期更新なし なら、そのウィジェットは Android では「アプリが動いている間だけ」更新される、という前提で設計する必要があります。

アプリ側でできること

updateSnapshot() はアプリが生きているあいだなら使えます。Expo の回答でも、ウィジェット上のボタンの押下ハンドラから新しい props を返す方法が挙がっていました。次のような分岐で、iOS はタイムライン、Android はスナップショットと使い分けられます。

import { Platform } from "react-native";
import { MyWidget } from "./widgets/MyWidget";
 
type Entry = { date: Date; props: { label: string } };
 
export function scheduleLabels(entries: Entry[]) {
  if (Platform.OS === "ios") {
    // iOS: システムが時刻に合わせて切り替える
    MyWidget.updateTimeline(entries);
    return;
  }
  // Android: 予約はできない。いまの表示だけを即時に差し替える
  const now = Date.now();
  const current = [...entries].reverse().find((e) => e.date.getTime() <= now);
  if (current) MyWidget.updateSnapshot(current.props);
}

Platform.OS で分けるのは、Android 側の updateTimeline が将来効くようになったときに、呼び出しを残したまま挙動が変わるのを避けるためです。コメントに「いまは何も起こらない」と書いておくと、半年後の自分が読み返したときに迷いません。

ただし限界もあります。報告者が指摘しているとおり、バックグラウンドのタスクから updateSnapshot() を呼ぶ方法は、JavaScript の実行環境を毎回起こす必要があり、省電力の制限下では時刻どおりに動く保証がありません。「表示が数分ずれてもよいもの」には使えますが、「毎朝七時に切り替える」のような約束には向きません。

落とし穴

  • updatePeriodMillis を後から XML だけ手で書き換えても、次の prebuild で上書きされます。変えるなら config plugin 側ですが、現状その設定項目はありません
  • Android は updatePeriodMillis を 30 分未満にしても、30 分に切り上げます。仮に設定できても、分単位の切り替えには使えません
  • iOS で通ったから Android も通る、という前提を、ウィジェットでは置かないほうが安全です

設計の線引き

私は個人開発で壁紙アプリを複数運用していて、日替わりで表示を変えるウィジェットはいちばん作りたくなる機能の一つです。だからこそ、最初の設計の段階で次のように割り切っています。

時刻に約束のある表示は iOS に、アプリを開いたときに追いつく表示は Android に。 両方に同じ約束をしないことが、いちばん手戻りの少ない線引きでした。

Android でも時刻どおりに切り替えたい場合は、ネイティブの AlarmManager や WorkManager を自分で足す道が残っています。ただし expo-widgets の生成物を自分で拡張することになり、SDK を上げるたびに追従の手間が増えます。そこまでの価値があるかは、そのウィジェットが無くなったときに困る人がいるかどうかで決めるのが、現実的だと思います。

次の一歩

まず prebuild のあとに上のスクリプトを一度実行し、自分のウィジェットが Android で「定期更新なし」になっていないかを見てみてください。見つかったら、その表示を iOS 限定の約束にするか、アプリ起動時に追いつく設計に直すか、どちらかを今日のうちに決めておくのが良いと思います。

iOS 側の更新予算の考え方は WidgetKit のタイムラインと更新予算の考え方 に、同じ expo-widgets の Android ビルド失敗については 版の固定と除外の判断 にまとめています。SDK 58 に上げる前の点検は 削除された API の監査スクリプト もご覧ください。

この原則だけは、機能が増えても守るようにしています。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

◇ アプリ開発2026-10-21
iOS では動く expo-widgets が Android のビルドだけで落ちる — 版の固定と除外、どちらで逃げるかの判断
expo-widgets を 58.0.17 以降に上げると Android だけ Kotlin のコンパイルで止まる報告があります。症状の見分け方、版の固定と autolinking 除外の使い分け、手元で判定できる確認スクリプトをまとめました。
⬡ 開発ツール2026-10-07
日本語の見出しだけ太字にならない — Android で fontWeight が効かないときの切り分けと直し方
Rork で作ったアプリで、iPhone では太字なのに Android では日本語の見出しが細いままになる症状の切り分け記事です。原因の見分け方、fontFamily を重みごとに分ける直し方、確認用コードを載せています。
⬡ 開発ツール2026-05-15
Rork Max Android アプリが古い OS バージョンだけクラッシュする原因と Java 8 デシュガリング修正
Rork Max で生成した Android アプリが古い OS バージョン(Android 6〜8 など)でのみクラッシュする問題の診断方法と、Java 8 デシュガリング設定による根本解決を解説します。