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

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.

The starter project for this chapter removed the code to request the location permission on app launch, so you’ll add that back in now.

Start off by commenting out the locationObservable declaration at the top of the init method of WeatherViewModel.

Next up, comment out the Observable.merge block at the end of the init method.

Now, add the following code at the bottom of init, below the commented-out merge code:

textObservable
  .subscribeOn(Schedulers.io())
  .observeOn(AndroidSchedulers.mainThread())
  .subscribe(this::showNetworkResult)
  .addTo(disposables)

Now that you’ve removed the Rx-oriented location code, it’s time to transition to the sad world of imperative programming. But don’t worry, you’ll be back soon!

Moving to WeatherActivity.kt, add the following code at the bottom of the onCreate method:

location.setOnClickListener {
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    requestPermissions(
      arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
      locationRequestCode)
  }
}

You’re setting a click listener on the location ImageView. The click listener will request the location permission if the app is running on a phone with a version of Android at or after Android M. The app is using the Kotlin Android extensions plugin to automatically generate references to views in the layout file, so no need for any pesky findViewById calls.

Now, add the locationRequestCode constant at the top of your MainActivity class:

private val locationRequestCode = 101

You’re now properly requesting the location permission whenever the user taps the Location button. However, you still need to listen for the permission callback and decide what to do from there.

Add the following below the onCreate method:

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!")
    }
  }
}

You’re listening for the onRequestPermissionResult lifecycle method to be called and checking if the user granted the location permission.

Add the following method at the bottom of the WeatherViewModel class:

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

In this method, you’re updating the text displayed to the user and using the lastKnownLocation Maybe<Location> object to fetch the last known location and then sending it through to the activity via the showNetworkResult method.

Head back to the WeatherActivity class. You’ll use the new updateWeatherFromLocation method, but before you do that you’ll need to update the code creating the WeatherViewModel to store the view model as an instance variable.

Before, all the business logic of the app was happening in the onCreate method, so you didn’t need a class-wide reference to the view model. Now that you’re interacting with the view model outside of onCreate, you’ll need that view model reference at a broader scope — such is life in the imperative world.

Add the following line below the locationRequestCode value you added earlier at the top of the WeatherActivity class:

private lateinit var model: WeatherViewModel

Now, change the code creating the view model from this:

val model = ...

To this:

model = ...

You now have a reference to the view model, so you can replace the TODO function in the onRequestPermissionsResult callback with the following:

model.updateWeatherFromLocation()

You should be fetching the weather from the user’s current location if they gave you the location permission. Run the app and ensure that, after clicking the Location button in the bottom-left the app, updates with your city’s weather.

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.

There’s a better way! Add the following dependency to your build.gradle file:

implementation 'com.github.tbruyelle:rxpermissions:0.10.2'

The RxPermissions library provides a reactive wrapper around the permissions flow.

You’re going to go back through the code you just wrote and replace it with the Rx-based version provided by RxPermissions. Head back to the WeatherActivity class and delete the onRequestPermissionResult method.

Also delete the clickListener you set on the location view in the bottom of onCreate.

Right below the block constructing the WeatherViewModel in onCreate, add the following line:

val permissions = RxPermissions(this)

The RxPermissions class is your window into the RxPermissions library. Through it you can request any type of permission, you could request normally via the requestPermissions method.

In order to stick with the reactive theme of the app, you’re going to go back to utilizing the clicks RxBindings method on the location view.

Add the following block below the permissions declaration:

// 1
val locationObservable = location.clicks()
  // 2
  .flatMap {
    permissions
      .request(Manifest.permission.ACCESS_FINE_LOCATION)
  }
  // 3
  .filter { it }
  // 4
  .map { Unit }

Here’s a breakdown of the above:

  1. As mentioned earlier, you’re using the clicks method to get an Observable<Unit> representing user taps on the location view.

  2. You’re then using the flatMap operator to start emitting objects from the permissions.request method. The request method takes in a permission and returns an Observable<Boolean>. If the resulting observable emits true, that means the permission was accepted. If it emits false, it means that the user did not grant the permission.

  3. You’re then using the filter operator to make sure that only successful attempts to get the location permission will progress through the Observable chain.

  4. Finally, you’re using the map operator to convert the Observable back into an Observable<Unit>, which is the initial type returned by the clicks method.

You’re probably noticing the error on the flatMap block. Recall that in the RxPreferences chapter you had to use the rxjava-bridge library to adapt RxJava2 Observables to their RxJava3 counterpart. Just like the RxPreferences library, the library you’re using to handle permission updates in this chapter hasn’t updated to use the RxJava3 library yet. To get this block compiling, replace the body of the flatMap call with the following:

