How I learned Clean Architecture for Android

David Adeyinka
6 min readAug 3, 2020

--

I just finished learning about Clean Architecture for Android, and I want to share how I used that to restructure my Bluetooth Chat App.

Assumptions

This article assumes that you’re already familiar with Android Development and that you have a good understanding of Android Jetpack Architecture. You should also be familiar with Kotlin.

Clean Architecture

Clean Architecture is an architectural principle designed by Uncle Bob. It’s mainly about the separation of concerns by dividing an application into various layers represented by concentric circles:

https://blog.cleancoder.com/uncle-bob/images/2012-08-13-the-clean-architecture/CleanArchitecture.jpg

Each circle only contains dependencies of the inner layer and no more, and this is known as The Dependency Rule:

This rule says that source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle. In particular, the name of something declared in an outer circle must not be mentioned by the code in the an inner circle.

Uncle Bob’s main article and many other articles out there cover this topic in detail, so I won’t go into that. I’m here to show you how I came up with the way to structure my app according to this architecture from what I’ve learned.

Android Architecture

Android Jetpack already has a great guide as to how to structure your app. It utilizes concepts like separation of concerns and a single source of truth for your application.

https://developer.android.com/topic/libraries/architecture/images/final-architecture.png

Clean Architecture for Android is kind of a step-up to that design, making you see their architecture as a clean one.

Clean Architecture for Android

Clean Architecture on Android has a slightly different graph from Uncle Bob’s original definition:

https://antonioleiva.com/wp-content/uploads/2018/09/clean-architecture-own-layers.png

And the implementation looks like this:

https://koenig-media.raywenderlich.com/uploads/2019/06/architecture-overview-650x343.png

The Ray Wenderlich diagram really helped me mentally piece these together, so you should really check out his article if you want to read more.

Clean Architecture on Android is made up of five layers:

  • Presentation
  • Use Case
  • Domain
  • Data
  • Framework

Note that the order I mentioned these layers is NOT hierarchical. That was the main mistake I first saw this layer. In fact, it’s better to visualize the circles like a slice of pie, or a cone, as David Preirera did for Clean Architecture:

https://www.codingblocks.net/wp-content/uploads/2018/02/The-Clean-Architecture-Cone.jpg

The App in Layers

I’ll elaborate on each layer, starting from the most abstract, to the most concrete, and show how I send a Chat from my code using each one. Once again bear in mind that layers in each circle come in no specific order.

Domain Layer

This represents your application-wide business rules. Try to think of them as simple relational database entities, but even more generic. You can have a set of entities for each layer, but I skipped over that part because I didn’t want to go through the stress of mapping back and forth. Here’s the code for Chat.kt

package com.example.bluechat.domain.modelsdata class Chat(val content : String, var timestamp : Long, var senderAddress : String, var senderName : String? = null) {}

Use Case Layer

This layer was new to me as someone already used to classic Android Architecture. Basically, it represents all the possible user interactions with the UI (like opening an email with a button) in the form of classes. I made an empty interface named UseCase to make them all feel uniform, and here’s the ChatActivityUseCase.kt

package com.example.bluechat.usecaseimport androidx.lifecycle.LiveData
import com.example.bluechat.data.repositories.ChatRepository
import com.example.bluechat.domain.models.Chat
class ChatActivityUseCase(var repository: ChatRepository) : UseCase {
fun sendMessage(message : String) {
repository.sendMessage(message)
}
fun getChatsFromPartner(partnerAddress : String) : LiveData<ArrayList<Chat>>{
return repository.getChatsFromPartner(partnerAddress)
}
}

Before I continue, I want you to notice that I made an Android import in this class. This should be forbidden since it breaks the dependency rule by introducing an Android framework class into an inner circle. However, I consider LiveData as an exception to this rule, since it can be considered as a reactive component specialized for Android.

Data Layer

The other thing that had me confused about Clean Architecture for Android was the strange naming of the Data layer. I initially thought it served the function of the Domain Layer, and that layer, too, got me confused as to why it wasn’t called the Entity layer like in the original graph. So take note of these if you find yourself confused as well.

The data layer interacts with the Framework layer via interfaces to abstractly define them and communicate with them. It’s basically the Repository pattern from Android Architecture, but cleaner. The ChatRepository class is as follows:

package com.example.bluechat.data.repositoriesimport androidx.lifecycle.LiveData
import com.example.bluechat.data.BluetoothInteractor
import com.example.bluechat.data.DataProvider
import com.example.bluechat.data.PlainMessageHandler
import com.example.bluechat.domain.models.Chat
import java.util.*
import kotlin.collections.ArrayList
class ChatRepository(private var provider : DataProvider,private var interactor: BluetoothInteractor) { ...
fun sendMessage(message : String) {
try{
interactor.write(message.toByteArray())
provider.addChat(Chat(message, Date().time, "02:00:00:00:00:00"))
} catch (e : Exception) {
e.printStackTrace()
}
}
}

BluetoothInteractor and DataProvider are both abstractions for Bluetooth connection management and database access, respectively. One great thing I’d like to mention about this architectural style is that I created an in-memory implementation of DataProvider . Whenever I want to add Room to my project, I’ll simply swap implementations, and the code won’t break. Here’s the interface, BluetoothInteractor.kt :

package com.example.bluechat.dataimport androidx.lifecycle.LiveData
import com.example.bluechat.domain.models.AvailableDevice
interface BluetoothInteractor {
fun write(bytes : ByteArray)
fun subscribeForMessages(plainHandler: PlainMessageHandler)
fun openChatWithDevice(position : Int, onFinished : (success : Boolean)->Unit)
fun getScannedDevices() : LiveData<ArrayList<AvailableDevice>>
fun getScanState() : LiveData<BluetoothScanState>
fun getVisibility() : LiveData<BluetoothVisibility>
}

Framework Layer

Here, you implement the interfaces provided by the Data Layer, and you can use Android-specific code here. This is my MainBluetoothService.kt

package com.example.bluechat.framework.servicesimport ...class MainBluetoothService : Service() , BluetoothInteractor{    ...
val binder
= MainBluetoothBinder()
...
override fun write(bytes : ByteArray) {
binder.write(bytes)
}
... inner class MainBluetoothBinder() : Binder() { var chatPartner : BluetoothDeviceGeneric? = null
...

fun write(bytes: ByteArray) {
chatPartner?.sendMessage(bytes)
}
}
}

BluetoothDeviceGeneric is a class that handles connections for individual Bluetooth devices. The sendMessage() method passes the ByteArray to the OutputStream and sends it across the connection.

Presentation Layer

This consists of all your UI code, including your Activities, Fragments, ViewModels, and RecyclerView.Adapter implementations. Here, there’s a lot of code between the ViewModel and Activity , so I’ll reduce the code to the most necessary parts. Here’s ChatActivityUseCase.kt

package com.example.bluechat.presenter.viewmodelsclass ChatViewModel(partnerAddress : String,var useCase: ChatActivityUseCase) : ViewModel(){    ...
fun sendMessage(message : String) {
useCase.sendMessage(message)
}
}

And here’s ChatActivity.kt :

package com.example.bluechat.presenter.activitiesimport ...class ChatActivity : AppCompatActivity(), View.OnClickListener {    lateinit var binder : MainBluetoothService.MainBluetoothBinder
lateinit var viewModel : ChatViewModel
val servConn = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
binder = service as MainBluetoothService.MainBluetoothBinder
val useCase = ChatActivityUseCase(
ChatRepository(dataProvider, binder.service)
)
...
}
override fun onServiceDisconnected(name: ComponentName?) { }
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
...
bindService(Intent(this, MainBluetoothService::class.java), servConn, Context.BIND_AUTO_CREATE)
}
override fun onClick(v: View?) {
v?.let {
when
(it) {
imageview_send_chat -> {
val text = edittext_chat_text.text.toString()
edittext_chat_text.setText("")
viewModel.sendMessage(text)
}
}
}
}
}

The Relationship

I found this nice illustration from this article that maps out the relationship between Android and Clean Architecture almost fully:

https://miro.medium.com/max/700/1*a-AUcEVdyRJhIepo9JyJBw.png

Conclusion

It can be a bit of a headache for you once you start shifting architectural designs, but hopefully, I was able to make moving from Android to Clean Android an easier process.

The code can be found here on Github. Thank you for your time.

--

--

Responses (1)