Giter Club home page Giter Club logo

cleanarchitectureforandroid's Introduction

Clean Architecture for Android Sample Project

Clean Architecture for Android Screenshot

This project is a loose implementation of Clean Architecture as presented in my book, Clean Architecture for Android. It is a native Android project written in Kotlin. It demonstrates the key principles presented in the book and how they apply to a real life project.

I will endeavour to keep this project up to date and use it to demonstrate the strengths of the architecture: scalability, testability and flexibility when it comes to choosing 3rd party solutions.

Features

  • Feature separation
  • Layer separation
    • UI
    • Presentation
    • Domain
    • Data
    • Data Source
  • Dummy analytics
  • Navigation
  • Animations
  • Unit tests
  • End-to-end tests
  • Demonstrates use of Jetpack Compose
  • Demonstrates use of Coroutines including Flow
  • Demonstrates MVVM
  • Code quality checks using ktlint
  • Code quality checks using detekt
  • Architecture consistency checks using konsist
  • Continuous integration (CI) using GitHub Actions
    • Unit tests
    • Instrumentation tests
    • Linting

Choices

  • Simplicity

    "The complexity introduced here is an overkill!"

    I agree. If this was to be a final project, to which no new functionality would ever be added, then Clean Architecture would have been overly complicated for it. However, the reality is that mobile project seldom are final. User feedback, marketing requirements, new technologies - these factors and others all lead to continuous changes to almost every project. And so, what may now seem like too much complexity will reward us when the time for change comes. This will be when Clean Architecture shines.

    As a side note: in some ways, I have intentionally over-complicated this project by introducing different technologies. The goal was to show how, even in real-life scenarios, where a project may have grown over time to include more than one technological solution, the architecture still works.

  • Mappers as classes vs. mapping extension functions

    When mapping between models, we have several options. The primary decision is between mapper classes and mapping extension functions.

    While extension functions are more concise, using them for mapping limits our choices of testing frameworks (Mockito, for example, cannot stub static functions).

    How about injecting the mapper extension functions? We could do that. However, this removes the benefits of conciseness almost entirely. It also makes navigation to the implementation harder.

    And so, I opted for the slightly more verbose concrete mapper classes.

  • Skipping Google's Architecture Components

    The greatest issue with Google's Architecture Components is that they leak Android details into the Presentation layer. This prevents the Presentation layer from being truly UI agnostic.

    Another issue with the Architecture Components is that they give too much responsibility to the ViewModel. They make it persist state it does not own, leading to potential data synchronization bugs.

    For these reasons, while still following MVVM, this project relies on Kotlin Flows rather than LiveData, and implements pure ViewModels rather than Google's.

  • Mocking framework

    Both Mockito-Kotlin and Mockk are used in this project to demonstrate how the use of each would look.

    My personal preference remains Mockito-Kotlin. I find the code easier to read and follow when using it. At the time of writing, judging by the number of stars on each repository, the industry seems to lean towards Mockk.

    I was asked about using fakes. I have explored fakes, and found them to be overly verbose and too expensive to maintain.

  • Dependency injection framework

    A critical part of most modern apps, dependency injection (DI) helps us obtain the objects that build our app. It also helps manage their scope. The most popular choices in the Android world are Hilt (which is built on top of Dagger) and Koin.

    Hilt was chosen for two main reasons:

    1. Compile time safety - having the confidence that all my dependencies are provided before the app starts is a huge time saver and helps maintain a stable app.
    2. Simplicity - from experience, setting up and using Hilt (unlike the underlying Dagger) is considerably easier than using Koin. Hilt also introduces fewer breaking changes over time.
  • XML vs Jetpack Compose

    Why not both? I still have a lot of concerns around Jetpack Compose. Even so, it was important to me to show the presented architecture works well regardless of the UI mechanism chosen. As an exercise, I invite you to try and replace the UI layer from Compose to XML or vice versa without updating the Presentation layer.

Links

Clean Architecture for Android on Amazon

Clean Architecture on the Clean Coder Blog

Contributing

Contributions to this project are welcome. Please feel free to report any issues or fork to make changes and raise a pull request.

Licence

This project is distributed under the terms of the MIT License. See LICENSE.md for details.

cleanarchitectureforandroid's People

Contributors

eranboudjnah avatar renovate[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

cleanarchitectureforandroid's Issues

[Question]: Parameterized test

Hello Eran. I am on the process of reading your book and came across to the testing of the model mappers.

On the file SavedIpAddressRecordToPresentationMapperTest.kt how does the actualValue work? I mean when you call the toPresentation that should return different instance of an object right? so why the test succeeded?

val actualValue = classUnderTest.toPresentation(givenSavedRecord)

When I tried I have a failure test. Even though my expected and actual are the same they are different object since classUnderTest.toPresentation(givenSavedRecord) will create a new instance

here is my error

Expected :link.limecode.newsfeed.presentation.model.pagination.PaginationPresentationModel@7bd7d6d6
Actual   :link.limecode.newsfeed.presentation.model.pagination.PaginationPresentationModel@50029372

I cant wrap around my head why does yours works and mine did not

here is my full class

package link.limecode.newsfeed.presentation.mapper

import link.limecode.newsfeed.domain.model.pagination.PaginationDomainModel
import link.limecode.newsfeed.presentation.model.pagination.PaginationPresentationModel
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters

@RunWith(Parameterized::class)
class PaginationToPresentationMapperTest(
    private val given: PaginationDomainModel,
    private val expected: PaginationPresentationModel
) {
    companion object {
        @JvmStatic
        @Parameters(name = "Given {0} then {1}")
        fun data(): Collection<Array<*>> = listOf(
            testCase(
                nextCursor = null,
                prevCursor = null
            ),
            testCase(
                nextCursor = "next_id",
                prevCursor = null
            ),
            testCase(
                nextCursor = null,
                prevCursor = "prev_id"
            )
        )

        private fun testCase(
            nextCursor: String?,
            prevCursor: String?
        ) = arrayOf(
            PaginationDomainModel(
                nextCursor = nextCursor,
                prevCursor = prevCursor
            ),
            PaginationPresentationModel(
                nextCursor = nextCursor,
                prevCursor = prevCursor
            )
        )
    }

    private lateinit var classUnderTest: PaginationToPresentationMapper

    @Before
    fun setUp() {
        classUnderTest = PaginationToPresentationMapper()
    }

    @Test
    fun `When toPresentation`() {
        // given

        // when
        val actual = classUnderTest.toPresentation(given)

        // then
        assertEquals(expected, actual)
    }
}

thank you

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.