RxJavaBridge.toV3Observable(permissions
  .request(Manifest.permission.ACCESS_FINE_LOCATION))

You’re using the toV3Observable method exposed by the rxjava-bridge library to transform the RxJava2 Observable returned by the request method to an RxJava3 Observable.

Take an extra moment to appreciate what’s happening, here in this rx chain: Instead of going through all of the cruft and hassle of requesting permissions and overriding on the onRequestPermissionsCallback method, RxPermissions gives you a clean, simple interface to request a permission and listen to the results via an Observable.

Since it’s hooked into the RxJava world, you can easily combine it with the result of location.clicks to request a permission anytime a user clicks the Location button. Isn’t that magical?

Now that you don’t need to reference the view model anywhere outside onCreate, delete the lateinit var from the top of WeatherActivity. After you do so, add val in front of model in the onCreate to make that a local constant again.

You can also delete the locationRequestCode at the top of WeatherActivity, since that’s now handled under the hood by RxPermissions.

Finally, make sure to actually subscribe to the locationObservable and forward its results through to your view model. Add the following below the locationObservable declaration:

locationObservable.subscribe { 
    model.locationClicked() 
}.addTo(disposables)

The locationClicked method simply pipes a value through to a subject that’s exposed in your WeatherViewModel. Follow the stream and open up WeatherViewModel.kt again.

Now that you’ve got your locationClicks receiving values, delete the updateWeatherFromLocation method you added earlier and un-comment the Rx blocks declaring locationObservable and using it in the Observable.merge call. Last but not least, delete the Rx block subscribing to textObservable below the call to Observable.merge.

Uninstall the app to reset your location permissions, and then run the project. You should see the app request location permissions and progress through to showing you your areas weather just like before.

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.

First, add two new Observables listening for clicks on the save and load views in WeatherActivity below the existing locationObservable Rx block:

val saveObservable = save.clicks()
val readObservable = load.clicks()

Now, add code requesting the permissions to write on the external storage to both saveObservable and readObservable:

.flatMap {
  RxJavaBridge.toV3Observable(
    permissions.request(Manifest
      .permission.WRITE_EXTERNAL_STORAGE)
  )
}
.filter { it }
.map { Unit }

Just like before, you’re using the request method to request a permission. And just like before you’re using the rxjava-bridge library to bridge between versions of RxJava. This time, you’re requesting the WRITE_EXTERNAL_STORAGE permission so that you can access external storage.

Note: The WRITE_EXTERNAL_STORAGE permission also implicitly gives your app access to read from external storage. However, if you do need to request multiple permissions at once time, you can use the requestEach method exposed by the RxPermissions library.

Now subscribe to your new Observables and pipe the values through to your view model. Add the following below the call subscribing to the locationobservable:

saveObservable.subscribe { model.saveClicked() }
  .addTo(disposables)
readObservable.subscribe { model.readSaveClicked() }
  .addTo(disposables)

Just like before, the save and read save clicked methods on your view model simply trigger existing PublishSubjects defined at the top of the WeatherViewModel class. You’ll use those subjects next.

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.

Add the following to the top of the WeatherViewModel init block:

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

The above code uses the readSavedClicks Observable as a trigger to read the last saved weather object from the external file directory using the helper function readLastWeather, which is a top-level function in the X.kt file. readLastWeather returns a Maybe<Weather>, so if there is no saved weather the maybe will complete without any elements.

It then uses the map operator to convert the Weather object into a WeatherApi.NetworkResult so that it’s compatible with the rest of the WeatherViewModels code.

Next up, you need to display that saved weather to the user.

At the bottom of the init method, add in the readObservable into the call to Observable.merge as follows:

Observable
  .merge(locationObservable, textObservable, readObservable)

And that’s it! You’re now merging three different sources of weather:

  1. The weather that’s produced when the user clicks the Location button.
  2. The weather that’s produced when the user enters some text into the edit text.
  3. The weather that’s produced when the user restores the last saved weather.

You’re now reading a weather object from the external storage, but it’d be nice to write one as well!

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)

Here’s a breakdown of the above:

  1. Use the filter operator to make sure there’s a value currently being displayed by inspecting the weatherLiveData, and then use the map operator to convert the Observable into one emitting the current Weather.
  2. Use the flatMapCompletable operator to flatMap from this Observable into a Completable.
  3. Call the save extension method on the Weather object emitted by the Observable. save returns a Completable representing the completed save operation. Once it completes, post a new value to the snackbar indicating that the weather was saved.

Note: You may be wondering why doOnComplete is called inside the flatMapCompletable block instead of being called before subscribe(). When using flatMapCompletable, the Completable returned by flatMapCompletable will only call onComplete when the source Observable itself completes.

