Search code examples
reactive-programmingrx-swift

RxSwift: FlatMap on nested observables


I have this editor view model that I use in different other view models. The parent view models have a selectable user, once a user is selected, I'm gonna need a new instance of the editor with the new user.

This is a simplified version of the editor and a parent.

class EditorViewModel {
    let user: String
    let item = PublishSubject<String>()
    
    init(user: String) {
        self.user = user
    }
}


class ParentViewModel {
    var editor: Observable<EditorViewModel>!
    let user = BehaviorSubject<String?>(value: nil)
    
    init() {
        editor = user.compactMap { $0 }.map { EditorViewModel(user: $0) }
    }
}

Once the editor saves an item, I expect to get the saved item by flatMaping the editor to its item. Like this:

let parent = ParentViewModel()

parent.editor.flatMapLatest { $0.item }.debug("item").subscribe(onNext: { item in
    print("This doesn't print")
})

parent.editor.subscribe(onNext: { editor in
    print("This one prints")
    editor.item.onNext("something")
})

parent.user.onNext("1")

The flatMap line does subscribe but it never gets an item.

This is the output for running the code above in the playground:

2021-10-28 13:47:41.528: item -> subscribed
This one prints

Also, if you think this is too crazy a setup, I concur and am open to suggestions.


Solution

  • By default, Observables are cold. This means that each subscription works independently and in this case each subscription is getting a different EditorViewModel. (The .map { EditorViewModel(user: $0) } Observable will call its closure for each subscription.)

    Adding a .share() or .share(replay: 1) after the .map { EditorViewModel(user: $0) } Observable will make it hot which means that all subscriptions will share the same effect.

    As to your sub-question. I don't think I would setup such a system unless something outside of this code forced me to. Instead, I would pass an Observable into the EditorViewModel instead of a raw User. That way you don't need to rebuild editor view models every time the user changes.