iOS のシミュレータではウィジェットが問題なく出ているのに、npx expo run:android を実行した瞬間に Kotlin のエラーが並びます。依存を少し上げただけで、昨日まで通っていたビルドが止まってしまうのです。
そんな状況になったときのために、いま GitHub で報告されている expo-widgets の Android 側の不具合を、切り分けの手順としてまとめます。私自身はこの不具合を再現しておらず、以下は Issue の本文と、手元で動作を確かめたスクリプトに基づく内容です。
症状:どのエラーが出たら疑うか
報告されているのは expo/expo の Issue #51653 です(2026年10月16日に起票、私が確認した10月20日時点では Open)。
- 対象は expo-widgets の 58.0.17 以降(報告者は 58.0.18 でも確認)。58.0.16 は影響なしとされています
- Android のネイティブビルドだけで起き、iOS は影響なしとされています
- Kotlin のコンパイルで
Unresolved reference 'jakex7'、Unresolved reference 'PeekNotificationViews'、Unresolved reference 'setPeekContent'といった行がCustomNotificationBuilder.ktとCustomNotificationRenderer.ktから出ます
報告者の説明では、原因は依存の名前の食い違いです。通知まわりの新しいコードが io.github.jakex7.peek を import している一方で、build.gradle が実際に取り込んでいるのは io.github.expo.peek 側の部品(0.3.2)で、jakex7 側は Maven Central で 0.3.0 までしかなく依存にも宣言されていない、という指摘です。
つまり、アプリのコードを疑っても何も出てきません。パッケージの特定の版が Android でだけ壊れているという切り分けになります。エラーに上の参照名が含まれていれば、この件を第一候補にしてかまいません。
逃げ道は2つ:版を固定するか、Android から外すか
Issue に回避策として示されているのは autolinking からの除外です。版の固定は、58.0.16 に該当の import がないという報告から導ける手段で、Issue 自体が勧めているわけではありません。
| 状況 | 選ぶ手 | 手元で起きること |
|---|---|---|
| Android でもウィジェットを出したい | 58.0.16 に固定する | 報告上は影響なしの版に戻ります。次に版を上げるまで、新しい通知機能は使えません |
| ウィジェットは iOS だけでよい | Android の autolinking から除外する | Android のビルドに expo-widgets が入らなくなり、版は最新のまま使えます |
| どちらでもない(まだ使っていない) | 導入を保留する | Issue が解消してから入れても遅くありません |
私なら、まず「Android にウィジェットが本当に必要か」を先に決めます。壁紙アプリのように、ウィジェットが iOS 側の付加機能にすぎない場合は、除外のほうが後始末が少ないと感じています。
版を固定する場合
^ や ~ が付いたままだと、次の npm install で 58.0.17 以降に戻ってしまいます。範囲指定を外して、完全な版番号にします。
npm install expo-widgets@58.0.16 --save-exactその後、package.json の expo-widgets が "58.0.16" のように記号なしになっていることを確認してください。Issue の再現手順にあるコンパイルタスクで、Android 側が通るかも確かめます。
npx expo prebuild -p android
cd android && ./gradlew :expo-widgets:compileDebugKotlinAndroid から外す場合
iOS だけでウィジェットを使うなら、package.json に次を足します。Issue で報告者が示している回避策です。
{
"expo": {
"autolinking": {
"android": {
"exclude": ["expo-widgets"]
}
}
}
}設定を足したら、Android のフォルダを作り直します。
npx expo prebuild -p android --clean--clean は既存の android フォルダを消してから作り直します。手でネイティブ側を書き換えている場合は、先にバックアップを取ってください。また、Android 側で expo-widgets の関数を呼んでいるコードがあれば、Platform.OS で分けておく必要があります。除外後の挙動は私の手元では確かめておらず、この点は各自のプロジェクトでの確認をお願いしたいところです。
自分のプロジェクトが対象かを一発で判定する
「いまの版がどれで、どう逃げているのか」を毎回 package.json と node_modules で見比べるのは手間です。そこで、判定だけを行う小さなスクリプトを用意しました。Node だけで動き、ネットワークには接続しません。
// check-expo-widgets-android.mjs
// 使い方: node check-expo-widgets-android.mjs [プロジェクトのパス]
import { readFileSync, existsSync } from "node:fs";
import { join, resolve } from "node:path";
const root = resolve(process.argv[2] ?? ".");
const readJson = (p) => JSON.parse(readFileSync(p, "utf8"));
const pkgPath = join(root, "package.json");
if (!existsSync(pkgPath)) {
console.error(`package.json が見つかりません: ${pkgPath}`);
process.exit(2);
}
const pkg = readJson(pkgPath);
const declared = { ...pkg.dependencies, ...pkg.devDependencies }["expo-widgets"];
if (!declared) {
console.log("expo-widgets は依存に入っていません。この問題の対象外です。");
process.exit(0);
}
const installedPath = join(root, "node_modules", "expo-widgets", "package.json");
if (!existsSync(installedPath)) {
console.error("node_modules に expo-widgets がありません。先に npm install を実行してください。");
process.exit(2);
}
const installed = readJson(installedPath).version;
const [major, minor, patch] = installed.split(/[.-]/).map(Number);
const excluded = (pkg.expo?.autolinking?.android?.exclude ?? []).includes("expo-widgets");
const pinned = /^\d+\.\d+\.\d+$/.test(declared); // ^ や ~ が付いていなければ固定
const brokenRange = major === 58 && minor === 0 && patch >= 17;
console.log(`宣言: ${declared}(${pinned ? "固定" : "範囲指定"})`);
console.log(`導入済み: ${installed}`);
console.log(`Android の autolinking から除外: ${excluded ? "はい" : "いいえ"}`);
if (excluded) {
console.log("判定: OK(Android には expo-widgets を入れない設定です)");
} else if (!brokenRange) {
console.log(
pinned
? "判定: OK(報告のある 58.0.17 以降ではなく、版も固定されています)"
: "判定: 注意(今は問題のない版ですが、範囲指定なので次回の install で 58.0.17 以降に上がる可能性があります)"
);
process.exit(pinned ? 0 : 1);
} else {
console.log("判定: 要対処(58.0.17 以降です。Android のビルドで Kotlin のコンパイルエラーが報告されています)");
process.exit(1);
}終了コードは、問題なしが 0、対処または注意が 1、前提が足りない場合が 2 です。CI の最初の手順に置けば、依存の自動更新が 58.0.17 以降を持ち込んだ時点で、Gradle を回す前に止められます。ビルドが数分走ってから落ちるより、数秒で気づけるほうがずっと楽です。
私は 58.0.18・58.0.16・範囲指定・除外ありの4パターンの模擬プロジェクトを作り、上の4つの判定がそれぞれ想定どおりに出ることを確かめました。
もう一つ、同じ時期の報告:Android では時刻で更新されない
別の Issue(#51633)では、Android の expo-widgets で updateTimeline() が何もせず、生成される XML の updatePeriodMillis も 0 に固定されているという指摘があります。報告者の整理では、アプリが起動している間に updateSnapshot() を呼んだときだけ表示が変わる状態です。こちらも私が見た時点では Open で、メンテナーの返答は付いていませんでした。
ここから言えるのは、Android で「毎朝0時に切り替わる壁紙ウィジェット」のような時刻駆動の更新を、expo-widgets に任せる設計は今は避けたほうがよい、という判断です。どちらの Issue も状況が変わりうるので、手を入れる前に最新の状態を見ておくと安心です。
iOS 側の更新の仕組みについては、WidgetKit のタイムラインを更新予算と向き合って設計する — 壁紙アプリのウィジェットが止まった理由 に整理してあります。
まとめ:今日やること
node check-expo-widgets-android.mjsを実行して、いまの状態を確かめる- Android にウィジェットが必要なら 58.0.16 に固定し、不要なら autolinking から除外する
- 依存を上げる前に、Expo SDK 58 に上げる前に、自分のコードで消える API を一覧にしておく の確認もあわせて行う
Android のビルド失敗がキャッシュ由来のように見える場合は、EAS Build だけ「No matching variant」で落ちるとき、まず疑う手元の android/build も合わせてご覧ください。
「落ちた原因がアプリか依存の特定の版か」を、先に切り分けること。 これだけで、半日の迷子がだいぶ減ります。
出典:expo/expo Issue #51653、#51633(いずれも2026年10月20日に本文を確認)。