Connecting...

W1siziisimnvbxbpbgvkx3rozw1lx2fzc2v0cy9zawduawz5lxrly2hub2xvz3kvanbnl2jhbm5lci1kzwzhdwx0lmpwzyjdxq

Arrow 101 — Building an Android app using Functional Programming by Leandro Borges Ferreira

W1siziisijiwmtgvmdyvmjavmtyvmjavntevnjgxlzffn0hksnbpuuu4ttetefhxmf81zwvqus5wbmcixsxbinailcj0ahvtyiisijkwmhg5mdbcdtawm2uixv0

If you have a strong interest in Functional Programming then read this article by Leandro Borges Ferreira where he explains how you can build 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.

Although Kotlin is not a functional programming language, any developer can use Arrow and gain many nice features for functional programming. Arrow adds new types like Option, Try, Either, IO, Functor, Applicative, Monad, High order abstractions, Type Classes and many more. In this article I am not going to explain the theory behind Arrow and functional programming, but instead show how to build a simple app using only functions so you can have a direction on your first steps into functional programming for Android.


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.

Don't worry if the concepts and code seam a bit odd at first, I took me a while to get it too! But, once it starts to make sense, you will see that FP (Functional Programming) is actually a lot simpler.

You can check the code in this repository: 

https://github.com/leandroBorgesFerreira/ArrowTry

So, let's start?


2 — The View (Activity)

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

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)
Note: I am using Anko to create the Alert Dialog.​

This activity gets Repositories from Github API, updates an Adapter to exhibit the repositories then and provides a click listener.

The interesting part is this one:

getRepositories().run(RepositoriesDeps(this, apiClient()))

This is a method from our presenter. This is a Reader monad that we are using to inject the View that the presenter needs in order to control the view. You can check this concept here: https://medium.com/@JorgeCastilloPr/kotlin-dependency-injection-with-the-reader-monad-7d52f94a482e.

Don't worry about being an expert in Monads, just remember that this is a function that holds dependencies that our functions needs. If we don't pass the view to the presenter it wouldn't be MVP. All our dependencies can reside inside RepositoriesDeps. Although we only use View, we could have also added a Navigator or an API client.

Before finish this section, you might be thinking "But why not have functions in the View?". Since we need the Context/Views to be able to draw our screen, the presenter wouldn't be able to call the functions directly, so we can't use functions in the View with MVP.

Now, next section.


2 — The Presenter

So, let's build the skeleton of getRepositories().
fun getRepositories() : Reader<RepositoriesDeps, Unit> =
        ReaderApi.ask<RepositoriesDeps>()

Right now we have a function that receives some dependencies and does nothing. So, how can we add some behaviour to it?

Reader has to following method:

fun <D, A, B> Reader<D, A>.map(f: (A) -> B): Reader<D, B> = map(IdBimonad, f)
Let's take a look. We need a function that take A (Unit) and then makes it something else. f: (A) -> B. By providing this function, we can add behaviour. Like this:
fun getRepositories() : Reader<RepositoriesDeps, Unit> =
        ReaderApi.ask<RepositoriesDeps>()
            .map { (view) ->
                 repositoryUseCase()
                   .unsafeRunAsync { reposEither ->
                     reposEither.fold(
                         { view.showGenericError() },
                         { reposList -> drawRepos(reposList, view)})
                   }
            }

repositoryUseCase() is from our Model Layer, our Use Case. It will return an IO, but more on that latter. unsafeRunAsync takes as a parameter an Either. Either is a Data Type that can have two states, in our case it could be the list of Repositories of an Exception, in case some problem occurred. You can check more about the Either in this part of Arrow's documentation: https://arrow-kt.io/docs/datatypes/either/

private fun drawRepos(repositoryList: List<Repository>, view: RepositoriesView) {
    repositoryList.
            sortedByDescending { 
                repository -> repository.stargazersCount 
            } pipe { view.drawHeroes(it) }
}

Than we just draw the information. You can use pipe to create nice compositions of functions, it basically takes the result of the previous function and passes to the next one. It can make a code easier to read when creating big compositions of functions.


3 — The Model Layer

Then, we have our use case:
fun repositoryUseCase(): IO<List<Repository>> = fetchAllRepositories()

And that's it. We could add some logic for cache… but I don't see why complicate this example.

And our data source:

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())
            }
        }

You may be thinking: "If I don't receive the apiClient as a parameter, how am I going to test this?" In the next section, I'll show how to inject the apiClient, but let's keep it simple for now.

The first function makes a synchronous call to the API (I am using Retrofit, but that's not really important here) and the result is wrapped in a Try. Try is also a Data type and it represents a computation that can return a value, if the computation is successful, or return an error if it goes wrong. It substitutes the try/catch in a more functional approach because we always get a result from out computation. Simply catching an Exception in another block of code would be odd because a function should always return a result, right?

The second function returns an IO. First we use the IO.async. It is a function made for integrations with asynchronous calls. It runs until the callback parameter is invoked. It uses Coroutines so we don't make a network call in the main thread.

So now we have everything we need. All you have to do is to implement the apiClient() part of you app, but I will leave that to you as this is no different from what you are used(or you can just check the code in the repository). In my app I got this:

With very little code and few new concepts we could create a small app without the need of classes to control its logic. This way we can avoid state in our code and the use of complex libraries, all we have to do it to trust in the concepts of FP.


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.

First we add the apiClient in the RepositoriesDeps:

data class RepositoriesDeps(val view: RepositoriesView, 
                            val apiClient: ApiClient)
And, in our activity, we write:
override fun onResume() {
    super.onResume()
    getRepositories().run(RepositoriesDeps(this, apiClient()))
}

The DataSource:

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
        }

We did the same as in the Presenter. We use a Reader that asks for the dependencies that we need. Now the can inject our ApiClient as a dependency in the queryForRepositories() function.

Use case:

fun repositoryUseCase() : Reader<RepositoriesDeps, IO<List<Repository>>> = fetchAllRepositories()

As the last part, we need to change the Presenter:

fun getRepositories(): Reader<RepositoriesDeps, Unit> =
        ReaderApi.ask<RepositoriesDeps>()
            .flatMap { (view, _) ->
                 repositoryUseCase()
                     .map { io ->
                              io.unsafeRunAsync { reposEither ->
                                  reposEither.fold(
                                          { view.showGenericError() },
                                          { reposList -> drawRepos(reposList, view) })
                              }
                          } 

             } 

Ok, now the code can the confusing. First, we cannot use map anymore, because our repositoryUseCase() does not return an IO, it returns a Reader. We are not respecting the signature: f: (A) -> B . We are instead using: (A) -> Reader<D, B>. But there is no problem, we can use flatMap that handles this problem. Take a look at the signature:

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

Using flatMap() we can change the content type (A) of our Reader and add behaviour using the function f.

We then map the answer of our repositoryUseCase function and we have the same behaviour as before.

Now we have a clean and easy to test code. The same approach can be used in more complex scenarios with no problem.


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.

If you are interested in learning more about FP. I suggest this articles that explains about Functors, Applicatives and Monads in Kotlin: https://hackernoon.com/kotlin-functors-applicatives-and-monads-in-pictures-part-1-3-c47a1b1ce251'

This article was written by Leandro Borges Ferreira and posted on Medium.com