Marvel at the sheer scale of the Universe with the convenience of your favorite doomscrolling app. Built with Compose Multiplatform, powered by Astrobin.
feed_mobile.mp4
liked_mobile.mp4
- Fancy Tinder-like UI
- Offline-first approach
- Flexible and scalable implementation of Clean Architecture with minimal boilerplate
- Custom paging solution (because Paging3 is not great!)
- Custom MVI-focused ViewModel (the default one is fine — I just wanted to try building my own!)
- Full Android support, basic desktop support (usable but lacks desktop-specific interactions)
It's an implementation of Clean Architecture, modularized by feature. The project is divided into feature (UI) and component (domain, data, data source) modules. ViewModels can be injected with repository interfaces located in the domain directly, avoiding useless use case wrappers around repository methods when they are not necessary.
- The “Real” Modularization in Android by Denis Brandi
- The “Real” Clean Architecture in Android: S.O.L.I.D. by Denis Brandi
- How To Avoid Use Cases Boilerplate in Android by Denis Brandi (I respectfully disagree with the conclusion, but the article is still great)
- The Wrong Abstraction by Sandi Metz
- An in-depth discussion on SOLID and Clean Architecture in the Now In Android project
A custom MviViewModel ensures all work is paused when the app is in the background, preventing unnecessary battery drain when polling for geolocation data, remote data sources, etc.
The first rule about Paging3: dont use Paging3
(C) Multiple users on Reddit, aka unexpectedly the only place to offer info on Paging3 beyond the limited official doc.
I've built a custom paging solution that supports multiple data sources, offline mode, real-time updates, and both key- and page-based paging. It still has some TODOs, and I wouldn’t claim it's completely fail-safe, but it should handle most cases you throw at it. For me, it's vastly more manageable than Paging3.
This solution is available as a separate module, pager
, in the project. Feel free to copy and
tweak it to your liking, or use it as inspiration for your own implementation.
Errors that we expect and should handle are represented as a sealed interface hierarchy, wrapped at the data source level. Other modules are exception-free.
I got lazy, so no tests!
I have opinions on testing though. Kotest allows for more readable and concise tests, but historically, its runner has always had issues with Android. To my dismay, this still holds true even for multiplatform modules targeting Android as one of the platforms, so I’d avoid it for now. Instead, I would use Junit5 with just Kotest Assertions.
I would prioritize testing ViewModels with faked data, so that a test encompasses a ViewModel, a
UseCase, a Repository, and potentially even a data source — depending on how you mock them. (For
example, using a manually pre-populated in-memory database for SQLDelight and MockEngine
for Ktor
Client.) I would consider sprinkling in some screenshot tests. Everything else should ideally be
tested via automated UI tests with no mocks at all.
- Mocking isn't evil, but avoid it anyway by Evan Nelson
- When to Mock by Uncle Bob
- The Unit Testing Diet: Start with BDD and Do Not Mock by Stelios Frantzeskakis
This project uses ktlint, integrated via
the spotless gradle plugin, and a custom code style.
Installing the ktlint extension for your IDE is
the easiest way to use ktlint. Optionally, you can enable the pre-commit git hook (
~/spotless/pre-commit
). Installation instructions are inside the file.
Get your Astrobin API key and secret here and paste
them into ~/.secrets.properties
:
ASTROBIN_API_KEY=[...]
ASTROBIN_API_SECRET=[...]
- Astrobin for serving as the backend for this app
- Denis Brandi for awesome articles on Clean Architecture
- Quantum fluctuations for originating the large-scale structure of the Universe by introducing a non-uniformity in energy distribution during cosmic inflation
Distributed under the MIT License. See LICENSE for more information.