“Loading state from the repository layer? Tell me you’re a junior without telling me you’re a junior.”
Someone once said that on Reddit. And honestly? That kind of thinking is what’s junior.
In tech, especially in architecture discussions, it’s easy to fall into dogma. But calling patterns “junior” without context isn’t just dismissive, it shuts down meaningful conversations. So let’s have one.
My goal here isn’t to “clap back”, it’s to clarify why emitting loading state from the repository is not only valid, but often the cleaner and more scalable choice. Especially if you’re working in Kotlin Multiplatform or modern Android architectures.
What Is “Emitting Loading from the Repository”?
What is a Repository?
In modern app architecture, a repository acts as an abstraction layer between your data sources (like network APIs, local databases, or disk caches) and the rest of your app (such as ViewModels or Presenters). Its purpose is to provide a clean, consistent interface for fetching and storing data, so the core of your app doesn’t need to know—or care—where the data actually comes from.
Think of a repository as the single source of truth for requesting application data. By centralizing how data is fetched, cached, or combined, you isolate business logic from both the presentation layer and low-level data source implementations.
Emitting Loading State from the Repository
In simple terms:
Your repository exposes a Flow<Resource<T>>
, where Resource
can be .Loading
, .Success(data)
, or .Error(e)
.
It doesn’t know how things look on screen. It just emits what’s happening during the lifecycle of a request.
Example: Defining the Resource wrapper and the repository pattern:
sealed class Resource<T> {
class Loading<T>: Resource<T>()
data class Success<T>(val data: T): Resource<T>()
data class Error<T>(val throwable: Throwable): Resource<T>()
}
In your repository:
fun getUser(id: String): Flow<Resource<User>> = flow {
emit(Resource.Loading())
try {
val user = api.getUser(id)
emit(Resource.Success(user))
} catch (e: Exception) {
emit(Resource.Error(e))
}
}
If you’re okay with exposing
.Success
or.Error
, but not.Loading
, then what you’re really doing is cherry-picking what you call “UI logic”.But all three represent the lifecycle of a call, not how it’s rendered. The UI doesn’t create that state, it reacts to it. And that distinction matters.
With that logic, you might as well start making network calls directly from the ViewModel.
Why This Pattern Is Clean and Modern
🧠 Separation of concerns
The ViewModel doesn’t control the timing, it simply reacts. No coupling between networking and presentation logic.
💡 Predictability
Every data stream, cached or remote, behaves the same. Your UI layer becomes lean and composable.
🧪 Testability
You can unit test your repository without caring about UI states. You can test your ViewModel without mocking timers or guessing when to emit loading.
🔁 Reusability
Multiple ViewModels or platforms (iOS, Android, etc.) can use the same repository logic with consistent behavior.
🌐 Perfect for Kotlin Multiplatform
KMP modules shouldn’t care about the UI. They care about business logic, and lifecycle state is part of that logic.
Alternatives & Their Drawbacks
Let’s consider a typical “emit loading in the ViewModel” approach:
fun fetchData() {
_loadingState.value = true
viewModelScope.launch {
try {
val data = repository.fetch()
_data.value = data
} finally {
_loadingState.value = false
}
}
}
→ Now every call has boilerplate code. You risk inconsistency.
-
Is UI guessing when something is loading?
→ Brittle, harder to test, and often requires local hacks like
isLoading = true
. -
Parsing and managing state in ViewModel?
→ Breaks Single Responsibility Principle. The ViewModel now does too much.
This Isn’t Just an Opinion, It’s a Practice
Plenty of experienced devs and teams use this pattern.
In Clean Architecture. In MVI setups. In Redux-style unidirectional flows. In production systems.
I’ve used this in Kotlin Multiplatform libraries, client apps, and scalable architectures across teams.
Not once did this pattern make our system less maintainable, only more.
This isn’t a junior move.
It’s a practiced one.
Final Thought
Respectful disagreement leads to growth.
Calling something “junior” without understanding it… that just signals ego, not expertise.
There are many good ways to build software.
This is one of them.
“The moment you dismiss something before trying to understand it, you stop learning.”
👋 If you’ve made it this far, I’d love to hear your thoughts, whether you agree, disagree, or have another perspective. Let’s keep the conversation respectful and constructive, that’s how we grow as developers.