RxJava and Coroutines in Android

Overview

This tutorial will introduce you to doing background operations and data manipulations with RxJava and coroutines. It covers how to use RxJava to retrieve data from an external API and how to do that with coroutines. You’ll also learn how to manipulate and display the data using RxJava operators and LiveData transformations.

By the end of this tutorial, you will be able to use RxJava to manage network calls in the background and use RxJava operators to transform data. You will also be able to perform network tasks in the background using Kotlin coroutines and manipulate data with LiveData transformations.

Introduction

You have now learned the basics of Android app development and implemented features such as RecyclerViews, notifications, fetching data from web services, and services. You’ve also gained skills in the best practices for testing and persisting data. In the previous chapter, you learned about dependency injection. Now, you will be learning about background operations and data manipulation.

Some Android applications work on their own. However, most apps would need a backend server to retrieve or process data. These operations may take a while, depending on the internet connection, device settings, and server specifications. If long-running operations are run in the main UI thread, the application will be blocked until the tasks are completed. The application might become unresponsive and might prompt the user to close it and stop using it.

To avoid this, tasks that can take an indefinite amount of time must be run asynchronously. An asynchronous task means it can run in parallel to another task or in the background. For example, while fetching data from a data source asynchronously, your UI can still display or interact with the users.

You can use libraries like RxJava and coroutines for asynchronous operations. We’ll be discussing both of them in this chapter. Let’s get started with RxJava.

RxJava

RxJava is a Java implementation of Reactive Extensions (Rx), a library for reactive programming. In reactive programming, you have data streams that can be observed. When the value changes, your observers can be notified and react accordingly. For example, let’s say clicking on a button is your observable and you have observers listening to it. If the user clicks on that button, your observers can react and do a specific action.

RxJava makes asynchronous data processing and handling errors simpler. Writing it the usual way is tricky and error-prone. If your task involves a chain of asynchronous tasks, it will be more complicated to write and debug. With RxJava, it can be done more easily and you will have less code, which is more readable and maintainable. RxJava also has a wide range of operators that you can use for transforming data into the type or format you need.

RxJava has three main components: observables, observers, and operators. To use RxJava, you will need to create observables that emit data, transform the data using RxJava operators, and subscribe to the observables with observers. The observers can wait for the observables to produce data without blocking the main thread.

Observables, Observers, and Operators

Let’s understand the three main components of RxJava in detail.

Observables

An observable is a source of data that can be listened to. It can emit data to its listeners.

The Observable class represents an observable. You can create observables from lists, arrays, or objects with the Observable.just and Observable.from methods. For example, you can create observables with the following:

val observable = Observable.just(“This observable emits this string”)

val observableFromList = Observable.fromIterable(listOf(1, 2, 3, 4))

There are more functions that you can use to create observables, such as Observable.createObservable.deferObservable.emptyObservable.generateObservable.neverObservable.rangeObservable.interval, and Observable.timer. You can also make a function that returns an observable. Learn more about creating observables at https://github.com/ReactiveX/RxJava/wiki/Creating-Observables.

Observables can be either hot or cold. Cold observables emit data only when they have subscribers listening. Examples are database queries or network requests. Hot observables, on the other hand, emits data even if there are no observers. Examples of this are UI events in Android like mouse and keyboard events.

Once you have created an observable, the observers can start listening to the data the observable will send.

Operators

Operators allow you to modify and compose the data you get from the observable before passing it to the observers. Using an operator returns another observable so you can chain operator calls. For example, let’s say you have an observable that emits the numbers from 1 to 10. You can filter it to only get even numbers and transform the list into another list containing each item’s square. To do that in RxJava, you can use the following code:

Observable.range(1, 10)

.filter { it % 2 == 0 }

.map { it * it }

The output of the preceding code will be a data stream with the values 4, 16, 36, 64, and 100.

Observers

Observers subscribe to observables and are notified when the observers emit data. They can listen to the next value or error emitted by the observable. The Observer class is the interface for observers. It has four methods that you can override when making an observer:

  • onComplete: When the observable has finished sending data
  • onNext: When the observable has sent new data
  • onSubscribe: When an observer is subscribed to an observable
  • onError: When the observable encountered an error

To subscribe to an observable, you can call Observable.subscribe() passing in a new instance of the Observer interface. For example, if you want to subscribe to an observable of even numbers from 2 to 10, you can do the following:

Observable.fromIterable(listOf(2, 4, 6, 8, 10))

    .subscribe(object : Observer<Int> {

        override fun onComplete() {

            println(“completed”)

        }

        override fun onSubscribe(d: Disposable) {

            println(“subscribed”)

        }

        override fun onNext(t: Int) {

            println(“next integer is $t”)

        }

        override fun onError(e: Throwable) {

            println(“error encountered”)

        }

    })

With this code, the observer will print the next integer. It will also print text when it has subscribed, when the observable is completed, and when it encounters an error.

Observable.subscribe() has different overloaded functions wherein you can pass the onNextonErroronComplete, and onSubscribe parameters. These functions return a disposable object. You can call its dispose function when closing an activity to prevent memory leaks. For example, you can use a variable for the disposable object:

val disposable = observable

            …

            .subscribe(…)

Then, in the onDestroy function of the activity where you’ve made the observable, you can call disposable.dispose() to stop the observers from listening to the observable:

override fun onDestroy() {

    super.onDestroy()

    disposable.dispose()

}

Aside from the observables, observers, and operators, you also need to learn about RxJava schedulers, which will be covered in the next section.

Schedulers

By default, RxJava is synchronous. This means all processes are done in the same thread. There are some tasks that take a while, such as database and network operations, which need to be made asynchronous or run in parallel in another thread. To do that, you need to use schedulers.

Schedulers allow you to control the thread where the actions will be running. There are two functions you can use: observeOn and subscribeOn. You can set which thread your observable will run on with the subscribeOn function. The observeOn function allows you to set where the next operators will be executed.

For example, if you have the getData function, which fetches data from the network and returns an observable, you can subscribe to Schedulers.io and observe the Android main UI thread with AndroidSchedulers.mainThread():

val observable = getData()

   .subscribeOn(Schedulers.io())

   .observeOn(AndroidSchedulers.mainThread())

   …

AndroidSchedulers is part of RxAndroid, which is an extension of RxJava for Android. You will need RxAndroid to use RxJava in Android app development.

In the next section, you will learn how to add RxJava and RxAndroid to your project.

Adding RxJava to Your Project

You can add RxJava to your project by adding the following code to your app/build.gradle file dependencies:

implementation ‘io.reactivex.rxjava3:rxandroid:3.0.0’

implementation ‘io.reactivex.rxjava3:rxjava:3.0.7’

This will add both the RxJava and RxAndroid libraries to your Android project. The RxAndroid library already includes RxJava but it is better to still add the RxJava dependency as the one bundled with RxAndroid might not be the latest version.

Using RxJava in an Android Project

RxJava has several benefits, one of which is handling long-running operations such as network requests in a non-UI thread. The result of a network call can be converted to an observable. You can then create an observer that will subscribe to the observable and present the data. Before displaying the data to the user, you can transform the data with RxJava operators.

If you are using Retrofit, you can convert the response to an RxJava observable by adding a call adapter factory. First, you would need to add adapter-rxjava3 in your app/build.gradle file dependencies with the following:

implementation ‘com.squareup.retrofit2:adapter-rxjava3:2.9.0’

With this, you can use RxJava3CallAdapterFactory as the call adapter in your Retrofit instance. You can do that with the following code:

val retrofit = Retrofit.Builder()

    …

    .addCallAdapterFactory(RxJava3CallAdapterFactory.create())

    …

Now, your Retrofit methods can return Observable objects that you can listen to in your code. For example, in your getMovies Retrofit method that calls your movie endpoint, you can use the following:

@GET(“movie”)

fun getMovies() : Observable<Movie>

Let’s try what you have learned so far by adding RxJava to an Android project.

Exercise 13.01: Using RxJava in an Android Project

For this chapter, you will be working with an application that displays popular movies using The Movie Database API. Go to https://developers.themoviedb.org/ and register for an API key. In this exercise, you will be using RxJava to fetch the list of all popular movies from the movie/popular endpoint, regardless of year:

  1. Create a new project in Android Studio. Name your project Popular Movies and use the package name com.example.popularmovies.

2. Set the location where you want to save the project, then click the Finish button.

3. Open AndroidManifest.xml and add the INTERNET permission:

<uses-permission android:name="android.permission.INTERNET" />

This will allow you to use the device’s internet connection to do network calls.

4. Open your app/build.gradle file and add the kotlin-parcelize plugin at the end of the plugins block:

plugins {
  ...id 'kotlin-parcelize'
}
This will allow you to use Parcelable
for the model class.

5. Add the following in the android block:

compileOptions {
  sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
  jvmTarget = '1.8'
}

These will allow you to use Java 8 for your project.

6. Add the following dependencies in your app/build.gradle file:

implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
implementation 'io.reactivex.rxjava3:rxjava:3.0.7'
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
implementation 'com.github.bumptech.glide:glide:4.11.0'

These lines will add the RecyclerView, Glide, Retrofit, RxJava, RxAndroid, and Moshi libraries to your project.

7. Create a dimens.xml file in the res/values directory and add a layout_margin dimension value:

< resources > < dimen name = "layout_margin" > 16 dp < /dimen></resources >

This will be used for the vertical and horizontal margins of the views.

8. Create a new layout file named view_movie_item.xml and add the following:

view_movie_item.xml9 < ImageView10 android: id = "@+id/movie_poster"
android: layout_width = "match_parent"
android: layout_height = "240dp"
android: contentDescription = "Movie Poster"
app: layout_constraintBottom_toBottomOf = "parent"
app: layout_constraintEnd_toEndOf = "parent"
app: layout_constraintStart_toStartOf = "parent"
app: layout_constraintTop_toTopOf = "parent"
tools: src = "@tools:sample/backgrounds/scenic" / > < TextView android: id = "@+id/movie_title"
android: layout_width = "match_parent"
android: layout_height = "wrap_content"
android: layout_marginStart = "@dimen/layout_margin"
android: layout_marginEnd = "@dimen/layout_margin"
android: ellipsize = "end"
android: gravity = "center"
android: lines = "1"
android: textSize = "20sp"
app: layout_constraintEnd_toEndOf = "@id/movie_poster"
app: layout_constraintStart_toStartOf = "@id/movie_poster"
app: layout_constraintTop_toBottomOf = "@id/movie_poster"
tools: text = "Movie" / >

