◉ANTIGRAVITY LABJP
Articles/App Development
▣ App Development/2026-05-10Intermediate

Three Decisions I Kept for Myself When Antigravity Maintained My Older Apps

How far could Antigravity maintain my long-running wallpaper and wellness apps? A field log of crash fixes and a staged rollout, with the lines I drew between delegated and retained work, and how I write context notes.

Antigravity386Indie Development8MaintenanceRefactoringReal ExperienceAdMob12

✦ Premium Article

Last month, crash reports kept arriving for the Android version of one of my wallpaper apps.

Tracing them back, the cause was a list being modified while RecyclerView was updating, which produced an IndexOutOfBoundsException. Over 28 days, more than 50 users had been affected.

I knew the fix. The trouble was that the same structure existed in several screens across several apps, and my energy ran out before the typing did.

So I handed part of the maintenance to Antigravity. Here is what I handed over, what I did not, and what I was thinking at the boundary.

The fix itself could be delegated

I fixed the RecyclerView crash by defensively copying the list at update time. Once the fix was settled in one app, I asked the agent to apply the same diff to similar screens, respecting each app's naming conventions.

// Goal: the adapter must not crash even if the source list changes mid-update
// Before: held the caller's list directly
class WallpaperAdapter(private var items: MutableList<Wallpaper>) :
    RecyclerView.Adapter<WallpaperHolder>() {
    fun update(newItems: MutableList<Wallpaper>) {
        items = newItems          // later mutation by the caller can cause out-of-range access
        notifyDataSetChanged()
    }
}
 
// After: copy on receipt; the adapter only ever reads its own copy
class WallpaperAdapter(items: List<Wallpaper>) :
    RecyclerView.Adapter<WallpaperHolder>() {
    private var items: List<Wallpaper> = items.toList()
    fun update(newItems: List<Wallpaper>) {
        items = newItems.toList() // why: ownership stays inside the adapter, blocking outside changes
        notifyDataSetChanged()
    }
}

The agent reported, "One app calls it AdManager, another AdvertisingService, so I adapted each accordingly." Doing this by hand, I would certainly have mixed up a name or two.

This kind of work shares two traits: the same shape appears three or more times, and the result can be measured. Whether crashes dropped is answered by the post-release dashboard.

The one-line fix was the hardest part

There was another, different crash.

After upgrading Glide to 5.0.5 and aligning Android Gradle Plugin to 9.x, only Android 6.0.1 devices crashed with NoClassDefFoundError: the Java 8 Supplier could not be found.

When I asked the agent for the cause, it offered several candidates: pin the library, raise minSdk, or avoid the offending call. Each was plausible, and each had wide side effects.

The actual fix was one line: coreLibraryDesugaringEnabled true, which lets older devices use newer APIs by transforming code at build time.

What I took from it: an agent is good at listing every way to fix something, and weaker at choosing what to leave unchanged. Raising minSdk would have meant giving up users on older devices. That is an operations decision, not a code problem.

I chose the one-line option and recorded the rejected ones in a note, along with why.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦After reading, you can sort the work in your own old repositories into 'safe to hand to an agent' and 'keep in your hands' using three questions
✦After reading, you can write a 3-4 sentence context note that stops an agent from helpfully deleting a workaround
✦After reading, you can decide, before release day, what to watch at each staged-rollout step (5%, 25%, 50%, 100%) and the numbers at which you stop
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

▣ App Dev2026-05-21
Tuning AdMob Placements at Runtime with Firebase Remote Config and an Antigravity Agent
A practical look at how I combine Firebase Remote Config with an Antigravity Agent to nudge AdMob placements while the app is live, drawn from years of running wallpaper apps as an indie developer.
▣ App Dev2026-10-15
I Swapped an Immutable Asset at the Same URL, and Only the Browser Kept the Old Copy
Overwrite a long-cached JS file at the same URL and neither a CDN purge nor a refresh will make the browser fetch it again. Four delivery styles — same URL, no-cache, query string, content-hash filename — compared in Chromium, plus a complete build-step script that adds the hash.
▣ App Dev2026-09-29
Purging Cloudflare Didn't Bring Back the New Wallpapers: the Stale JPEGs Were Held by XPageSpeed on the Origin
An operations record from a wallpaper app: images overwritten at the same URL kept arriving stale even after a Cloudflare purge. Covers the three-way local/origin/edge probe that names the guilty layer, when prefix purge beats per-URL purge, and how to switch off Xserver's XPageSpeed in .htaccess without a 500.