SQLDelight in Android: Getting Started

Aug 3 2021 · Kotlin 1.4, Android 11, Android Studio 4.1

Part 2: Advanced SQLDelight Integrations

12. Integrate with RxJava

Episode complete

Play next episode

Next
About this episode
Leave a rating/review
See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 11. Validate & Test Database Code Next episode: 13. Integrate with Kotlin Coroutines

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Notes: 12. Integrate with RxJava

To refresh your memory about RxJava, the project repository has more info on how it works.

Transcript: 12. Integrate with RxJava

SQLDelight exposes a Query API to execute database functions on demand and allows users to attach listeners that get called whenever the underlying data has been updated. If you’re accustomed with the concepts of the reactive programming library RxJava, this may sound very familiar to you.

In fact, the SQLDelight team has created an extension library for RxJava which makes working with this asynchronous data delivery much easier. Let’s add it to our sample app and change one of the screens to use RxJava Observables instead!

Open the app’s build script and find the addition of the Android driver dependency. Right below it, add a new dependency on the RxJaa Extensions artifact for SQLDelight.

This is available for RxJava 2 and 3, so please choose whichever one fits your codebase best and sync the project afterwards. Also, we need to add a dependency on RxJava itself, of course!

dependencies {
    implementation "com.squareup.sqldelight:rxjava3-extensions:$sqldelightVersion"

    // RxJava
    implementation "io.reactivex.rxjava3:rxjava:3.0.12"
}

If you’re stuck with RxJava 1, you need to write your own conversion library, or use the missing official support as leverage to finally upgrade that stack!

The RxJava extension API for SQLDelight consists of only a handful of methods and a single entrypoint.

Whenever you have your hands on a Query object, it’s possible to call ‘asObservable()’ on it to convert it into an Observable of the Query object.

From there, some more extension functions will convert this stream into a list of objects, a single object, or an Optional value in case the object may be null in the database.

No matter which of these ways you choose for a particular use case, the stream will automatically emit a new value to subscribers when the data is being updated - these are all still Observable streams. No need to handle listeners manually anymore - the call sites become very tidy as a result of this.

The collection overview screen shows the list of collections and automatically updates the UI whenever something changes in that list. We will replace the current listener infrastructure that we built into the ViewModel with an RxJava stream.

Let’s start in the DatabaseRepository and follow the changes down to the UI from there! Open it up and find the listCollections() method. Currently, it returns Query so that the ViewModel can add listeners to the return value.

With RxJava, this is built into the Observable type already, so we don’t need it anymore. Change the return type of the method to Observable<List> - we want to propagate all collections, not just one of them.

-fun listCollections(): Query<Collection> {
+fun listCollections(): Observable<List<Collection>> {
    return database.collectionQueries.all()
}

Inside the method, we will continue to call the method ‘all’ of the CollectionQueries object, but now we will add more calls to the end of this chain in order to transform it into a list. Let’s break up this chain into multiple lines to make it easier to follow along.

After the call to ‘all()’, add the extension function ‘asObservable()’ to transform the query into the RxJava type. This method has an optional parameter where you could specify a Scheduler, if you wanted to. I don’t.

At this point in the chain, the return type is Observable, so add another extension function to the end of it in order to get the underlying data. Write out ‘mapTo’ and check the IDE’s suggestions to see the different options. This is a list query, so we will use ‘mapToList’.

Similarly, for the method that only returns a single collection, we could use ‘mapToOne’ instead, for example. With this choice made, the return type matches and we’re done with the repository.

fun listCollections(): Observable<List<Collection>> {
-    return database.collectionQueries.all()
+    return database.collectionQueries
+        .all()
+        .asObservable()
+        .mapToList()
}

Let’s move to the place that calls this method and open the CollectionListViewModel. In here, a bunch of stuff will change now. There is no need for a manual listener anymore, since the RxJava stream will automatically send updates to the data downstream.

Instead, we will subscribe to the stream when the ViewModel is created and unsubscribe from this stream in the onCleared() method. Let’s get rid of the old stuff first: Delete the listener field and the collectionQuery one as well.

Then, add a new private field for the Disposable. Make it a var so that the constructor can assign a new value to it.

-private val collectionQuery = repository.listCollections()
-
-private val collectionQueryListener = object : Query.Listener {
-    override fun queryResultsChanged() {
-        refreshState()
-    }
-}
+
+private var disposable = Disposable.empty()

Remove the entire contents of the constructor and replace it with a subscription to the RxJava stream for collections. Store that subscription in the field we just created.

init {
-    refreshState()
-
-    collectionQuery.addListener(collectionQueryListener)
+
+    disposable = repository.listCollections()
+      .subscribe { collections ->
+    
+    }
}

Inside the subscribe() method, we can update the UI state of this ViewModel directly - there is no need anymore to use the refreshState() method from earlier. Copy its content in here and forward the parameter to the new state like so.

init {
    disposable = repository.listCollections()
        .subscribe { collections ->
+            _state.value = State.Result(
+                collections = collections
+            )
        }
    }

Finally, remove the content of the onCleared() method and replace it with a call to dispose of the subscription. Then, delete the unused refreshState() method and we’re done.

override fun onCleared() {
-    collectionQuery.removeListener(collectionQueryListener)
+    disposable.dispose()
}

-private fun refreshState() {
-    _state.value = State.Result(
-        collections = collectionQuery.executeAsList()
-    )
-}

With the listener handling moved out of the ViewModel, this call site looks much cleaner than with the vanilla API of SQLDelight. I would recommend using the extension library for RxJava if you plan to integrate SQLDelight into a codebase that already relies on RxJava.

Hit the Run button to launch the app and watch the screen automatically update the list of collections. It’s just like before, but now with cleaner code to achieve the same result.