1
votes

I need some internal state in a viewModel but also trying to follow the "no subscription / bind / drive / ..." ideal approach and only compose between Observables.

How can I specify what a Variable observes?

Example:

private var userProfilesToFetch = Variable<[String]>([])
private var users: Variable<[User]> {

    return //something that observes fetchUserProfiles() and when it emits, appends to its .value
}

private func fetchUserProfiles() -> Observable<User?> {

    let reference = databaseRef.child("users")

    return userProfilesToFetch.asObservable()
        .filter({ $0 != [] })
        .map({ $0.last! })
        .flatMap({ (userId) -> Observable<User?> in

            return self.service.observeAllChildren(of: reference.child(userId), by: .value)
                .map({ (snapshot) -> User? in

                        guard let values = snapshot.value as? [String: AnyObject] else { return nil }

                        var user = User(dictionary: values)

                        user.id = snapshot.key

                        return user
                })
        })
} 
1
If there are no subscriptions or binds, then nothing will happen. Observables are lazy and don't do any work unless they are being observed. - Daniel T.
@DanielT. Agreed, but the whole point to achieve "maximum separation of concerns" is to have chained subscriptions from a call in the view controller. The thing is I cannot figure out how to include a Variable in the subscription chain. For example if in my VC I subscribe to (A) in my viewModel, then because this (A) is bound to another observable in its definition, like the fetchUserProfiles() function above, it will chain subscribe to userProfilesToFetch and so on. What I can't figure out is how to chain the Variable itself by specifying what it should observe. - Herakleis

1 Answers

2
votes

The "ideal approach" is to avoid the use of Subjects/Variables. Instead, favor sequence emitters (functions that return an observable,) sequence receivers (functions that accept an observable as a parameter,) and sequence transformers (functions that do both.)

Sequence emitters and receivers necessarily perform side effects and inside a sequence receiver, there is necessarily a subscribe/bind in order to unpack the value and use it.

There should be a direct and obvious link between the emitters and receivers. Subjects/Variables tend to break that link.

In this ideal approach, your "view model" is not a class/struct containing a bunch of variables. Your view model is a function that takes observables as parameters and returns observables for the view controller to bind to. For example:

class MyViewController: UIViewController {
    @IBOutlet weak var name: UITextView!
    @IBOutlet weak var label: UILabel!

    override
    func viewDidLoad() {
        super.viewDidLoad()
        let viewModel = myViewModel(name: name.rx.text.orEmpty)
        viewModel.label.bind(to: label.rx.text).disposed(by: bag)
    }
    let bag = DisposeBag()
}

struct MyViewModel { 
    let label: Observable<String>
}

// this function could be turned into an `init` method on the MyViewModel struct if you would prefer.
fun myViewModel(name: Observable<String>) -> MyViewModel {
    let label = name.map { "Hello \($0)!" }
    return MyViewModel(label: label)
}