RORK LABJP
TOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the roundTOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the round
Articles/App Dev
App Dev/2026-05-28Intermediate

Adding Slideshow and Page Jump to My Android Wallpaper Apps

Implementation notes from adding slideshow, a page-jump slider, and an ad-free option to the Android editions of Beautiful 4K/HDR Wallpapers and Ukiyo-e Wallpapers.

Android43RecyclerView3ViewPager2indie developer37wallpaper app23

Two requests had been turning up in user reviews for years. One was "let me lie in bed and have the wallpapers advance on their own," and the other was "there are too many wallpapers, I cannot reach the one I want." This week I shipped an update for the Android editions of Beautiful 4K/HDR Wallpapers and Ukiyo-e Wallpapers that adds a slideshow and a page-jump slider to address both. The same release also introduces an ad-free option, which is another long-standing request. The code itself is not exotic, but as an indie developer who has been running wallpaper apps since 2014, I want to leave a record of the moments where I hesitated and what I chose.

Threading auto-advance through the existing RecyclerView

A slideshow that "moves to the next wallpaper every three seconds" sounds trivial, but rolling your own Thread tends to leak across rotations and background transitions. I decided to keep the underlying RecyclerView that ViewPager2 drives, and orchestrate everything from a UI-thread Handler.

private val mainHandler = Handler(Looper.getMainLooper())
private val advanceRunnable = object : Runnable {
    override fun run() {
        val next = viewPager.currentItem + 1
        if (next < adapter.itemCount) {
            viewPager.setCurrentItem(next, true)  // smoothScroll = true
        } else {
            viewPager.setCurrentItem(0, true)     // loop back to the start
        }
        mainHandler.postDelayed(this, INTERVAL_MS)
    }
}
 
fun startSlideshow() {
    mainHandler.removeCallbacks(advanceRunnable)
    mainHandler.postDelayed(advanceRunnable, INTERVAL_MS)
}
 
fun stopSlideshow() {
    mainHandler.removeCallbacks(advanceRunnable)
}

The detail that mattered was catching "tap to stop" through ViewPager2.OnPageChangeCallback#onPageScrollStateChanged rather than onTouchEvent. When the user swipes manually, I want auto-advance and manual scroll never to fight each other, so I call stopSlideshow() the moment SCROLL_STATE_DRAGGING is observed. My first attempt only listened for taps, and it produced a bug where inertial scrolling kept advancing pages after the user had clearly tried to stop the show.

Page jump as a two-layer SeekBar with a lightweight preview

I wanted users to reach a specific image out of a hundred thousand with a single slider. The naive approach—calling viewPager.setCurrentItem(progress, false) straight from OnSeekBarChangeListener—runs layout every frame during the drag and stutters badly.

seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
    override fun onProgressChanged(sb: SeekBar?, progress: Int, fromUser: Boolean) {
        if (!fromUser) return
        previewLabel.text = "${progress + 1} / ${adapter.itemCount}"  // lightweight preview
    }
    override fun onStartTrackingTouch(sb: SeekBar?) {
        stopSlideshow()  // pause auto-advance while the user is scrubbing
    }
    override fun onStopTrackingTouch(sb: SeekBar?) {
        viewPager.setCurrentItem(sb?.progress ?: 0, false)  // commit only on release
    }
})

While the user drags, I only update a text label that reads "current / total." The real setCurrentItem call happens once, the moment the finger lifts. Even at a hundred-thousand-item scale the scrub stays smooth, and Glide's cancellation and reload only fire once at the final destination, which is friendlier to both memory and bandwidth.

How this interacts with the ad-free option

The "let me hide the ads" request has come in for years, so I bundled it into this release. Internally, the ad-free state is the OR of two independent sources of truth: permanent purchase or subscription on one side, and a time-bounded ad-free state earned by watching a rewarded ad on the other. The combined value is exposed as Globals.SHOW_AD. During an active slideshow, an interstitial would shatter the experience, so I added a guard that suppresses background interstitial calls while auto-advance is running.

A while back I had ad gating logic nested inside other gating logic, and the implicit priority order made debugging genuinely painful. Learning from that, I now keep the slideshow flag, ad-free flag, and ad counters as parallel, independent variables. When the state machine is flat, you can later trace "who suppressed whom" purely from logs.

Lock, rotation, and background transitions

The unglamorous fix that mattered most was: always call stopSlideshow() in onPause, and never auto-resume in onResume. If the slideshow keeps running while the device is locked, the user unlocks to find that several images have silently flown past.

For rotation, I save both the current page index and the "slideshow is running" flag in a ViewModel, and double up with onSaveInstanceState. Carrying both feels redundant, but at the cumulative download scale my wallpaper apps operate at—north of fifty million across the family—you see both clean configuration changes and full-Activity crash recoveries with non-trivial frequency, so the belt-and-suspenders setup earns its keep.

What I am still working on

The next items on my list are letting users adjust the slideshow interval from the Settings screen, adding a shortcut that sets the wallpaper directly from the lock screen, and back-porting the same machinery to the iOS edition. I grew up around grandfathers who were temple carpenters and treated "moving your hands" as a form of devotion, and that has stayed with me—I am still not at peace with some of the original release decisions in these apps. This update is one of the steps I am taking, slowly, to tidy them up.

If anything feels off while you use it, please leave a note in the Google Play review and I will reply by hand. There are behaviors only real devices reveal, and I plan to keep responding one review at a time. Thank you for reading.

Share

Thank You for Reading

Rork 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 →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

App Dev2026-05-29
Two Weeks of Maintenance After v2.1.0 — Running Crashlytics Triage Through Claude in Chrome
Notes from operating Beautiful 4K/HDR Wallpapers v2.1.0 and Ukiyo-e Wallpapers v1.8.0 through phased rollout. Defensive RecyclerView copies, Glide desugaring, drawable-nodpi placement, and what I now hand off to Claude in Chrome every morning.
AI Models2026-05-20
A month of running Rork and Claude on Xcode side by side — where each one ended up fitting
Notes from an indie developer who has shipped wallpaper apps on iOS and Android since 2014. After a month of keeping both Rork and Claude on Xcode open on the same desk, the work that fit each one turned out to be more obvious than I expected.
App Dev2026-07-14
Long-Press Context Menus for a Gallery Item in a Rork Expo App
Long-pressing a wallpaper card does nothing, yet iOS users expect a preview and a menu. From why Pressable alone falls short, to a native context menu with zeego, resolving the scroll-vs-long-press conflict, wiring up save and share, and a custom overlay fallback for Android — all with working code.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →