Convert Figma logo to code with AI

bufferapp logoandroid-clean-architecture-boilerplate

An android boilerplate project using clean architecture

3,656
519
3,656
28

Top Related Projects

This is a sample app that is part of a series of blog posts I have written about how to architect an android application using Uncle Bob's clean architecture approach.

A collection of samples to discuss and showcase different architectural tools and patterns for Android apps.

This repository contains a detailed sample app that implements MVVM architecture using Dagger2, Room, RxJava2, FastAndroidNetworking and PlaceholderView

Do's and Don'ts for Android development, by Futurice developers

Quick Overview

The bufferapp/android-clean-architecture-boilerplate is a sample Android project that demonstrates the implementation of Clean Architecture principles in Android app development. It serves as a template for building scalable, maintainable, and testable Android applications using modern architectural patterns and libraries.

Pros

  • Implements Clean Architecture, promoting separation of concerns and modularity
  • Uses modern Android development tools and libraries (e.g., Kotlin, Coroutines, Dagger Hilt)
  • Provides a solid foundation for building large-scale Android applications
  • Includes comprehensive unit and integration tests

Cons

  • May have a steep learning curve for developers unfamiliar with Clean Architecture
  • The boilerplate structure might be overkill for small or simple projects
  • Requires additional setup and configuration compared to a basic Android project
  • Some parts of the project may become outdated as Android development practices evolve

Code Examples

  1. Domain layer - Use case implementation:
class GetBookmarkedProjects @Inject constructor(
    private val projectsRepository: ProjectsRepository
) : UseCase<List<Project>, Nothing?>() {

    override suspend fun run(params: Nothing?): Either<Failure, List<Project>> {
        return projectsRepository.getBookmarkedProjects()
    }
}
  1. Data layer - Repository implementation:
class ProjectsDataRepository @Inject constructor(
    private val mapper: ProjectMapper,
    private val store: ProjectsDataStore
) : ProjectsRepository {

    override suspend fun getProjects(): Either<Failure, List<Project>> {
        return try {
            val projects = store.getProjects().map { mapper.mapFromEntity(it) }
            Either.Right(projects)
        } catch (exception: Exception) {
            Either.Left(Failure.ServerError)
        }
    }
}
  1. Presentation layer - ViewModel implementation:
@HiltViewModel
class BrowseProjectsViewModel @Inject constructor(
    private val getProjects: GetProjects
) : ViewModel() {

    private val _state = MutableStateFlow<BrowseProjectsViewState>(BrowseProjectsViewState.Loading)
    val state: StateFlow<BrowseProjectsViewState> = _state.asStateFlow()

    fun getProjects() {
        viewModelScope.launch {
            _state.value = BrowseProjectsViewState.Loading
            getProjects(None()).fold(
                { failure -> _state.value = BrowseProjectsViewState.Error(failure) },
                { projects -> _state.value = BrowseProjectsViewState.Data(projects) }
            )
        }
    }
}

Getting Started

  1. Clone the repository:

    git clone https://github.com/bufferapp/android-clean-architecture-boilerplate.git
    
  2. Open the project in Android Studio.

  3. Build and run the app on an emulator or physical device.

  4. Explore the codebase to understand the Clean Architecture implementation.

  5. Modify and extend the project structure to fit your specific app requirements.

Competitor Comparisons

This is a sample app that is part of a series of blog posts I have written about how to architect an android application using Uncle Bob's clean architecture approach.

Pros of Android-CleanArchitecture

  • More comprehensive implementation of Clean Architecture principles
  • Includes detailed documentation and explanations of architectural decisions
  • Provides a more robust example with real-world use cases

Cons of Android-CleanArchitecture

  • Slightly more complex structure, which may be overwhelming for beginners
  • Less focus on modern Android development practices like Jetpack Compose
  • Older project with less frequent updates

Code Comparison

Android-CleanArchitecture:

class GetUserDetails @Inject constructor(
    private val userRepository: UserRepository
) : UseCase<GetUserDetails.Params, UserEntity>() {

    override suspend fun run(params: Params) = userRepository.user(params.userId)

    data class Params(val userId: Int)
}

android-clean-architecture-boilerplate:

class GetUser @Inject constructor(
    private val userRepository: UserRepository
) : SingleUseCase<User, GetUser.Params>() {

    override fun buildUseCaseObservable(params: Params): Single<User> {
        return userRepository.getUser(params.userId)
    }

    data class Params(val userId: String)
}

The code comparison shows that both projects implement similar use case structures, but Android-CleanArchitecture uses suspend functions and coroutines, while android-clean-architecture-boilerplate relies on RxJava's Single for asynchronous operations. This reflects the different approaches to handling asynchronous tasks in each project.

A collection of samples to discuss and showcase different architectural tools and patterns for Android apps.

Pros of architecture-samples

  • Official Google sample, regularly updated with latest Android development practices
  • Demonstrates multiple architectural approaches (MVI, MVVM, etc.)
  • Includes Jetpack Compose examples alongside XML-based UI

Cons of architecture-samples

  • May be overwhelming for beginners due to multiple architecture examples
  • Less focused on a single, opinionated clean architecture implementation
  • Doesn't include as many real-world use cases as android-clean-architecture-boilerplate

Code Comparison

architecture-samples (MVVM example):

@HiltViewModel
class TasksViewModel @Inject constructor(
    private val tasksRepository: TasksRepository
) : ViewModel() {
    private val _forceUpdate = MutableLiveData(false)
    val tasks: LiveData<Result<List<Task>>> = _forceUpdate.switchMap { forceUpdate ->
        if (forceUpdate) {
            _dataLoading.value = true
            viewModelScope.launch {
                tasksRepository.refreshTasks()
                _dataLoading.value = false
            }
        }
        tasksRepository.observeTasks().switchMap { filterTasks(it) }
    }
}

android-clean-architecture-boilerplate:

class GetBufferProjects @Inject constructor(
    private val bufferProjectsRepository: BufferProjectsRepository
) : SingleUseCase<List<BufferProject>, Void?>() {

    public override fun buildUseCaseObservable(params: Void?): Single<List<BufferProject>> {
        return bufferProjectsRepository.getBufferProjects()
    }
}

The code snippets show different approaches to handling data and business logic, with architecture-samples using a ViewModel and android-clean-architecture-boilerplate using a UseCase pattern.

This repository contains a detailed sample app that implements MVVM architecture using Dagger2, Room, RxJava2, FastAndroidNetworking and PlaceholderView

Pros of android-mvvm-architecture

  • Simpler architecture, easier for beginners to understand and implement
  • Includes more comprehensive examples of common Android app features
  • Better documentation and explanations of architectural decisions

Cons of android-mvvm-architecture

  • Less emphasis on clean architecture principles
  • May not scale as well for larger, more complex applications
  • Fewer abstraction layers, potentially leading to tighter coupling

Code Comparison

android-mvvm-architecture:

class MainViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {
    private val _user = MutableLiveData<User>()
    val user: LiveData<User> = _user
}

android-clean-architecture-boilerplate:

class GetUser @Inject constructor(
    private val userRepository: UserRepository
) : UseCase<GetUser.Params, User>() {
    override suspend fun run(params: Params) = userRepository.getUser(params.userId)
    data class Params(val userId: String)
}

The android-mvvm-architecture example shows a simpler ViewModel implementation, while the android-clean-architecture-boilerplate demonstrates a more abstracted UseCase approach, adhering to clean architecture principles.

Both repositories offer valuable insights into Android app architecture, with android-mvvm-architecture being more accessible for beginners and android-clean-architecture-boilerplate providing a more scalable, clean architecture approach for complex applications.

Do's and Don'ts for Android development, by Futurice developers

Pros of android-best-practices

  • Comprehensive guide covering various aspects of Android development
  • Regularly updated with community contributions
  • Includes best practices for both development and project management

Cons of android-best-practices

  • Lacks a concrete implementation or boilerplate code
  • May require more effort to implement in a new project
  • Some practices might be opinionated or not suitable for all projects

Code comparison

android-clean-architecture-boilerplate:

class GetBufferoos @Inject constructor(
    private val bufferooRepository: BufferooRepository,
    postExecutionThread: PostExecutionThread
) : UseCase<List<Bufferoo>, Void?>(postExecutionThread) {

    public override fun buildUseCaseObservable(params: Void?): Observable<List<Bufferoo>> {
        return bufferooRepository.getBufferoos()
    }
}

android-best-practices:

// No direct code implementation, but provides guidelines like:
class MyViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()

    fun loadData() {
        viewModelScope.launch {
            // Load data and update _uiState
        }
    }
}

The android-clean-architecture-boilerplate provides a concrete implementation of clean architecture, while android-best-practices offers guidelines and best practices without specific code implementations. The choice between them depends on whether you need a ready-to-use boilerplate or prefer a set of guidelines to implement in your own way.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

Build Status codecov Codacy Badge

Android Clean Architecture Boilerplate

Welcome 👋 We hope this boilerplate is not only helpful to other developers, but also that it helps to educate in the area of architecture. We created this boilerplate for a few reasons:

  1. To experiment with modularisation
  2. To share some approaches to clean architecture, especially as we've been talking a lot about it
  3. To use as a starting point in future projects where clean architecture feels appropriate