This layout file, containing the movie poster and title text, will be used for each movie in the list.

9. Open activity_main.xml. Replace the Hello World TextView with RecyclerView:

< androidx.recyclerview.widget.RecyclerView android: id = "@+id/movie_list"
android: layout_width = "match_parent"
android: layout_height = "match_parent"
app: layoutManager = "androidx.recyclerview.widget.GridLayoutManager"
app: layout_constraintBottom_toBottomOf = "parent"
app: layout_constraintTop_toTopOf = "parent"
app: spanCount = "2"
tools: listitem = "@layout/view_movie_item" / >

This RecyclerView will be displaying the list of movies. It will be using GridLayoutManager with two columns.

10. Create a new package, com.example.popularmovies.model, for your model class. Make a new model class named Movie with the following:

@Parcelizedata class Movie(val adult: Boolean = false, val backdrop_path: String = "", val id: Int = 0, val original_language: String = "", val original_title: String = "", val overview: String = "", val popularity: Float = 0 f, val poster_path: String = "", val release_date: String = "", val title: String = "", val video: Boolean = false, val vote_average: Float = 0 f, val vote_count: Int = 0): Parcelable

This will be the model class representing a Movie object from the API.

11. Create a new activity named DetailsActivity with activity_details.xml as the layout file.

12. Open the AndroidManifest.xml file and add MainActivity as the value for the parentActivityName attribute of DetailsActivity:

<activity android:name=".DetailsActivity"            android:parentActivityName=".MainActivity" />

This will add an up icon in the details activity to go back to the main screen.

13. Open activity_details.xml. Add the required views.

activity_details.xml9 < ImageView10 android: id = "@+id/movie_poster"
android: layout_width = "160dp"
android: layout_height = "160dp"
android: layout_margin = "@dimen/layout_margin"
android: contentDescription = "Poster"
app: layout_constraintStart_toStartOf = "parent"
app: layout_constraintTop_toTopOf = "parent"
tools: src = "@tools:sample/avatars" / > < TextView android: id = "@+id/title_text"
style = "@style/TextAppearance.AppCompat.Title"
android: layout_width = "0dp"
android: layout_height = "wrap_content"
android: layout_margin = "@dimen/layout_margin"
android: ellipsize = "end"
android: maxLines = "4"
app: layout_constraintEnd_toEndOf = "parent"
app: layout_constraintStart_toEndOf = "@+id/movie_poster"
app: layout_constraintTop_toTopOf = "parent"
tools: text = "Title" / >

This will add the poster, title, release, and overview on the details screen.

14. Open DetailsActivity and add the following:class DetailsActivity :

AppCompatActivity() {
  companion object {
    const val EXTRA_MOVIE = "movie"
    const val IMAGE_URL = "https://image.tmdb.org/t/p/w185/"
  }
  override fun onCreate(savedInstanceState: Bundle ? ) {
    super.onCreate(savedInstanceState) setContentView(R.layout.activity_details) val titleText: TextView = findViewById(R.id.title_text) val releaseText: TextView = findViewById(R.id.release_text) val overviewText: TextView = findViewById(R.id.overview_text) val poster: ImageView = findViewById(R.id.movie_poster) val movie = intent.getParcelableExtra < Movie > (EXTRA_MOVIE) movie?.run {
      titleText.text = title releaseText.text = release_date.take(4) overviewText.text = "Overview: $overview"
      Glide.with(this @DetailsActivity).load("$IMAGE_URL$poster_path").placeholder(R.mipmap.ic_launcher).fitCenter().into(poster)
    }
  }
}

This will display the poster, title, release, and overview of the movie selected.

15. Create an adapter class for the movie list. Name the class MovieAdapter. Add the following:

class MovieAdapter(private val clickListener: MovieClickListener): RecyclerView.Adapter < MovieAdapter.MovieViewHolder > () {
  private val movies = mutableListOf < Movie > () override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.view_movie_item, parent, false) return MovieViewHolder(view)
  }
  override fun getItemCount() = movies.size override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
    val movie = movies[position] holder.bind(movie) holder.itemView.setOnClickListener {
      clickListener.onMovieClick(movie)
    }
  }
  fun addMovies(movieList: List < Movie > ) {
    movies.addAll(movieList) notifyItemRangeInserted(0, movieList.size)
  }
}

This class will be the adapter for your RecyclerView.

16. Add ViewHolder for your class after the onBindViewHolder function:

