By Faiz Ullah — Android Developer & Founder of DG Technology
A wallpaper app sounds simple: fetch images, show a grid, let the user tap "set wallpaper." I built exactly that — WallReddit, a native Android app pulling images from Reddit — and used it as an excuse to do something I'd wanted to get right for a while: a genuinely clean, layered architecture instead of the "everything in one Activity" approach most small apps fall into.
Here's what building it taught me.
Why Bother With Clean Architecture for "Just a Wallpaper App"
It's tempting to skip architecture for a small app. But the moment you add offline favorites, a download manager, NSFW filtering, and a background auto-rotation feature, an unstructured app turns into a maze of interdependent code that's terrifying to touch.
So I split the app into three layers with one hard rule: dependencies only point inward.
ui/ → talks to → domain/ → talks to → data/
(Compose) (use cases, (Retrofit, Room,
pure Kotlin) DataStore)
The payoff: the domain layer has zero Android imports. No Context, no Activity, nothing Android-specific — just plain Kotlin business logic. That means it's trivially testable and would survive a total UI rewrite untouched.
Reactive Settings Without the Boilerplate
Every setting in the app — dark mode, AMOLED mode, grid density, NSFW visibility — needed to update the UI instantly when changed, from anywhere in the app. Instead of manually plumbing callbacks everywhere, I exposed every preference as a Kotlin Flow backed by Jetpack DataStore:
val isDarkMode: Flow<Boolean> = dataStore.data.map { it[DARK_MODE_KEY] ?: false }
Any screen can collectAsState() on this and the UI updates the instant the value changes anywhere else in the app — no event bus, no manual notification, no stale state.
Fighting Reddit's Anti-Scraping Defenses
Reddit's public JSON endpoints are great until you hit their CDN's bot protection — a default Retrofit client gets 403 Forbidden almost immediately. The fix is straightforward but easy to miss: send a real browser User-Agent and proper timeout/retry configuration:
OkHttpClient.Builder()
.addInterceptor { chain ->
chain.proceed(chain.request().newBuilder()
.header("User-Agent", "WallReddit/1.0 (Android)")
.build())
}
It's a one-line fix, but without it the entire app silently fails to load any content — the kind of bug that's invisible until you actually test against the real network.
Wallpapers Look Wrong Without Real Math
The naive way to "set a wallpaper" is to just hand the bitmap to Android's WallpaperManager and hope for the best. The result: stretched, cropped, or letterboxed images depending on the device's aspect ratio.
The actual fix is computing the transform yourself before handing off the bitmap — comparing the image's aspect ratio to the screen's, then applying a Matrix for center-crop or fit-to-screen:
val scale = max(screenWidth / bitmapWidth, screenHeight / bitmapHeight)
matrix.setScale(scale, scale)
This is the difference between an app that looks like an amateur project and one that looks production-grade — and it's invisible work nobody notices unless you get it wrong.
Background Work That Doesn't Drain the Battery
Auto-rotating wallpapers on a schedule means running work when the app isn't even open. Android's background execution limits make naive approaches (a Service that just runs forever) both unreliable and battery-hostile.
WorkManager is the right tool here specifically because it respects Doze mode and battery optimization automatically — your job runs reliably without you having to reinvent Android's power management:
val request = PeriodicWorkRequestBuilder<AutoWallpaperWorker>(interval, TimeUnit.HOURS)
.setConstraints(Constraints.Builder().setRequiresBatteryNotLow(true).build())
.build()
What I'd Tell Someone Building Their Second Android App
- Keep your domain layer Android-free. It's the single highest-leverage architectural decision you can make.
- Expose settings as reactive streams, not getter functions. Future-you will thank present-you.
- Test your networking against the real API, not a mock — anti-bot measures only show up in production traffic.
-
Use
WorkManagerfor anything recurring. Don't fight the OS's power management; work with it.
The Stack
| Layer | Technology |
|---|---|
| Language | Kotlin |
| UI | Jetpack Compose, Material 3 |
| Architecture | MVVM + Clean Architecture |
| Networking | Retrofit + OkHttp |
| Local storage | Room, DataStore |
| Pagination | Paging 3 |
| Background work | WorkManager |
| Images/Video | Coil, Media3 |
Faiz Ullah
Android Developer · Full-Stack Engineer · Founder of DG Technology
🌐 faizullah.pk · 💻 github.com/faizullahpk/wallpaper-app-android
Building something on Android and hitting architecture questions? Follow along — I write about real-world mobile and full-stack engineering.
Top comments (0)