Android Clean Architecture with Coroutines, Koin, and Retrofit.

In this article, I will explain how to build an Android App using Clean Architecture. For that, I will focus on a specific use case: Get posts from the internet and display them in a recyclerView.

We will fetch posts from this endPoint: https://jsonplaceholder.typicode.com/posts.

We will use:

  • Koin: As a dependency injection framework to provide object dependencies.
  • Retrofit: For making network API calls.
  • Kotlin coroutines: For making asynchronous calls.

I will use Kotlin to write the Application. If you are not familiar with clean architecture, please visit this link.

Here is the architecture of the project:

The Application will have three layers: presentation, core, and data.

The project looks like this in Android Studio:

Each layer will have a koin module where I will provide all object dependencies. Since we have three layers, I will create a presentationModule, coreModule, and a dataModule. I will initialize Koin with those modules in the App class.

Let’s start by adding dependencies. Add this to app/build.gradle.

In the Android Manifest.xml add internet Permissions:

<uses-permission android:name="android.permission.INTERNET" />

Here is the content of the App class:

Presentation Layer:

This layer will follow the MVP pattern. I created an Activity in which I will display posts. The activity here represents the View in MVP. We will display posts in a recyclerView defined in the MainActivity. The Posts(PostItem) represent the Model in MVP. To show posts, I will need a Presenter. I set a contract between the MainActivity(View) and the Presenter. I called it PostContract.

I used composition to create the recyclerView adapter. I will not give all the details about recyclerView implementation since this is not the goal of this article. In this article, I will only talk about fetching posts using the Presenter and make them available for the view to display them. But, if you are curious about how I am showing them in the recycler view, please check the GitHub link of the project for more details. So, the package “list” defines the abstraction around a ListAdapter, and the post package is the actual implementation of that abstraction for PostListAdapter.

The MainActivity will be responsible for displaying posts. Here is the content activity_main.xml:

post_list is how recycler_view, which will contain the list of posts. We will also need to present a progress bar when fetching posts.

Here is the content of MainActivity.kt

As mentioned up, there is a contract between the PostPresenter and the MainActivity: PostContract. The MainActivity implements PostContract.View. I am injecting PostPresenter using Koin in line 3. After initializing the view in onCreate, the activity will ask the Presenter to get posts via the getPosts() method. Line 9, I attach the view(MainActivity) to PostPresenter, making the Presenter keep the reference to the MainActivity. Line 19, the setPost() method, will pass the list of posts to the PostAdapter to set posts in the recyclerView. The showProgres() method will show the ProgressBar and the hideProgress() method will hide the ProgressBar().

Here is the content of PostPresenter.kt

Line 16, I create a coroutine to get posts asynchronously using the method getPostAsync(). getPostAsync will use postInteractor to get posts. In case of success, postInteractor will return the list of Post that I will map to PostItems before passing them to the view. It is important to do the mapping because posts belong to the core layer; therefore, the view should not use them. The view can only receive Models defined in the contract. I will show the definition of PostContract in the domain(Core) layer.

is Result.Success -> view?.setPosts(result.data.map { it.mapToPostItem() })

If getPostAsync() returns an error, the message is still displayed in the view using the showMessage() method.

is Result.Error -> result.throwable.message?.let { view?.showMessage(it) }

The presentationModule will provide dependencies in the presentation layer.

Core Layer:

This layer contains the business logic of the Application. This layer doesn’t depend on the Android platform. We will define the contract between the view and the Presenter here:

Post.kt defines a Post.

data class Post(val title: String, val body: String)

PostInteractor.kt defines the getPost use case.

interface PostInteractor {
suspend fun getPosts(): Result<List<Post>>
}

PostInteractorImpl is the implementation of PostInteractor. PostInteractorImpl will need a PostRepository to get posts from the internet.

Here is the signature of the PostRepository.

interface PostRepository {
suspend fun getPosts(): Result<List<Post>>
}

getPost() returns a Result. Here I am using a sealed class Result for Success and Error.

sealed class Result<out T: Any> {
data class Success<out T : Any>(val data: T) : Result<T>()
data class Error(val throwable: Throwable) : Result<Nothing>()
}

Here is how I provide the PostInteractor in the coreModule class.

Data Layer:

This layer will be responsible for providing all the data needed by the domain(Core) layer.
Let’s say we are developing the App against a Stage environment but want to know also how it behaves on Prod. The class Environment will give us that flexibility. So, the Environment class represents that abstraction. So, if you have a Stage, Prod, and UAT environment, you will define three separate environments. The Environment class is pretty simple in our case here since it only takes one parameter, which is the base Url.

data class Environment(val baseUrl: String)

For this project, this is the environment object that will be provided by Koin:

Environment("https://jsonplaceholder.typicode.com/")

We will create a ServiceFactory class that will provide a PostService. ServiceFactory uses retrofit to provide services. Here is the content of ServiceFactory:

Retrofit is a Type-safe HTTP client for Android. I use it in the ServiceFactory to make network calls. ServiceFactory takes as parameters:

  • converterFactory: Can pass converter factory for Moshi, Gson, and more. Since I am going to use Moshi, the converterFactory is MoshiConverterFactory.
  • callAdapterFactory: You can use Rx or Coroutines. In this case, we will use CoroutineCallAdapterFactory.
  • Log Level: Let’s make this open to whoever provides ServiceFactory implementation to choose his Okhttp log level. I used HttpLoggingInterceptor.Level.BODY
  • environment: The environment in which runs the App.

Koin will provide them.

NetworkServiceFactory is the implementation of ServiceFactory.

Here is an example of interceptor that can be pass to the ServiceFactory:

NetworkRequestHeader is a class defining headers to pass to each request.

class NetworkRequestHeader(val type: String, val value: String)

When fetching posts, MoshiConverterFactory will convert the JSON response to a List of PostDAO. Here is the definition of PostDAO.

I left JSON annotations on purpose to show that PostDAO is a Moshi serializable object and also to emphasize that this object belongs to the data layer, and I will map it to a Post entity that has no dependencies on Moshi.

I created the mapToPost() extension function to map the PostDAO to Post entity.

Here is the signature of the PostService that will be provided by the NetworkServiceFactory. The getPostsAsync() method returns a Deferred because of CoroutineCallAdapterFactory.

interface PostService {
@GET("/posts")
@Headers("Cache-Control: no-cache")
fun getPostsAsync(): Deferred<List<PostDAO>>
}

ServiceProviderImpl needs ServiceFactory to provide PostService.

Here is the implementation of PostRepository:

Line 6, I apply the await() to get a list of PostDAO. Line 7, PostDAOs are mapped to Posts. Line 9, I return the Posts. If an error occurs, I return it in line 10.

Here is the content of the dataModule:

And here are the posts

If you liked the article, please clap it. Thanks !!

The code can be found here:

Lead Android Developer @ VMLY&R