class MovieAdapter......class MovieViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
  private val imageUrl = "https://image.tmdb.org/t/p/w185/"
  private val titleText: TextView by lazy {
    itemView.findViewById(R.id.movie_title)
  }
  private val poster: ImageView by lazy {
    itemView.findViewById(R.id.movie_poster)
  }
  fun bind(movie: Movie) {
    titleText.text = movie.title Glide.with(itemView.context).load("$imageUrl${movie.poster_path}").placeholder(R.mipmap.ic_launcher).fitCenter().into(itemView.poster)
  }
}
}

This will be the ViewHolder used by MovieAdapter for the RecyclerView.

17. Below the MovieViewHolder declaration, add MovieClickListener:

class MovieAdapter......interface MovieClickListener {
  fun onMovieClick(movie: Movie)
}
}

This interface will be used when clicking on a movie to view the details.

18. Create another class named PopularMoviesResponse in the com.example.popularmovies.model package:

data class PopularMoviesResponse (    val page: Int,    val results: List<Movie>)

This will be the model class for the response you get from the API endpoint for popular movies.

19. Create a new package, com.example.popularmovies.api, and add a MovieService interface with the following contents:

interface MovieService {
  @GET("movie/popular") fun getPopularMovies(@Query("api_key") apiKey: String): Observable < PopularMoviesResponse >
}

This will define the endpoint you will be using to retrieve the popular movies.

20. Create a MovieRepository class with a constructor for movieService:

class MovieRepository(private val movieService: MovieService) { ... }

21. Add the apiKey (with the value of the API key from The Movie Database API) and a fetchMovies function to retrieve the list from the endpoint:

private val apiKey = "your_api_key_here"fun fetchMovies() = movieService.getPopularMovies(apiKey)

22. Create an application class named MovieApplication with a property for movieRepository:class MovieApplication :

Application() {
  lateinit
  var movieRepository: MovieRepository
}

23. Override the onCreate function of the MovieApplication class and initialize movieRepository:

override fun onCreate() {
  super.onCreate() val retrofit = Retrofit.Builder().baseUrl("https://api.themoviedb.org/3/").addConverterFactory(MoshiConverterFactory.create()).addCallAdapterFactory(RxJava3CallAdapterFactory.create()).build() val movieService = retrofit.create(MovieService::class.java) movieRepository = MovieRepository(movieService)
}

24. Set MovieApplication as the value for the android:name attribute of the application in the AndroidManifest.xml file:

<application    ...    android:name=".MovieApplication"    ... />

25. Create a MovieViewModel class with a constructor for movieRepository:

class MovieViewModel(private val movieRepository: MovieRepository) : ViewModel() { ... }

26. Add properties for popularMovieserror, and disposable:

private val popularMoviesLiveData = MutableLiveData < List < Movie >> () private val errorLiveData = MutableLiveData < String > () val popularMovies: LiveData < List < Movie >> get() = popularMoviesLiveDataval error: LiveData < String > get() = errorLiveDataprivate
var disposable = CompositeDisposable()

27. Define the fetchPopularMovies function. Inside the function, get the popular movies from movieRepository

fun fetchPopularMovies() {
  disposable.add(movieRepository.fetchMovies().subscribeOn(Schedulers.io()).map {
    it.results
  }.observeOn(AndroidSchedulers.mainThread()).subscribe({
    popularMoviesLiveData.postValue(it)
  }, {
    error -> errorLiveData.postValue("An error occurred: ${error.message}")
  }))
}

This will fetch the popular movies asynchronously in the Schedulers.io thread when subscribed and will return an observable and with operators on the main thread.

28. Override the onCleared function of the MovieViewModel and dispose of the disposable:   

override fun onCleared() {
  super.onCleared() disposable.dispose()
}

This will dispose of the disposable when the ViewModel has been cleared, like when the activity has been closed.

29. Open MainActivity and add define a field for the movie adapter:

private val movieAdapter by lazy {
  MovieAdapter(object: MovieAdapter.MovieClickListener {
    override fun onMovieClick(movie: Movie) {
      openMovieDetails(movie)
    }
  })
}

This will have a listener that will open the details screen when a movie is clicked.

30. In the onCreate function, set the adapter for the movie_list RecyclerView:

val recyclerView: RecyclerView = findViewById(R.id.movie_list) recyclerView.adapter = movieAdapter

31. Create a getMovies function on MainActivity. Inside, initialize movieRepository and movieViewModel:    

private fun getMovies() {
  val movieRepository = (application as MovieApplication).movieRepository val movieViewModel = ViewModelProvider(this, object: ViewModelProvider.Factory {
    override fun < T: ViewModel ? > create(modelClass: Class < T > ) : T {
      return MovieViewModel(movieRepository) as T
    }
  }).get(MovieViewModel::class.java)
}

32. At the end of the getMovies function, add an observer to the popularMovies and error LiveData from movieViewModel:

private fun getMovies() {
  ...movieViewModel.fetchPopularMovies() movieViewModel.popularMovies.observe(this, {
    popularMovies -> movieAdapter.addMovies(popularMovies)
  }) movieViewModel.error.observe(this, {
    error -> Toast.makeText(this, error, Toast.LENGTH_LONG).show()
  })
}

