App state with Realm and RxSwift

Intro

This post isn’t that much about operators but how to leverage the awesome technology that Realm is.

Long story short - I wanted to submit an app for review and literally just before hitting submit I realized I forgot to implement a favorites filter in one of the view controllers. I would’ve quickly hacked something to send it off but since different controllers took care of the navigation bar and the actual table view showing the data could not hack my way around.

So I sat down and wrote me a proper reactive app settings class to use around the app in some 10 minutes or so.

The data model class

Since the app uses Realm I created a new Object sub-class for my app settings - this way my settings would always persist between app launches and can be observed on any thread. Here’s the AppState class I ended up with:

import Foundation
import RealmSwift

class AppState: Object {
    
    dynamic var scheduleOnlyFavorites = false
    dynamic var speakersOnlyFavorites = false
    
}

For this version of the app I just want to persist whether the user has favorites-only toggled in the schedule screen and the speakers screen (it’s an event app).

The default values for both properties are false. When the app starts it checks whether there’s an AppState object already stored in Realm and if not - creates one.

App state subject in app controller

I have an app controller class that provides few generic services so I thought it’s a good idea to add a PublishSubject to it that different view controllers (and other classes) can observe.

I added an appState subject and a property to retain the Realm updates subscription:

class AppController {
    
    private var stateNotification: NotificationToken?
    let appState = PublishSubject<AppState>()
    
}

Provide app state updates

Wiring up the app settings subject was very easy thanks to the addNotificationBlock method on Realm’s AnyRealmCollection:

init() {
    stateNotification = RealmProvider.appRealm.objects(AppState).addNotificationBlock {[weak self]results, error in
        if let state = results?.first, let `self` = self {
            self.appState.onNext(state)
        }
    }
}

RealmProvider.appRealm returns a Realm on the current thread that points to the particle realm file I want to use for the app state.

What this code does is to fetch all objects of class AppState stored in Realm and any time any time those are updated call the closure provided to addNotificationBlock. The closure gets a reference to the results as its first parameter so it’s pretty easy to just fetch the first result (the only AppState object in the Realm actually) and emit it as a .Next event from the appState subject of the app controller.

That’s really all there is to binding a PublishSubject to any Realm result. As long as anything retains stateNotification you will receive updates from Realm.

Observe for app state updates

Next - I wanted to observe for changes in the two settings in my persisted app settings object.

So in my view model, that shows the event schedule, I added:

UIApplication.controller.appState
.map {state in
    return state.scheduleOnlyFavorites
}
.bindTo(onlyFavorites)
.addDisposableTo(bag)

I observe appState and map the emitted value, which is of type AppState, to the value of scheduleOnlyFavorites. The result is a Bool value, which I just bind to another subject that I have called onlyFavorites.

Cool! Now any class could update my app settings and that’ll reflect the bindings I have in this particular view model.

Updating the app state

The last piece of the puzzle was the code to run when the user toggles the favorites-only button. I went back to my app controller class and added a new method that would update my app state:

func updateState(updates: (inout AppState)->Void) {
    try! RealmProvider.appRealm.write {
        var currentState = RealmProvider.appRealm.objects(AppState).first!
        updates(&currentState)
    }
}

updateState takes a closure inside which you can mutate the app state. This pattern let me create a Realm on the current thread, fetch the current app state and inject it to the user-defined updates closure.

I wrapped the whole thing in a Realm.write call so that it is actually allowed to mutate the state object from inside the closure.

Observing for buttons taps and mutating the state

It was finally time to wire everything together. In the view controller taking care of my navigation bar where the favorites button lives I added:

btnFavorites.rx_tap
    .subscribeNext {[weak self]_ in
        guard let `self` = self else {return}
        UIApplication.controller.updateState {state in
            state.scheduleOnlyFavorites = self.btnFavorites.selected
        }
    }
    .addDisposableTo(bag)

When the user taps the favorites button I call updateState and set the value of scheduleOnlyFavorites to the selected property of my button. selected toggles between true and false so that changes the app state property accordingly.

As soon as my updateState closure executes, app controller gets a notification from Realm that one of the stored AppState objects was modified and that emits a new value from the appState subject.

And finally since my view models throughout the app observe that subject they update their bindings and UI throughout the app gets updated accordingly.

Conclusion

It took me very little time to add a persisted reactive app state to the project. Using Realm for the task made it very easy to have the app state reactive and accessible across classes and threads.

The code in my app controller is 10-15 lines and I have another 5 lines in the data model class. How cool is that?

I hope this post has been useful! Do you know a better way to do any of this? Seen a bug? Ping me on Twitter at icanzilb

Hope that post was helpful, and if you want to get in touch you can find me here

Share this post:


If you'd like to learn how to create professional production apps with RxSwift, the best resource out there is the RxSwift book written by Florent Pillet, Junior Bontognali, Marin Todorov, & Scott Gardner.

It features 20+ chapters covering the basics, the Rx operators, and advanced topics like testing, error handling, and app architecture.

Available from Ray Wenderlich: » Learn more.
Tags// , ,