DEV Community

Cover image for My MainActivity Was 700 Lines Long. That's When I Finally Learned MVVM.
Aalaa Fahiem
Aalaa Fahiem

Posted on

My MainActivity Was 700 Lines Long. That's When I Finally Learned MVVM.

I'm not even exaggerating with that number. 700 lines. One file. One class. Every single thing my app did — API calls, click listeners, UI updates, data formatting — all crammed into MainActivity.kt like a suitcase someone sat on to close.

It worked. Until it didn't.

The day it fell apart was when I tried to add a simple line of Code. I ended up breaking the list, the error message, and somehow the back button. I spent four hours fixing something that should've taken ten minutes.

That's when I finally stopped avoiding the word I'd been scared of since I started: architecture.


Why I Ignored It For So Long

Honestly? The word itself put me off.

Architecture. It sounds like something senior engineers debate in long meetings. Not something a beginner building their first to-do app needs to worry about.

So I kept ignoring it. Kept piling more code into my Activity. Kept telling myself I'd "refactor later."

Later never came. The code just got worse.

If you're in that same place right now — I get it. But let me tell you what nobody told me: MVVM isn't complicated. It's actually just a way of answering one question —

Who is responsible for what?

That's it. Seriously.


The Analogy That Made It Click For Me

Forget code for a second. Think about a restaurant.

When you go out to eat, there are three people involved in getting food to your table:

  • 🍽️ The waiter takes your order and brings your food. They don't cook anything. They don't decide what's on the menu. They just deal with you.
  • 👨‍🍳 The chef does all the actual work. Takes the order from the waiter, figures out how to prepare it, sends it back out. Never comes to your table.
  • 📦 The stockroom has all the ingredients. The chef goes there to get what they need. You never see it. The waiter definitely doesn't go back there.

Three people. Three jobs. Nobody steps on anyone else's toes.

MVVM is exactly this:

  • Your View (Activity, Fragment, Composable) = the waiter. Shows things to the user. Reacts to what the user does. That's all.
  • Your ViewModel = the chef. Holds the logic. Decides what data to use and when. Never touches the UI directly.
  • Your Model (Repository, database, API) = the stockroom. Just holds and provides data. Doesn't care who's asking or why.

When I understood this, everything else fell into place.


What My Code Looked Like Before (Please Don't Judge Me)

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // calling the API straight from the Activity 💀
        CoroutineScope(Dispatchers.IO).launch {
            val users = RetrofitClient.api.getUsers()
            withContext(Dispatchers.Main) {
                adapter.submitList(users)
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This looks innocent. It's not.

Rotate the phone → Activity gets destroyed → coroutine gets cancelled
→ the API gets called again from scratch. Every. Single. Rotation.

Also, try writing a test for this. You can't. The networking is baked directly into the UI layer. There's no way to test the logic without spinning up the whole Activity.

I didn't even know this was a problem until it bit me.


What It Looks Like After

Here's the same feature split across three layers the right way:

The Model — just fetches data, nothing else

class UserRepository(private val api: ApiService) {
    suspend fun getUsers(): List<User> = api.getUsers()
}
Enter fullscreen mode Exit fullscreen mode

The ViewModel — holds the logic and the state

class UserViewModel(private val repository: UserRepository) : ViewModel() {

    private val _users = MutableStateFlow<List<User>>(emptyList())
    val users: StateFlow<List<User>> = _users

    init { loadUsers() }

    private fun loadUsers() {
        viewModelScope.launch {
            _users.value = repository.getUsers()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The View — just watches and reacts

class MainActivity : AppCompatActivity() {

    private val viewModel: UserViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        lifecycleScope.launch {
            viewModel.users.collect { users ->
                adapter.submitList(users)
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Three files. Each one does one thing. The Activity has no idea how users are fetched. The ViewModel has no idea how they're displayed. And if I rotate the phone? The ViewModel is still alive — data doesn't reload, nothing breaks.

The One Thing I Wish Someone Had Told Me

The ViewModel isn't just an organizational trick. It has an actual superpower that nothing else gives you:

It survives configuration changes.

When you rotate your phone, Android destroys and recreates your Activity. This is just how Android works — it's not a bug. But if your data lives inside the Activity, it dies with it.

A ViewModel doesn't. It stays alive across rotations, theme changes, keyboard toggles — anything that would normally kill your Activity. When the Activity comes back, it reconnects to the same ViewModel that was already there, with all its data intact.

That alone is worth learning MVVM for.


The Honest Truth About Getting Started

You're not going to write perfect MVVM on your first try. I didn't. My first "MVVM" app still had logic leaking into the Activity in places. I used LiveData wrong. My Repository was doing things it shouldn't.

That's fine.

The goal isn't perfection. The goal is to stop putting everything in one place. Even a messy, imperfect separation of concerns is infinitely better than a 700-line Activity.

Start on your next project. Even a small one. Just ask yourself before writing anything: does this belong in the View, the ViewModel, or the Model?

That question alone will change how you write code.

Top comments (0)