The widget shows up fine in the iOS simulator, but the moment you run npx expo run:android, a wall of Kotlin errors appears. You only bumped a dependency, and yesterday's build is suddenly stopped.
This is a walkthrough of the expo-widgets Android problem currently reported on GitHub, written as a way to narrow things down. I have not reproduced the failure myself. What follows rests on the issue text and on a script I did run against mock projects.
The symptom: which errors point here
The report is expo/expo issue #51653, opened on October 16, 2026 and still open when I read it on October 20.
- It affects expo-widgets 58.0.17 and later (the reporter also saw it on 58.0.18). 58.0.16 is reported as unaffected
- It happens only in native Android builds; iOS is unaffected
- Kotlin compilation prints lines such as
Unresolved reference 'jakex7',Unresolved reference 'PeekNotificationViews'andUnresolved reference 'setPeekContent', coming fromCustomNotificationBuilder.ktandCustomNotificationRenderer.kt
According to the reporter, the cause is a naming mismatch between dependencies. The new notification code imports io.github.jakex7.peek, while build.gradle actually pulls in io.github.expo.peek (0.3.2). The jakex7 artifacts stop at 0.3.0 on Maven Central and are not declared as a dependency.
So nothing in your own app code will explain it. A specific package version is broken on Android only. If your error contains those names, treat this as the first suspect.
Two ways out: pin the version, or leave Android out
The issue itself names the autolinking exclusion as the workaround. Pinning is an inference from the report that 58.0.16 lacks the broken imports, not something the issue recommends.
| Situation | What to do | What happens |
|---|---|---|
| You want widgets on Android too | Pin to 58.0.16 | You return to the version reported as unaffected. The newer notification features wait until you move up again |
| Widgets only need to exist on iOS | Exclude from Android autolinking | expo-widgets stays out of the Android build, and you can keep the latest version |
| You have not adopted it yet | Hold off | Adding it after the issue is resolved costs you nothing |
I would first decide whether Android really needs the widget. When a widget is an extra on the iOS side, as it is in my wallpaper apps, exclusion leaves less to clean up later.
If you pin the version
With ^ or ~ still in place, the next npm install can move you back to 58.0.17 or later. Remove the range and use an exact version.
npm install expo-widgets@58.0.16 --save-exactThen confirm that package.json shows "58.0.16" without a prefix, and run the compile task from the issue's reproduction steps to see whether Android passes.
npx expo prebuild -p android
cd android && ./gradlew :expo-widgets:compileDebugKotlinIf you exclude it from Android
When widgets are iOS-only, add the following to package.json. This is the workaround the reporter shows in the issue.
{
"expo": {
"autolinking": {
"android": {
"exclude": ["expo-widgets"]
}
}
}
}After adding it, regenerate the Android folder.
npx expo prebuild -p android --clean--clean deletes the existing android folder before regenerating it, so take a backup first if you have edited native files by hand. If any Android code path calls expo-widgets functions, guard them with Platform.OS. I have not checked how a project behaves after exclusion, so please verify that part in your own project.
Check whether your project is affected
Comparing package.json with node_modules every time is tedious, so here is a small script that only judges. It needs just Node and makes no network calls.
// check-expo-widgets-android.mjs
// Usage: node check-expo-widgets-android.mjs [project path]
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 not found: ${pkgPath}`);
process.exit(2);
}
const pkg = readJson(pkgPath);
const declared = { ...pkg.dependencies, ...pkg.devDependencies }["expo-widgets"];
if (!declared) {
console.log("expo-widgets is not a dependency. This issue does not apply.");
process.exit(0);
}
const installedPath = join(root, "node_modules", "expo-widgets", "package.json");
if (!existsSync(installedPath)) {
console.error("expo-widgets is missing from node_modules. Run npm install first.");
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); // no ^ or ~ means pinned
const brokenRange = major === 58 && minor === 0 && patch >= 17;
console.log(`Declared: ${declared} (${pinned ? "pinned" : "range"})`);
console.log(`Installed: ${installed}`);
console.log(`Excluded from Android autolinking: ${excluded ? "yes" : "no"}`);
if (excluded) {
console.log("Verdict: OK (expo-widgets is kept out of the Android build)");
} else if (!brokenRange) {
console.log(
pinned
? "Verdict: OK (not in the reported 58.0.17+ range, and the version is pinned)"
: "Verdict: WARN (safe version now, but the range may move to 58.0.17+ on the next install)"
);
process.exit(pinned ? 0 : 1);
} else {
console.log("Verdict: ACTION NEEDED (58.0.17+; Kotlin compile errors are reported on Android)");
process.exit(1);
}The exit code is 0 for fine, 1 for action or a warning, and 2 when prerequisites are missing. Placed as the first CI step, it can stop an automated dependency bump to 58.0.17 or later before Gradle even starts. Noticing in a few seconds beats failing after several minutes of build.
I built four mock projects (58.0.18, 58.0.16, a range specifier, and one with the exclusion) and confirmed that each returned the expected verdict.
A second report from the same period: no time-based updates on Android
Another issue (#51633) says that on Android, updateTimeline() in expo-widgets does nothing, and the generated XML hardcodes updatePeriodMillis to 0. In the reporter's summary, the display changes only when the running app calls updateSnapshot(). It was also open when I looked, with no maintainer reply.
The practical reading: for now, avoid designing a time-driven widget on Android, such as one that swaps the wallpaper at midnight, around expo-widgets. Both issues may change, so look at their latest state before you act.
For how updates work on the iOS side, see Designing WidgetKit Timelines Around the Refresh Budget: Why My Wallpaper Widgets Stopped.
What to do today
- Run
node check-expo-widgets-android.mjsto see where you stand - Pin to 58.0.16 if Android needs the widget; otherwise exclude it from autolinking
- Before upgrading dependencies, also run through Before Moving to Expo SDK 58, List What Disappears in Your Own Code
If your Android failure looks cache-related instead, When Only EAS Build Fails with "No matching variant", Check Your Local android/build First may fit better.
Decide first whether your app or one specific dependency version is breaking the build. That alone saves a lot of wandering.
Sources: expo/expo issues #51653 and #51633 (both read on October 20, 2026).