All about that architecture: exploring different architecture patterns and how to use them in your app

As an Android developer, one of the most important decisions you‘ll make when starting a new project is which architecture pattern to use. Getting your app architecture right is crucial for building a robust, maintainable, and testable codebase that can scale and evolve with changing requirements. A poorly architected Android app becomes exponentially more difficult and time-consuming to work with as it grows.

While there are many factors to consider such as the size and scope of the project, your team‘s skills and preferences, and the app‘s performance and UX goals, ultimately your architecture should help you achieve two key principles:

  1. Separation of concerns – Divide your code into distinct sections each with clear responsibilities. UI logic should be separate from business logic and data logic.

  2. Loose coupling – Different parts of your app should depend on each other as little as possible. Changes in one area shouldn‘t require changes in unrelated areas.

There are several established architecture patterns used in Android development that aim to meet these goals. Let‘s take a closer look at the most common ones and explore how they apply to modern Android apps.

The Big Three: MVC vs MVP vs MVVM

The three predominant architecture patterns discussed in the Android community are:

  • Model-View-Controller (MVC)
  • Model-View-Presenter (MVP)
  • Model-View-ViewModel (MVVM)

They all share some common concepts and are based on the fundamental premise of separating the UI from the underlying data and business logic. However, they differ in how they assign responsibilities to classes and how the different components communicate with each other.

MVC architecture

MVC is the oldest of the three patterns, originating in desktop software development long before Android existed. In MVC:

  • Model represents the data and business logic
  • View displays the UI and receives user input
  • Controller accepts input and converts it to commands for the model or view

In theory, MVC provides a nice separation of concerns. The model is completely UI-independent, so it can be tested without any knowledge of the user interface. The controller handles data flow between the view and model.

However, the boundary between what counts as a controller or view is not well-defined in the context of Android. Is an Activity or Fragment a view, a controller, or both? Attempting to shoehorn Android components into strict MVC roles often results in a convoluted architecture where views and controllers are tightly coupled.

In practice, a lot of business logic ends up in the views, making them bloated and hard to test. Or alternatively, all the app logic is placed in the controller, creating a "mega-controller" that is difficult to maintain.

For these reasons, while you may encounter MVC in older Android projects, it has largely fallen out of favor with the Android community in recent years. Most developers have moved on to patterns that are better suited to the Android framework.

MVP architecture

MVP builds upon the concepts of MVC but aims to provide a clearer separation between the view and the model. The key difference is the introduction of the Presenter, which retrieves data from the model and formats it for display in the view.

Here‘s a brief overview of the components in MVP:

  • Model – Data source (database, network, etc) and any business logic
  • View – Passive interface that displays data and routes user commands to the presenter
  • Presenter – Retrieves data from model and prepares it for display in the view

The view and presenter are connected through an interface, which allows them to communicate without depending directly on each other. This makes the view more passive and easier to unit test.

One common criticism of MVP is that the presenter can become overly complex as it handles all the view logic that isn‘t directly related to drawing on the screen. Moving formatting and data manipulation code into the presenter often results in a "fat presenter" with low cohesion.

However, with clear boundaries and single responsibility principles in mind, MVP remains a solid choice for many Android apps. It has been widely adopted by the Android community and is well-supported by third-party libraries and frameworks.

MVVM architecture

MVVM has gained popularity in recent years as the recommended architecture pattern for Android development by Google. It leverages the data binding support in Android to provide a clean separation between the UI and business logic.

In MVVM, the components are:

  • Model – Represents the data used by the app, either from local storage or remote sources
  • View – Defines the UI layout and structure
  • ViewModel – Holds the UI data and state, survives configuration changes

The key idea is that the view observes changes to the data in the view model through data binding. Whenever the data changes, the UI is automatically updated to reflect the current app state.

Some advantages of MVVM are:

  • Views are very lightweight, since they don‘t contain any logic
  • ViewModels are easy to unit test as they have no direct dependency on the view
  • Less boilerplate than MVP since interfaces aren‘t needed
  • Decouples view and model without a presenter intermediary

While MVVM has many benefits, it does introduce additional complexity, especially when used with a reactive framework like RxJava. Coordinating multiple asynchronous data streams in the view model requires careful implementation to avoid race conditions and keep the UI consistent.

MVVM is the architecture pattern that Google is basing its official Android Architecture Components around. Let‘s take a closer look at these tools and see how they simplify the process of implementing an MVVM architecture.

Android Architecture Components

To help developers deal with common Android challenges like managing the complex lifecycle of activities and fragments, handling asynchronous data streams, and persisting UI state across configuration changes, Google introduced Android Architecture Components.