That means that, if doOnComplete was added before subscribe, it would only be called once the saveClicks Observable completed. Since saveClicks is driven by a user interaction, it will never complete! You can get around this trickiness by using the do operators inside the flatMapCompletable block.

Easy! Run the app and fetch the weather, either by using the Location button or by search via city name. Tap the Save icon next to the Location icon at the bottom of the screen. You should see a message confirming that the location was saved.

Now, search for a different city or restart the app and tap the Load button to the right of the Save button. You should see the weather details and name of the city that you just saved.

Reacting to orientation changes

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

Imagine a scenario wherein your user clicked a button and that triggered a permission request, just like in Wundercast.

Then, before the user clicks Accept or Deny, they rotate their phone. As we all know, configuration changes like this result in the operating system destroying your activity and instantiating a new one. The system will re-create the permissions prompt. Then the user clicks Accept or Deny.

We now have a problem. The old activity or fragment, i.e., the one that existed before the user rotated the phone, was observing the permissions Observable returned by the RxPermissions library.

However, the new activity or fragment is not observing for those changes unless you called the request method directly in onCreate or onStart or one of the other initialization Android life cycle methods. It would only start observing permission changes after the user clicked the button that triggered the permission in the first place.

Now, if you’re just calling the request method in onCreate or one of the other initialization life cycle methods, you’re fine because the new activity or fragment will immediately resubscribe to the permissions Observable, and you’ll be all set.

However, that’s not what Wundercast and a lot of other apps do. They depend on a user clicking something before the prompt should be shown.

You can test this bug out for yourself. Uninstall the app, then reinstall it. Click the location button and then rotate the phone before accepting the permission prompt. Once the phone is rotated and the activity has been restarted, accept the prompt. The app will fail to fetch the weather for your current location because it didn’t receive the message that the permission was accepted.

There’s a workaround for this particular issue.

In WeatherActivity, replace the line calling flatMap on the location.clicks method with the following:

.compose(permissions.ensure(
  Manifest.permission.ACCESS_FINE_LOCATION))

There are two new things going on, here:

  1. You’re using the compose method instead of flatMap. We touched briefly on how compose works in Chapter 16, “Creating Custom Reactive Extensions.” If you’d like a quick recap: The compose method allows you to chain custom Observable operators on your Rx chains. For the most part, Kotlin extension functions are a better answer when you’re tempted to use compose, but, in this scenario, the fact that the code passed into compose is executed immediately, allowing the RxPermissions library to look up any existing permissions requests and immediately reroute them to your Observable.
  2. You’re using the ensure method instead of the request method you used earlier. request returns an Observable<Boolean>, whereas ensure returns a ObservableTransformer object. ObservableTransformer is an interface primarily used in the compose method mentioned earlier. Both ensure and request will prompt the user for the permissions you ask for, but ensure is compatible with compose allowing you to recover from orientation changes.

Since the RxPermissions library is still using RxJava2, you again need to go through the effort of moving between RxJava2 and RxJava3 types, otherwise you’ll see an error. Open the X.kt file and add the following method at the bottom of the file:

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

This new ensure method is an extension method on the Observable type. It converts this Observable into an RxJava2 Observable, then uses the RxPermissions libraries compose transformer, and then converts the resulting Observable back into an RxJava3 Observable. Phew, that’s a lot of back and forth!

Back in WeatherActivity, replace the compose call you just added with the following:

ensure(Manifest.permission.ACCESS_FINE_LOCATION, permissions)

Your error should disappear.

Now, uninstall and then run the app again. Tap the Location button and rotate the phone, then accept the permission. You should now see the weather updates you’d expect.

Update the logic in the saveObservable and readObservables to use ensure instead of flatMap as well:

.ensure(Manifest.permission.WRITE_EXTERNAL_STORAGE, permissions)

This compose trick only works if you make sure to subscribe to the Observable that will trigger the permission request in onCreate or onStart depending on if you’re in an activity or a fragment. Since those initialization life cycle methods happen once your fragment or activity is re-created, it allows you to immediately resubscribe to permission updates so you don’t miss any of the action.

One final note: Make sure you’re not running the permission requesting logic directly in onResume. If you request the permission in onResume, your activity will go into a paused state while the permission is shown. Then if the user denies your permission your activity will be resumed, calling onResume. This will then trigger a call to show the permission and so on. Using onCreate allows you to show the prompt immediately or after some event is triggered without being re-run whenever the activity is resumed.

In general, if you’re triggering a permission based off some event, you should be using the ensure method. If you’re just triggering the permission as soon as the activity or fragment is created, feel free to use the request method.

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.
© 2026 Kodeco Inc.