It is written 100% in Kotlin with both UI and Unit tests - we will also be keeping this up-to-date as libraries change!

Disclaimer

Note: The use of clean architecture may seem over-complicated for this sample project. However, this allows us to keep the amount of boilerplate code to a minimum and also demonstrate the approach in a simpler form.

Clean Architecture will not be appropriate for every project, so it is down to you to decide whether or not it fits your needs 🙂

Languages, libraries and tools used

Requirements

Architecture

The architecture of the project follows the principles of Clean Architecture. Here's how the sample project implements it:

architecture

The sample app when run will show you a simple list of all the Bufferoos (Buffer team members!).

Drawing

Let's look at each of the architecture layers and the role each one plays :)

architecture

User Interface

This layer makes use of the Android Framework and is used to create all of our UI components to display inside of the Browse Activity. The layer receives its data from the Presentation layer and when retrieved, the received models are mapped using the Bufferoo Mapper so that the model can be mapped to this layer's interpretation of the Bufferoo instance, which is the BufferooViewModel. The Activity makes use of the BrowseContract to enable communication to and from the presenter.

Presentation

This layer's responsibility is to handle the presentation of the User Interface, but at the same time knows nothing about the user interface itself. This layer has no dependence on the Android Framework, it is a pure Kotlin module. Each Presenter class that is created implements the Presenter interface defined within an instance of a contract - in this case the BrowseContract, which also contains an interface for the View interface.

When a Presenter is constructed, an instance of this View is passed in. This view is then used and the presenter is set for it using the implemented setPresenter() call.

The presenters use an instance of a SingleUseCase from the Domain layer to retrieve data. Note here that there is no direct name reference to the UseCase that we are using - we do inject an instance of the GetBufferoos UseCase, however.

The presenter receives data from the Domain layer in the form of a Bufferoo. These instances are mapped to instance of this layers model, which is a BufferooView using the BufferooMapper.

Domain

The domain layer responsibility is to simply contain the UseCase instance used to retrieve data from the Data layer and pass it onto the Presentation layer. In our case, we define a GetBufferoos - this use case handles the subscribing and observing of our request for data from the BufferooRepository interface. This UseCase extends the SingleUseCase base class - therefore we can reference it from outer layers and avoid a direct reference to a specific implementation.

The layer defines the Bufferoo class but no mapper. This is because the Domain layer is our central layer, it knows nothing of the layers outside of it so has no need to map data to any other type of model.

The Domain layer defines the BufferooRepository interface which provides a set of methods for an external layer to implement as the UseCase classes use the interface when requesting data.

architecture

Data

The Data layer is our access point to external data layers and is used to fetch data from multiple sources (the cache and network in our case). It contains an implementation of the BufferooRepository, which is the BufferooDataRepository. To begin with, this class uses the BufferooDataStoreFactory to decide which data store class will be used when fetching data - this will be either the BufferooRemoteDataStore or the BufferooCacheDataStore - both of these classes implement the BufferooDataStore repository so that our DataStore classes are enforced.

Each of these DataStore classes also references a corresponding BufferooCache and BufferooRemote interface, which is used when requesting data from an external data source module.

This layers data model is the BufferooEntity. Here the BufferooMapper is used to map data to and from a Bufferoo instance from the domain layer and BufferooEntity instance from this layer as required.

Remote

The Remote layer handles all communications with remote sources, in our case it makes a simple API call using a Retrofit interface. The BufferooRemoteImpl class implements the BufferooRemote interface from the Data layer and uses the BufferooService to retrieve data from the API.

The API returns us instances of a BufferooModel and these are mapped to BufferooEntity instance from the Data layer using the BufferooEntityMapper class.

Cache

The Cache layer handles all communication with the local database which is used to cache data.

The data model for this layer is the CachedBufferoo and this is mapped to and from a BufferooEntity instance from the Data layer using the BufferooEntityMapper class.

Conclusion

We will be happy to answer any questions that you may have on this approach, and if you want to lend a hand with the boilerplate then please feel free to submit an issue and/or pull request 🙂

Again to note, use Clean Architecture where appropriate. This is example can appear as over-architectured for what it is - but it is an example only. The same can be said for individual models for each layer, this decision is down to you. In this example, the data used for every model is exactly the same, so some may argue that "hey, maybe we don't need to map between the presentation and user-interface layer". Or maybe you don't want to modularise your data layer into data/remote/cache and want to just have it in a single 'data' module. That decision is down to you and the project that you are working on 🙌🏻

Thanks

A special thanks to the authors involved with these two repositories, they were a great resource during our learning!