Arrow 101 — Building an Android app using Functional Programming

1 — Intro

Functional programming might seam a little bit challenging at first, but once you give it a try, you'll see that it comes with great benefits. The immutability makes the code a lot easier to reason about, to test and maintain. You don't need so many patterns and so many libraries in order make good quality code. Just by using the principles of functional programming you can make your code shine.

1.1 — About the project

The project uses MVP. It reads from the Github API, so we don't have to worry about creating an API to interact with our app and focus on what matters. Our control and model layer are composed only by functions, but it is still necessary to keep the activity code as OOP, as you will see.

2 — The View (Activity)

The code in our activity is not different from what you're probably used to:

class MainActivity : AppCompatActivity(), RepositoriesView {

private val repoList : MutableList<Repository> = mutableListOf()

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

setupList(repoList)
}

private fun setupList(listItems : List<Repository>) {
heroesListRV.layoutManager = LinearLayoutManager(this)
heroesListRV.adapter = RepositoriesAdapter(listItems, { Log.d("Click", "Got a click!") })
}

override fun onResume() {
super.onResume()
getRepositories().run(RepositoriesDeps(this))
}

override fun showGenericError() {
runOnUiThread {
alert("Generic error!!") {
yesButton { }
}
.show()
}
}

override fun drawRepositories(heroes: List<Repository>) {
runOnUiThread {
repoList
.addAll(heroes)
repositoryListRV.adapter.notifyDataSetChanged()
}
}
}
data class RepositoriesDeps(val view: RepositoriesView)
getRepositories().run(RepositoriesDeps(this))

2 — The Presenter

So, let's build the skeleton of getRepositories().

fun getRepositories() : Reader<RepositoriesDeps, Unit> =
ReaderApi.ask<RepositoriesDeps>()
fun <D, A, B> Reader<D, A>.map(f: (A) -> B): Reader<D, B> = map(IdBimonad, f)
fun getRepositories() : Reader<RepositoriesDeps, Unit> =
ReaderApi.ask<RepositoriesDeps>()
.map { (view) ->
repositoryUseCase()
.unsafeRunAsync { reposEither ->
reposEither.fold(
{ view.showGenericError() },
{ reposList -> drawRepos(reposList, view)})
}
}
private fun drawRepos(repositoryList: List<Repository>, view: RepositoriesView) {
repositoryList.
sortedByDescending {
repository -> repository.stargazersCount
}
pipe { view.drawHeroes(it) }
}

3 — The Model Layer

Then, we have our use case:

fun repositoryUseCase(): IO<List<Repository>> = fetchAllRepositories()
private fun queryForRepositories(): Try<List<Repository>> =
Try {
apiClient()
.getRepositories("Java", "star", 1)
.execute()
.body()!!
.items
}
fun fetchAllRepositories(): IO<List<Repository>> =
IO.async { either ->
async {
either(queryForRepositories().toEither())
}
}

4 — Improving the code (for tests)

This code is not testable, because we can't mock the apiClient. So how to handle this? Well, the same way we did in the Presenter. We are going to use a Reader.

data class RepositoriesDeps(val view: RepositoriesView, 
val apiClient: ApiClient)
override fun onResume() {
super.onResume()
getRepositories().run(RepositoriesDeps(this, apiClient()))
}
fun fetchAllRepositories(): Reader<RepositoriesDeps, IO<List<Repository>>> =
ReaderApi.ask<RepositoriesDeps>()
.map { deps ->
IO.async<List<Repository>> { either ->
async {
either(queryForRepositories(deps.apiClient).toEither())
}
}
}
private fun queryForRepositories(apiClient: ApiClient): Try<List<Repository>> =
Try {
apiClient
.getRepositories("Java", "star", 1)
.execute()
.body()!!
.items
}
fun repositoryUseCase() : Reader<RepositoriesDeps, IO<List<Repository>>> = fetchAllRepositories()
fun getRepositories(): Reader<RepositoriesDeps, Unit> =
ReaderApi.ask<RepositoriesDeps>()
.flatMap { (view, _) ->
repositoryUseCase()
.map { io ->
io.unsafeRunAsync { reposEither ->
reposEither.fold(
{ view.showGenericError() },
{ reposList -> drawRepos(reposList, view) })
}
}


}
Reader<D, A>.flatMap(f: (A) -> Reader<D, B>): Reader<D, B>

5 — Final considerations

I hope you enjoyed to use FP in the Android world. It can be a bit hard if you're coming from Java, but after you get the concept, it goes just fine.

Software developer at GetStream.io — I love Android =]

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store