The Art of Building Robust Android Apps with Kotlin’s Functional Programming Techniques

Summit Kumar
7 min readMay 13, 2023

--

Kotlin is a modern, multi-purpose programming language that is rapidly gaining popularity among Android app developers. One of the key features that sets Kotlin apart from other programming languages is its strong support for functional programming. Kotlin functional programming allows developers to write cleaner, more concise, and more maintainable code that is easier to test and debug. In this article, we’ll explore the power of Kotlin functional programming in Android app development and showcase ten real-world use cases with practical code examples.

Here are some practical code examples of how Kotlin functional programming can be used in Android app development:

Mapping and filtering data:
Using functional programming concepts like map, filter, and reduce, We can transform data from one form to another or filter data based on specific conditions. For example, We can use these concepts to parse and manipulate JSON data returned from a web API.

data class User(val name: String, val age: Int)

val users = listOf(
User("Shiva", 25),
User("Vishnu", 30),
User("Kartik", 20),
User("Brambha", 35)
)

val names = users.map { it.name } // ["Shiva", "Vishnu", "Kartik", "Brambha"]
val adults = users.filter { it.age >= 18 } // [User("Shiva", 25), User("Vishnu", 30), User("Kartik", 20), User("Brambha", 35)]

In the above example, we have a list of User objects and we use the map function to extract the names of the users into a new list, and the filter function to create a new list containing only the adult users.

Event handling:
We can use functional programming concepts to handle events in our Android app. For example, We can use higher-order functions to pass functions as parameters to event handlers, or use functional interfaces to define the event handlers themselves.

button.setOnClickListener { showToast("Button clicked!") }

fun showToast(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}

In the above example, we use a lambda expression to define an event handler for a button click event. The event handler simply calls a showToast function that displays a toast message.

Asynchronous programming:
Kotlin’s support for coroutines and asynchronous programming can make it easier to manage asynchronous tasks like network calls or database queries. We can use functional programming concepts like monads to manage the results of asynchronous tasks and handle errors more effectively.

Here’s an example implementation of using the Maybe monad for asynchronous programming in Kotlin:

fun getUserById(id: String): Maybe<User> {
return Maybe.create { emitter ->
try {
val user = apiService.getUserById(id)
if (user != null) {
emitter.onSuccess(user)
} else {
emitter.onComplete()
}
} catch (e: Throwable) {
emitter.onError(e)
}
}.subscribeOn(Schedulers.io())
}

In the above example, we define a function getUserById that returns a Maybe<User> object. Inside the function, we create a new Maybe object that retrieves a user by their ID using an apiService object.

If a user is retrieved, we emit the Success state with the user object using the onSuccess method. If no user is retrieved, we emit the Complete state using the onComplete method. If an error occurs during the retrieval process, we emit the Error state with the error object using the onError method.

We also use the subscribeOn operator to specify that the network request should be performed on the io thread.

To handle the result of the asynchronous operation, we can chain together various operations using the flatMap and map operators provided by the Maybe class. Here's an example:

getUserById(userId)
.flatMap { user -> getPostsByUser(user.id) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ postList -> /* Display the list of posts */ },
{ error -> /* Display an error message */ },
{ /* Handle onComplete */ }
)

In the above example, we call the getUserById function to retrieve a user by their ID. We then use the flatMap operator to map the User object to a Maybe<List<Post>> object by calling the getPostsByUser function. If a user is retrieved, we return a list of posts by that user. If no user is retrieved, the flatMap operator will return an empty Maybe object.

We then use the observeOn operator to switch to the main thread and subscribe to the Maybe object. We provide three lambdas to handle the Success, Error, and Complete states of the Maybe object.

Dependency injection:
Kotlin functional programming can be used to implement dependency injection in our Android app. By defining functions or lambdas that return instances of objects, We can reduce the amount of boilerplate code needed to create and manage dependencies.

class UserRepository(private val api: UserApi) {
suspend fun getUser(id: String): User {
return api.getUser(id)
}
}