33. At the end of the onCreate function of the MainActivity class, call the getMovies() function:getMovies()

34. Add the openMovieDetails function to open the details screen when clicking on a movie from the list:

private fun openMovieDetails(movie: Movie) {
  val intent = Intent(this, DetailsActivity::class.java).apply {
    putExtra(DetailsActivity.EXTRA_MOVIE, movie)
  }
  startActivity(intent)
}

Run your application. You will see that the app will display a list of popular movie titles:Figure 13.1: How the Popular Movies app will look
Figure 13.1: How the Popular Movies app will look

Click on a movie, and you will see its details, such as its release date and an overview:

Figure 13.2: The movie details screen

Figure 13.2: The movie details screen

You have learned how to use RxJava to retrieve the response from an external API. In the next section, you will convert the data you fetched into the data that you need to display with RxJava operators.

Modifying Data with RxJava Operators

When you have an observable that emits data, you can use operators to modify the data before passing it to the observers. You can use a single operator or a chain of operators to get the data that you want. There are different types of operators that you can use, such as transforming operators and filtering operators.

Transforming operators can modify items from the observable into your preferred data. The flatMap() operator transforms the items into an observable. In Exercise 13.01, Using RxJava in an Android Project, you transformed the observable of PopularMoviesResponse into an observable of Movies with the following:

.flatMap { Observable.fromIterable(it.results) }

Another operator that can transform data is map. The map(x) operator applies a function x to each item and returns another observable with the updated values. For example, if you have an observable of a list of numbers, you can transform it into another observable list with each number multiplied by 2 with the following:

.map { it * 2 }

Filtering operators allow you to select only some of the items. With filter(), you can select items based on a set condition. For example, you can filter odd numbers with the following:

.filter { it % 2 != 0 }

The first() and last() operators allow you to get the first and last item, while with take(n) or takeLast(n), you can get n first or last items. There are other filtering operators such as debounce()distinct()elementAt()ignoreElements()sample()skip(), and skipLast().

There are many other RxJava operators you can use. Let’s try to use RxJava operators in an Android project.

Exercise 13.02: Using RxJava Operators

In the previous exercise, you used RxJava to fetch the list of popular movies from The Movie Database API. Now, before displaying them in RecyclerView, you will be adding operators to sort the movies by title and only get the movies released last month:

  1. Open the Popular Movies project, Using RxJava in an Android Project.
  2. Open MovieViewModel and navigate to the fetchPopularMovies function.
  3. You will be modifying the app to only display popular movies for this year. Replace .map { it.results } with the following:.flatMap { Observable.fromIterable(it.results) }.toList()This will convert the Observable of MovieResponse into an observable of Movies.
  4. Before the toList() call, add the following:.filter {    val cal = Calendar.getInstance()    cal.add(Calendar.MONTH, -1)    it.release_date.startsWith(        “${cal.get(Calendar.YEAR)}-${cal.get(Calendar.MONTH) + 1}”    )}This will select only the movies released in the previous month.
  5. Run the application. You will see that the other movies are no longer displayed. Only those released this year will be on the list:Figure 13.3: The app with the year’s popular movies
Figure 13.3: The app with the year’s popular movies
  6. You’ll also notice that the movies displayed are not in alphabetical order. Sort the movies by using the sorted operator before the toList() call:.sorted { movie, movie2 -> movie.title.compareTo(movie2.title) }This will sort the movies based on their titles.
  7. Run the application. You will see that the list of movies is now sorted alphabetically by title:Figure 13.4: The app with the year’s popular movies sorted by title
Figure 13.4: The app with the year’s popular movies sorted by title
  8. Before the toList() call, use the map operator to map the list of movies into another list whose title is in uppercase:.map { it.copy(title = it.title.toUpperCase(Locale.getDefault())) }
  9. Run the application. You will see that the movie titles are now in uppercase letters:Figure 13.5: The app with the movie titles in uppercase
Figure 13.5: The app with the movie titles in uppercase
  10. Before the toList() call, use the take operator to only get the first four movies from the list:.take(4)
  11. Run the application. You will see that the RecyclerView will only show four movies:Figure 13.6: The app with only four movies
Figure 13.6: The app with only four movies
  12. Try other RxJava operators and run the application to see the results.

You have learned how to use RxJava operators to manipulate the retrieved response from an external API before displaying them in the RecyclerView.

In the next section, you will learn how to use coroutines instead of RxJava to get data from an external API.

Coroutines

Coroutines were added in Kotlin 1.3 for managing background tasks such as making network calls and accessing files or databases. Kotlin coroutines are Google’s official recommendation for asynchronous programming on Android. Their Jetpack libraries, such as LifeCycle, WorkManager, and Room, now include support for coroutines.

With coroutines, you can write your code in a sequential way. The long-running task can be made into a suspending function, which when called can pause the thread without blocking it. When the suspending function is done, the current thread will resume execution. This will make your code easier to read and debug.

To mark a function as a suspending function, you can add the suspend keyword to it; for example, if you have a function that calls the getMovies function, which fetches movies from your endpoint and then displays it:

val movies = getMovies()

displayMovies(movies)

You can make the getMovies() function a suspending function by adding the suspend keyword:

suspend fun getMovies(): List<Movies> { … }

Here, the calling function will invoke getMovies and pause. After getMovies returns a list of movies, it will resume its task and display the movies.

Suspending functions can only be called in suspending functions or from a coroutine. Coroutines have a context, which includes the coroutine dispatcher. Dispatchers specify what thread the coroutine will use. There are three dispatchers you can use:

  • Dispatchers.Main: Used to run on Android’s main thread
  • Dispatchers.IO: Used for network, file, or database operations
  • Dispatchers.Default: Used for CPU-intensive work

To change the context for your coroutine, you can use the withContext function for the code that you want to use a different thread with. For example, in your suspending function, getMovies, which gets movies from your endpoint, you can use Dispatchers.IO:

suspend fun getMovies(): List<Movies>  {

    withContext(Dispatchers.IO) { … }

}

In the next section, we will cover how to create coroutines.

Creating Coroutines

You can create a coroutine with the async and launch keywords. The launch keyword creates a coroutine and doesn’t return anything. On the other hand, the async keyword returns a value that you can get later with the await function.

async and launch must be created from CoroutineScope, which defines the lifecycle of the coroutine. For example, the coroutine scope for the main thread is MainScope. You can then create coroutines with the following:

MainScope().async { … }

MainScope().launch { … }

You can also create your own CoroutineScope instead of using MainScope by creating one with CoroutineScope and passing in the context for the coroutine. For example, to create CoroutineScope for use on a network call, you can define the following:

val scope = CoroutineScope(Dispatchers.IO)

The coroutine can be canceled when the function is no longer needed, like when you close the activity. You can do that by calling the cancel function from CoroutineScope:

scope.cancel()

A ViewModel also has a default CoroutineScope for creating coroutines: viewModelScope. Jetpack’s LifeCycle also has the lifecycleScope that you can use. viewModelScope is canceled when the ViewModel has been destroyed; lifecycleScope is also canceled when the lifecycle is destroyed. Thus, you no longer need to cancel them.

In the next section, you will be learning how to add coroutines to your project.

Adding Coroutines to Your Project

You can add coroutines to your project by adding the following code to your app/build.gradle file dependencies:

implementation “org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9”

implementation “org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9”

kotlinx-coroutines-core is the main library for coroutines while kotlinx-coroutines-android adds support for the main Android thread.

You can add coroutines in Android when making a network call or fetching data from a local database.

If you’re using Retrofit 2.6.0 or above, you can mark the endpoint function as a suspending function with suspend:

@GET(“movie/latest”)

suspend fun getMovies() : List<Movies>

Then, you can create a coroutine that will call the suspending function getMovies and display the list:

CoroutineScope(Dispatchers.IO).launch {

    val movies = movieService.getMovies()

    withContext(Dispatchers.Main) {

        displayMovies(movies)

    }

}

You can also use LiveData for the response of your coroutines. LiveData is a Jetpack class that can hold observable data. You can add LiveData to your Android project by adding the following dependency:

implementation ‘androidx.lifecycle:lifecycle-livedata-ktx:2.2.0’

Let’s try to use coroutines in an Android project.

Exercise 13.03: Using Coroutines in an Android App

In this exercise, you will be using coroutines to fetch the list of popular movies from The Movie Database API. You can use the Popular Movies project in the previous exercise or make a copy of it:

  1. Open the Popular Movies project in Android Studio.

2. Open the app/build.gradle file and remove the following dependencies:

implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
implementation 'io.reactivex.rxjava3:rxjava:3.0.7'

These dependencies will no longer be needed as you will be using coroutines instead of RxJava.

3. In the app/build.gradle file, add the dependencies for the Kotlin coroutines:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'

These will allow you to use coroutines in your project.

4. Also, add the dependencies for the ViewModel and LiveData extension libraries:

implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'

5. Open the MovieService interface and replace it with the following code:

interface MovieService {
  @GET("movie/popular") suspend fun getPopularMovies(@Query("api_key") apiKey: String): PopularMoviesResponse
}

This will mark getPopularMovies as a suspending function.

6. Open MovieRepository and add the movies and error LiveData for the list of movies: 

private val movieLiveData = MutableLiveData < List < Movie >> () private val errorLiveData = MutableLiveData < String > () val movies: LiveData < List < Movie >> get() = movieLiveData val error: LiveData < String > get() = errorLiveData

7. Replace the fetchMovies function with a suspending function to retrieve the list from the endpoint:   

suspend fun fetchMovies() {
  try {
    val popularMovies = movieService.getPopularMovies(apiKey) movieLiveData.postValue(popularMovies.results)
  } catch (exception: Exception) {
    errorLiveData.postValue("An error occurred: ${exception.message}")
  }
}

8. Update the contents of MovieViewModel with the following code:   

init {
  fetchPopularMovies()
}
val popularMovies: LiveData < List < Movie >> get() = movieRepository.movies fun getError(): LiveData < String > = movieRepository.error private fun fetchPopularMovies() {
  viewModelScope.launch(Dispatchers.IO) {
    movieRepository.fetchMovies()
  }
}