Architecture Components is a collection of libraries for structuring your app in a way that is robust, testable, and maintainable. The main components are:

  • Lifecycle – Manages activity and fragment lifecycles
  • LiveData – Observable data holder that notifies views of changes
  • ViewModel – Holds view data and survives configuration changes
  • Room – Abstraction layer over SQLite

Together these components make it much easier to implement the MVVM pattern in an Android app. ViewModels survive rotation and can easily hold LiveData objects, which can be observed by the UI. Room provides a simple way to persist the data.

Here‘s an overview of how the Architecture Components work together in a MVVM architecture:

By using Architecture Components, a lot of the boilerplate and tricky lifecycle management code is handled for you. This frees you up to focus on the unique aspects of your app.

It‘s important to note that you don‘t have to use all of the Architecture Components if you don‘t want to. For example, you can use ViewModel and LiveData without Room if you have a different strategy for storing your data.

Organizing your Android project

Regardless of which architecture pattern you choose, it‘s critical to organize your project‘s source files in a logical and consistent way. As your codebase grows, proper organization helps with navigation, maintainability, and readability.

There are two common approaches:

  1. Organize by layer – All activities in one folder, all models in another, etc.
  2. Organize by feature – Related classes grouped by their feature or screen


In general, organizing by feature scales better for large projects. It keeps related things close to each other and makes it easier to modularize the codebase and create reusable components.

Within each feature package, you can then organize classes into their respective architectural roles. For example, a "checkout" feature may have:

  • CheckoutActivity
  • CheckoutViewModel
  • CheckoutFragment
  • PaymentMethodModel

If you‘re using MVVM with Architecture Components, Google recommends creating a UI package for your view-related classes and a data package for your models, repositories, and data access objects.

Implementing MVVM with Architecture Components

Now let‘s walk through the steps to implement an MVVM architecture in an Android app using the Architecture Components.

  1. Add the Architecture Component dependencies to your gradle file:
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"  
// Room 
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
  1. Define your data model:
@Entity(tableName = "users")
data class User (
    @PrimaryKey val id: Int,
    val firstName: String,
    val lastName: String,
    val age: Int
)
  1. Create a Room database:
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}
  1. Define a DAO interface for accessing the data:
@Dao 
interface UserDao {
    @Query("SELECT * FROM users")
    fun getAll(): LiveData<List<User>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertUsers(vararg users: User)
}
  1. Create a repository to manage the data:
class UserRepository(private val userDao: UserDao) {

    val allUsers: LiveData<List<User>> = userDao.getAll()

    fun insertUsers(vararg users: User) {
        userDao.insertUsers(*users)
    }
}
  1. Define a ViewModel to handle the UI data:
class UserViewModel(private val userRepository: UserRepository) : ViewModel() {

    val allUsers: LiveData<List<User>> = userRepository.allUsers

    fun addUser(user: User) = viewModelScope.launch {
        userRepository.insertUsers(user)
    }
}
  1. Use the ViewModel in your UI controller:
class UserActivity : AppCompatActivity() {

    private lateinit var userViewModel: UserViewModel

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

        userViewModel = ViewModelProvider(this).get(UserViewModel::class.java)

        userViewModel.allUsers.observe(this, Observer { users -> 
            // Update UI
        })

        fab.setOnClickListener {
            userViewModel.addUser(User(...))
        }
    }
}

With this structure in place, the user interface will automatically update whenever the underlying data changes. The UI controller doesn‘t need to worry about managing the lifecycle or data persistence.

Of course, this is a simplified example and real-world apps will be more complex. But it demonstrates the basic steps and concepts involved in implementing an MVVM architecture using the Android Architecture Components.

Alternative architecture patterns

While this article focused on the three most established architecture patterns for Android, there are other approaches and variations worth considering depending on your app‘s specific needs:

  • MVI (Model-View-Intent) – An evolution of MVP that treats the UI as a state machine
  • VIPER (View, Interactor, Presenter, Entity, Router) – A more complex clean architecture pattern
  • Hexagonal architecture – Puts the business logic at the center, with the UI and data on the outsides

Ultimately, the "right" architecture is the one that allows you to write maintainable, testable code and to collaborate with your team effectively. It‘s helpful to learn from best practices, but don‘t be afraid to adapt them to your own situation.

Wrap-up

By now you should have a solid understanding of the different architecture patterns used in Android development, and the considerations that go into choosing between them.

Google‘s recommended approach of using the MVVM pattern with their Architecture Components is a good default choice for most new Android projects. With the addition of libraries like Room and LiveData, boilerplate is reduced and lifecycle management is simplified.

However, the most important thing is to be consistent and to structure your app in a logical way with clear separation of concerns. Discuss the architecture with your team up front, agree on conventions, and document your approach.

A solid architecture lays the foundation for a successful Android app. Taking the time to consider your options and following Android architecture best practices will pay off in the form of more robust, maintainable code. Happy coding!

Similar Posts