val userRepository = UserRepository(UserApiImpl())

In the above example, we define a UserRepository class that depends on a UserApi interface. We pass an instance of UserApiImpl to the constructor of UserRepository to inject the dependency.

Unit testing:
Functional programming concepts like pure functions and immutability can make unit testing easier and more reliable. By writing pure functions that take immutable data structures as inputs and return immutable data structures as outputs, Wecan test individual functions in isolation and reduce the risk of side effects.

fun add(a: Int, b: Int): Int {
return a + b
}

@Test
fun testAdd() {
assertEquals(5, add(2, 3))
}

In the above example, we define a pure function add that takes two integers as inputs and returns their sum. We then write a unit test using the assertEquals function to verify that add(2, 3) returns 5.

State management:
Kotlin functional programming can be used to manage app state and reduce the complexity of state management. We can use functional concepts like immutable data structures and higher-order functions to manage state changes more effectively and prevent race conditions.

data class AppState(val counter: Int)

fun incrementCounter(state: AppState): AppState {
return state.copy(counter = state.counter + 1)
}

val initialState = AppState(counter = 0)
var currentState = initialState

currentState = incrementCounter(currentState)

In the above example, we define an immutable data class AppState to represent the state of our app. We then define a pure function incrementCounter that takes an AppState object as input, increments its counter, and returns a new AppState object with the updated counter value. We then use this function to update the current state of the app.

Data binding:
Kotlin functional programming can be used to implement data binding in our Android app. By defining functions that return calculated values based on data input, We can reduce the amount of code needed to bind data to user interfaces.

data class User(val name: String, val age: Int)

class UserViewModel(val user: User) {
val greeting: String
get() = "Hello, my name is ${user.name} and I am ${user.age} years old."
}

In the above example, we define a User data class and a UserViewModel class that depends on a User object. The UserViewModel class provides a greeting property that returns a formatted greeting message based on the user's name and age. We can then use data binding to display the greeting in our app's UI.

Function composition:
Function composition is the process of combining two or more functions to create a new function. In Kotlin, we can use the compose method of the Function interface to achieve function composition in just two lines of code.

fun add(a: Int, b: Int): Int {
return a + b
}

fun square(x: Int): Int {
return x * x
}

val addAndSquare = { x: Int, y: Int -> square(add(x, y)) }

addAndSquare(2, 3) // 25

In the above example, we define two pure functions add and square. We then define a higher-order function addAndSquare that takes two integers as inputs, adds them together, squares the result, and returns the final value.

Higher-order functions:
Higher-order functions are functions that take other functions as parameters or return functions as results. In Kotlin, we can define higher-order functions in just two lines of code using function types and lambda expressions.

fun repeat(n: Int, action: () -> Unit) {
for (i in 1..n) {
action()
}
}

repeat(3) { showToast("Hello!") }

In the above example, we define a repeat function that takes an integer n and a lambda expression action as inputs. The function then executes the action lambda n times. We can use this function to simplify the code for repeating a certain action multiple times, like showing a toast message.

Currying:
Currying is the process of transforming a function that takes multiple arguments into a series of functions that each take a single argument. In Kotlin, we can use function types and lambda expressions to achieve currying in just two lines of code.

fun add(a: Int): (Int) -> Int {
return { b -> a + b }
}

val add5 = add(5)
val add10 = add(10)

add5(3) // 8
add10(3) // 13

In the above example, we define a add function that takes an integer a and returns another function that takes an integer b and returns the sum of a and b. We can then use this function to create partially applied functions add5 and add10 that add 5 and 10 to a given input, respectively.

In conclusion, Kotlin functional programming can be used in many ways in Android app development to improve code quality, reduce code duplication, and enhance app performance. By leveraging functional programming concepts and techniques, We can create more expressive, maintainable, and robust code in our Android apps.

--

--

Summit Kumar

Tech-savvy BTech IT professional with a passion for latest technologies. Always seeking new challenges & opportunities to expand my knowledge. #KeepLearning #IT