The fetchPopularMovies function has a coroutine, using viewModelScope, that will fetch the movies from movieRepository.

9. Open the MovieApplication file. In the onCreate function, remove the line containing addCallAdapterFactory. It should look like this:  

override fun onCreate() {
  super.onCreate() val retrofit = Retrofit.Builder().baseUrl("https://api.themoviedb.org/3/").addConverterFactory(MoshiConverterFactory.create()).build()...
}

10. Open the MainActivity class. Delete the getMovies function.

11. In the onCreate function, remove the call to getMovies. Then, at the end of the onCreate function, create movieViewModel:

val movieRepository = (application as MovieApplication).movieRepositoryval movieViewModel = ViewModelProvider(this, object: ViewModelProvider.Factory {
  override fun < T: ViewModel ? > create(modelClass: Class < T > ) : T {
    return MovieViewModel(movieRepository) as T
  }
}).get(MovieViewModel::class.java)

12. After that, add an observer to the getPopularMovies and error LiveData from movieViewModel:     

movieViewModel.popularMovies.observe(this, {
  popularMovies -> movieAdapter.addMovies(popularMovies.filter {
    it.release_date.startsWith(Calendar.getInstance().get(Calendar.YEAR).toString())
  }.sortedBy {
    it.title
  })
}) movieViewModel.getError().observe(this, {
  error -> Toast.makeText(this, error, Toast.LENGTH_LONG).show()
})

This will update the activity’s RecyclerView with the movies fetched. The list of movies is filtered using Kotlin’s filter function to only include movies released this year. They are then sorted by title using Kotlin’s sortedBy function.

13. Run the application. You will see that the app will display a list of popular movie titles from the current year, sorted by title:

Figure 13.7: The app displaying popular movies released this year, sorted by title

Figure 13.7: The app displaying popular movies released this year, sorted by title

You have used coroutines and LiveData to retrieve and display a list of popular movies from a remote data source without blocking the main thread.

Before passing the LiveData into the UI for display, you can also transform the data first. You will learn about that in the next section.

Transforming LiveData

Sometimes, the LiveData you pass from the ViewModel to the UI layer needs to be processed first before displaying. For example, you can only select a part of the data or do some processing on it first. In the previous exercise, you filtered the data to only select popular movies from the current year.

To modify LiveData, you can use the Transformations class. It has two functions, Transformations.map and Transformations.switchMap, that you can use.

Transformations.map modifies the value of LiveData into another value. This can be used for tasks like filtering, sorting, or formatting the data. For example, you can transform movieLiveData into string LiveData from the movie’s title:

private val movieLiveData: LiveData<Movie>

val movieTitleLiveData : LiveData<String> =

   Transformations.map(movieLiveData) { it.title }

When movieLiveData changes value, movieTitleLiveData will also change based on the movie’s title.

With Transformations.switchMap, you can transform the value of a LiveData into another LiveData. This is used when you want to do a specific task involving a database or network operation with the original LiveData. For example, if you have a LiveData representing a movie id object, you can transform that to movie LiveData by applying the function getMovieDetails, which returns LiveData of movie details from the id object (such as from another network or database call):

private val idLiveData: LiveData<Int> = MutableLiveData()

val movieLiveData : LiveData<Movie> =

    Transformations.switchMap(idLiveData) { getMovieDetails(it) }

fun getMovieDetails(id: Int) : LiveData<Movie> = { … }

Let’s use LiveData transformations on the list of movies fetched using coroutines.

Exercise 13.04: LiveData Transformations

In this exercise, you will be transforming the LiveData list of movies before passing them to the observers in the MainActivity file:

  1. In Android Studio, open the Popular Movies project you worked on in the previous exercise.
  2. Open the MainActivity file. In the movieViewModel.popularMovies observer in the onCreate function, remove the filter and sortedBy function calls. The code should look like the following:movieViewModel.getPopularMovies().observe(this, Observer { popularMovies ->    movieAdapter.addMovies(popularMovies)})This will now display all movies in the list without them being sorted by title.
  3. Run the application. You should see all movies (even those from the past year), not sorted by title:Figure 13.8: The app with unsorted popular movies
Figure 13.8: The app with unsorted popular movies
  4. Open the MovieViewModel class and update popularMovies with LiveData transformations to filter and sort the movies:        val popularMovies: LiveData<List<Movie>>        get() = movieRepository.movies.map { list ->        list.filter {            val cal = Calendar.getInstance()            cal.add(Calendar.MONTH, -1)            it.release_date.startsWith(                “${cal.get(Calendar.YEAR)}-${cal.get(Calendar.MONTH) + 1}”            )        }.sortedBy { it.title }    }This will select the movies released last month and sort them by title before passing them to the UI observer in MainActivity.
  5. Run the application. You will see that the app shows a list of popular movies from the current year, sorted by title:
Figure 13.9: The app with the movies released this year sorted by title

Figure 13.9: The app with the movies released this year sorted by title

