Introduction to Kotlin Lambdas: Getting Started
In this tutorial you will learn how to use lambda expressions and other functional literals provided by Kotlin for the Android platform. Lambda expression is simplified representation of a function. It can be passed as a parameter, stored in a variable or even returned as a value. By Rachit .
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Introduction to Kotlin Lambdas: Getting Started
25 mins
- Getting Started
- Recapping Functions
- Getting Into Lambda Expressions
- Function Types
- Lambda With Parameters
- Making Buttons Clickable
- Understanding Lambda Concepts
- Hiding the Empty UI
- Displaying Songs
- Reloading Songs
- Anonymous Functions
- Saving Songs
- Validating Song Data
- Saving Data With Validation
- Higher-Order Functions
- Understanding Closures
- Where to Go From Here?
Understanding Lambda Concepts
Lambdas have two special characteristics.
First, the onClickListener()
that you just defined could’ve been written this other way:
button_add_song_empty.setOnClickListener(View.OnClickListener {
showAddSongDialog()
})
As you can see, in this case, the lambda is defined inside the parentheses as a parameter. If a lambda has a function as its last or only parameter, you can omit the parentheses as you did in the previous code.
The second characteristic is that you can replace interfaces with a single method with a lambda expression. You can check the Android Documentation on View.OnClickListener to see that View.OnClickListener
is an interface with just one method: onClick(View v)
.
Finally, call setClickListeners()
from the onCreate()
function in the MainActivity:
setClickListeners()
Build and run, and click the Add Song button again. You will see the dialog to add a new song.
Next, you will add logic to show songs and hide empty UI.
Hiding the Empty UI
The MainActivity
class initially shows an empty screen. You need to hide the empty UI once you start adding songs.
In the MainActivity.kt file, add the following lambda expression as a new property below private lateinit var songStore: SongStore
to toggle the empty UI visibility:
// 1
val toggleEmptyView = { show: Boolean ->
// 2
group_empty.visibility = if (show) View.VISIBLE else View.GONE
button_add_song.visibility = if (show) View.GONE else View.VISIBLE
}
In the snippet above, you:
- Define a lambda that has a parameter
show
ofBoolean
type. - Show the empty UI if the
show
value istrue
, else hide it.
Next, you will add the code to show songs from SongStore
on the UI.
Displaying Songs
As specified earlier, the SongStore.kt stores the song to preferences. It stores each song as a comma-separated String
item in StringSet
preference in the format title, artist, year.
The SongStore class provides you with a property allSongs
to access all the currently stored songs. You will now add the code to show song list on the screen. You will also add a bit of styling to the song list by underlining the song title.
In the MainActivity.kt file, and add the following function below the setClickListeners()
method to show the available songs. Don’t forget to import the necessary classes using Opt + Enter (or Alt + Enter on Windows):
// 1
private fun showAllSongs(songs: List<String>) {
// 2
val spans = mutableListOf<Spanned>()
// 3
val underlineTitle: (String) -> SpannableString = {
val songTitle = it.split(",")[0]
SpannableString(it).apply {
setSpan(UnderlineSpan(), 0, songTitle.length,
Spannable.SPAN_EXCLUSIVE_INCLUSIVE)
}
}
// 4
for (song in songs) {
spans.add(underlineTitle(song))
spans.add(SpannedString("\n\n"))
}
// 5
text_view_all_songs.text = TextUtils.concat(*spans.toTypedArray())
// 6
toggleEmptyView(spans.isEmpty())
}
In this code, you:
- Define function
showAllSongs()
, which takes a list of songs as parameter. - Initialize list of
Spanned
to hold each styled song item. - Define lambda expression which takes a parameter of
String
type and has a return type ofSpannableString
. The lambda finds the song title by splitting the song at,
and reading the first value. It then applies the underline span to each song title. - Apply the underline style to each song title by passing each song to the
underlineTitle
lambda variable asunderlineTitle(song)
. You also add new line span to add spacing between song items in UI. - Show the song list on the UI by joining all the spans and setting it to
text_view_all_songs
view. - Toggle the empty UI by calling the
toggleEmptyView
based on if the styled song item list is not empty.
Reloading Songs
In the MainActivity.kt file, add the following line below the setClickListeners()
call in the onCreate()
function:
showAllSongs(songStore.allSongs.toList())
In this code block, you call the showAllSongs
and pass the current stored songs by reading the store.allSongs.toList()
value from SongStore
class.
Also, add following code to the onSongAdded
function:
showAllSongs(songStore.allSongs.toList())
toggleEmptyView(false)
The onSongAdded
function is called by the AddSongFragment
every time a new song is added.
In the above code, you call the showAllSongs
and pass the current stored songs from SongStore
class. You also toggle the empty UI.
Save your changes and run the app. No songs are visible yet as the SongStore
is still empty. Next, you will go ahead and add the code to handle the addition of songs.
Anonymous Functions
As you learnt in the previous section, lambda expressions can’t have a return statement. The return type is either inferred from the variable which stores the lambda or from the last statement of the lambda body. Another missing thing is multiple return points you might often need.
You can use anonymous functions to solve these shortcomings. You define an anonymous function as a regular function omitting its name.
After you type in the song title, artist and year in the input fields on the screen, click the Save button.
Nothing happens yet as you must still add the code to read and store the input song. Next, you will add the code to save the song once you click the Save button.
Saving Songs
Open the AddSongFragment.kt file and add the following code below the handleSongSaveClick()
function to read input data:
// 1
private fun saveSong(): Boolean {
// 2
val title = edit_text_title.text?.toString()
val artist = edit_text_artist.text?.toString()
val year = edit_text_year.text?.toString()
return true
}
In this code, you:
- Define a function to
saveSong()
with aBoolean
return type. - Read and store the input values in the respective variables. You store the title, artist and year in the
title
,artist
,year
variables.
Next, you will add the following code inside the onClickListener { }
block code defined in handleSongSaveClick()
to call the saveSong
function each time you click the Save button:
if (saveSong()) {
dismiss()
}
Here, you dismiss the input screen dialog if the saveSong()
returns true.
You might wonder why the saveSong()
function returns a Boolean
value. Next, you will find out why validation is important and the use case for anonymous functions.
Validating Song Data
Whenever you take user input, it is necessary for you to validate the input data and make sure input data is correct and consistent. Inconsistent data leads to app crashes and bad user experiences.
Add the following code below the saveSong()
function in the AddSongFragment.kt file. You will need to validate the input data using the following functions before storing it:
// 1
private fun isValidArtist(artist: String?): Boolean {
// 2
val artistValidator = fun(value: String?): Boolean {
// 3
if (value.isNullOrEmpty()) {
showError(R.string.error_artist_empty)
return false
}
if (value.length < 2) {
showError(R.string.error_artist_invalid)
return false
}
return true }
// 4
return artistValidator(artist)
}
// 5
private fun isValidYear(year: String?): Boolean {
val yearValidator: (String?) -> Boolean = { !it.isNullOrEmpty() && it.toInt() in 1877..2019 }
return yearValidator(year)
}
In the code block above, you:
- Define a function
isValidArtist()
withBoolean
return type. - Initialize
artistValidator
variable with an anonymous function. - Return
false
if the input value of artist name isnull
or empty or if its length is less than two characters. Otherwise, you returntrue
from the anonymous function. - Execute the anonymous function by calling
artistValidator()
and passingartist
as parameter. - Define a function
isValidYear()
, which returns atrue
orfalse
value. It calls the lambdayearValidator
on the year value to validate the input data and returns its result.
You will need to call these validation functions whenever a song is being saved.