Chapters

Hide chapters

Reactive Programming with Kotlin

Second Edition · Android 10 · Kotlin 1.3 · Android Studio 4.0

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section II: Operators & Best Practices

Section 2: 7 chapters
Show chapters Hide chapters

20. RxPermissions
Written by Alex Sullivan

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

Starting in Android Marshmallow, Android developers need to ask for certain permissions at runtime to allow the user a chance to reject those permissions without rejecting the entire app. For the most part, it’s been a great change to the Android ecosystem. However, it has also come with a non-trivial amount of developer pain.

Most Android developers are intimately familiar with the Android flow for requesting a permission. It requires you to request the permission and then handle the result of that permission request in another callback in the activity life cycle. This discrepancy between where you request a permission and where you learn if you’ve gotten it or not is the cause of a lot of headaches.

There’s a helpful library called RxPermissions that you’ll use in this chapter to help alleviate some of these pain points and give you a reactive flow when requesting permissions. What more could you want?

Getting started

Start off by opening the starter project for this chapter. You’ll work on the Wundercast app that you started earlier in the book. Recall that Wundercast allows you to search for a city and see the temperature, humidity and other weather information.

In addition to the location and API key buttons you’ve come to love, there’s also two new buttons at the bottom of the screen for this chapter. The Save icon towards the left will, once you’re done with the chapter, save the currently displayed weather. The Clock icon to its right will then reload the last saved weather and display it in the app. Handy, right?

Wundercast uses the OpenWeatherMap API, so before continuing, make sure you have a valid OpenWeatherMap API key http://openweathermap.org. If you don’t already have a key, you can sign up for one at https://home.openweathermap.org/users/sign_up.

Once you’ve completed the sign-up process, visit the dedicated page for API keys at https://home.openweathermap.org/api_keys and generate a new one.

Then, in the starter project, open the WeatherApi.kt file, take the key you generated above and replace the placeholder in the top of the file:

val apiKey =
  BehaviorSubject.createDefault("INSERT-API-KEY-HERE")

Once that’s done, run the app and make sure you can fetch the weather for your favorite city or town.

Requesting the location permission

When you first started working on Wundercast, the app would immediately request the location permission as soon as the app launched. As we all know, that’s not great user experience. It forces the user to make a quick decision about giving your app the location permission before they have a chance to see why you actually need it. In addition to that, requesting the permission without the proper context can make the user more likely to reject your permission. Instead, it’d be much better if you requested the location permission only after the user clicked the location button in the bottom-left.

textObservable
  .subscribeOn(Schedulers.io())
  .observeOn(AndroidSchedulers.mainThread())
  .subscribe(this::showNetworkResult)
  .addTo(disposables)
location.setOnClickListener {
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    requestPermissions(
      arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
      locationRequestCode)
  }
}
private val locationRequestCode = 101
override fun onRequestPermissionsResult(requestCode: Int,
  permissions: Array<out String>, grantResults: IntArray) {
  super.onRequestPermissionsResult(requestCode, permissions,
    grantResults)
  if (requestCode == locationRequestCode) {
    val result = grantResults[0]
    if (result == PackageManager.PERMISSION_GRANTED) {
      TODO("Fetch the location!")
    }
  }
}
fun updateWeatherFromLocation() {
  cityLiveData.postValue("Current Location")
  lastKnownLocation
    .flatMapSingle {
      WeatherApi.getWeather(it).subscribeOn(Schedulers.io())
    }
    .onErrorResumeWith(Maybe.just(
      WeatherApi.NetworkResult.Success(Weather.empty)
    ))
    .subscribe(this::showNetworkResult)
    .addTo(disposables)
}
private lateinit var model: WeatherViewModel
val model = ...
model = ...
model.updateWeatherFromLocation()

Using RxPermissions

You’ve got a working solution that incorporates permissions, but it took a lot of code, and you had to disrupt the existing reactive setup you had. It required storing more state, i.e., the view model, in your WeatherActivity class as well.

implementation 'com.github.tbruyelle:rxpermissions:0.10.2'
val permissions = RxPermissions(this)
// 1
val locationObservable = location.clicks()
  // 2
  .flatMap {
    permissions
      .request(Manifest.permission.ACCESS_FINE_LOCATION)
  }
  // 3
  .filter { it }
  // 4
  .map { Unit }
RxJavaBridge.toV3Observable(permissions
  .request(Manifest.permission.ACCESS_FINE_LOCATION))
locationObservable.subscribe { 
    model.locationClicked() 
}.addTo(disposables)

Requesting another permission

You’ve got the basics of requesting permissions with RxPermissions down, good job! It’s time to implement the save and restore features mentioned earlier in the chapter.

val saveObservable = save.clicks()
val readObservable = load.clicks()
.flatMap {
  RxJavaBridge.toV3Observable(
    permissions.request(Manifest
      .permission.WRITE_EXTERNAL_STORAGE)
  )
}
.filter { it }
.map { Unit }
saveObservable.subscribe { model.saveClicked() }
  .addTo(disposables)
readObservable.subscribe { model.readSaveClicked() }
  .addTo(disposables)

Reading from external storage

Now that you’re calling both the saveClicked and readSaveClicked methods, it’s time to update the WeatherViewModel to execute the read and save logic.

val readObservable = readSavedClicks
  .subscribeOn(AndroidSchedulers.mainThread())
  .flatMapMaybe { readLastWeather(filesDir) }
  .doOnNext { cityLiveData.postValue(it.cityName) }
  .map { WeatherApi.NetworkResult.Success(it) }
Observable
  .merge(locationObservable, textObservable, readObservable)

Writing the weather to external storage

Saving the weather will be just as easy as reading the weather out of external storage. Add the following block to the bottom of the init method:

saveClicks
  // 1
  .filter { weatherLiveData.value != null }
  .map { weatherLiveData.value!! }
  // 2
  .flatMapCompletable {
    // 3
    it.save(filesDir)
      .doOnComplete {
        snackbarLiveData.postValue(
          "${weatherLiveData.value!!.cityName} weather saved"
        )
      }
  }
  .subscribe()
  .addTo(disposables)

Reacting to orientation changes

RxPermissions is a great library, but there’s one big pain point to watch out for.

.compose(permissions.ensure(
  Manifest.permission.ACCESS_FINE_LOCATION))
fun <T> Observable<T>.ensure(permission: String, rxPermissions: RxPermissions): Observable<Boolean> {
  return RxJavaBridge.toV2Observable(this)
    .compose(rxPermissions.ensure(permission))
    .`as`(RxJavaBridge.toV3Observable())
}
ensure(Manifest.permission.ACCESS_FINE_LOCATION, permissions)
.ensure(Manifest.permission.WRITE_EXTERNAL_STORAGE, permissions)

Key points

  • The RxPermissions library provides an easy mechanism through which to request permissions.
  • You can chain Observables you get back from the library with other Observables just like normal.
  • Keep in mind that the code requesting permissions has to be made in an initialization method (like onCreate or onStart).
  • Use ensure if you’re triggering the permission request off some other event.
  • Use request if you’re triggering the permission request as soon as the page loads.

Where to go from here?

RxPermissions is another great example of the Android community embracing the Rx paradigm. In the next chapter, you’ll dive into yet another library that equally embraces Rx, however, what makes the library interesting is the fact that Google developed the library. Google’s decision to support RxJava through the JetPack components should be a compelling argument in favor of the library. When you’re ready, continue onwards!

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now