You have used LiveData transformations to modify the list of movies to select only the ones released this year. They were also sorted by title before passing them to the observers in the UI layer.

Coroutines Channels and Flows

If your coroutine is fetching a stream of data or you have multiple data sources and you process the data one by one, you can use either Channel or Flow.

Channels allow you to pass data between different coroutines. They are a hot stream of data. It will run and emit values the moment they are called, even when there’s no listeners. Flows, meanwhile, are cold asynchronous streams. They only emit values when the values are collected.

To learn more about Channels and Flows, you can go to https://kotlinlang.org.

RxJava versus Coroutines

Both RxJava and coroutines can be used for doing background tasks in Android, such as network calls or database operations.

Which one should you use then? While you can use both in your application, for example, RxJava for one task and coroutines for another, you can also use them together with LiveDataReactiveStreams or kotlinx-coroutines-rx3. This, however, will increase the number of dependencies you use and the size of your application.

So, RxJava or coroutines? The following table shows the differences between the two:

Figure 13.10: Differences between coroutines and RxJava

Figure 13.10: Differences between coroutines and RxJava

Let’s move on to the next activity.

Activity 13.01: Creating a TV Guide App

A lot of people watch television. Most of the time, though, they are not sure what TV shows are currently on the air. Suppose you wanted to develop an app that can display a list of these shows from The Movie Database API’s tv/on_the_air endpoint using Kotlin coroutines and LiveData.

The app will have two screens: the main screen and the details screen. On the main screen, you will display a list of the TV shows that are on the air. The TV shows will be sorted by name. Clicking on a TV show will open the details screen, which displays more information about the selected TV show.

Steps for completion:

  1. Create a new project in Android Studio and name it TV Guide. Set its package name.
  2. Add the INTERNET permission in the AndroidManifest.xml file.
  3. Add the Java 8 compatibility and the dependencies for the RecyclerView, Glide, Retrofit, RxJava, RxAndroid, Moshi, ViewModel, and LiveData libraries in your app/build.gradle file.
  4. Add a layout_margin dimension value.
  5. Create a view_tv_show_item.xml layout file with ImageView for the poster and TextView for the name of the TV show.
  6. In the activity_main.xml file, remove the Hello World TextView and add a RecyclerView for the list of TV shows.
  7. Create a model class, TVShow.
  8. Create a new activity named DetailsActivity with activity_details.xml as the layout file.
  9. Open the AndroidManifest.xml file and add the parentActivityName attribute in the DetailsActivity declaration.
  10. In activity_details.xml, add the views for the details of the TV show.
  11. In DetailsActivity, add the code for displaying the details of the TV show selected.
  12. Create a TVShowAdapter adapter class for the list of TV shows.
  13. Create another class named TVResponse for the response you get from the API endpoint for the TV shows on air.
  14. Create a TelevisionService class for adding the Retrofit method.
  15. Create a TVShowRepository class with a constructor for tvService, and properties for apiKey and tvShows.
  16. Create a suspending function to retrieve the list of TV shows from the endpoint.
  17. Create a TVShowViewModel class with a constructor for TVShowRepository. Add a getTVShows function that returns the LiveData for the list of TV shows and fetchTVShows that fetches the list from the repository.
  18. Create an application class named TVApplication with a property for TVShowRepository.
  19. Set TVApplication as the value for the application in the AndroidManifest.xml file.
  20. Open MainActivity and add the code to update the RecyclerView when the LiveData from ViewModel updates its value. Add a function that will open the details screen when clicking on a TV show from the list.
  21. Run your application. The app will display a list of TV shows. Clicking on a TV show will open the details activity, which displays the show details. The main screen and details screen will be similar to the following figure:
Figure 13.11: The main screen and details screen of the TV Guide app

Figure 13.11: The main screen and details screen of the TV Guide app

Summary

This chapter focused on doing background operations with RxJava and coroutines. Background operations are used for long-running tasks such as accessing data from the local database or a remote server.

You started with the basics of RxJava: observables, observers, and operators. Observables are the data sources that provide data. The observers listen to observables; when an observable emits data, observers can react accordingly. Operators allow you to modify data from an observable to the data you need before it can be passed to the observers.

Next, you learned how to make RxJava calls asynchronous with schedulers. Schedulers allow you to set the thread through which the required action will be done. The subscribeOn function is used for setting which thread your observable will run on, and the observeOn function allows you to set where the next operators will be executed. You then fetched data from an external API with RxJava and used RxJava operators to filter, sort, and make modifications to the data.

Next, you learned about using Kotlin coroutines, which is Google’s recommended solution for asynchronous programming. You can make a background task into a suspending function with the suspend keyword. Coroutines can be started with the async or launch keywords.

You’ve learned how to create suspending functions and how to start coroutines. You also used dispatchers to change the thread where a coroutine runs. Finally, you used coroutines for doing network calls and modified the data retrieved with the LiveData transformation functions map and switchMap.

Written by

XR Developer responsible for end-to-end development of XR solutions spanning multiple domains, by using various XR and WebXR libraries.

Leave a Reply