1
votes

I am adopting the MVVM pattern in my iOS application. I expose a range of Observables as public properties in my view model and bind the UI to these properties. These Observables are created from a private connectable observable.

A view controller class then calls the "execute" method to fire the network request. However, if it fails for any reason, I'd like to be able to call "execute" again but this does not work. I believe this is due to the fact that the connectable observable has completed.

How can I achieve this without having to recreate the view model each time? I know I could do this by transforming a simple execute publish subject to the userDetailsObservable by using flatMap but I rely on the onCompleted event for other functionality. The onCompleted event would be lost as the publish subject remains active.

Connectable Observable Solution

class ViewModel {
    public var userName: Observable<String> {
         self.userDetailsObservable.map {
             return $0["username"]
         }
    }

    public var address: Observable<String> {
         self.userDetailsObservable.map {
             return $0["address"]
         }
    }

    public func execute() {
        self.userDetailsObservable.connect()
    }

    private lazy var userDetailsObservable: ConnectableObservable<JSON> {
        return Observable.create { observer in
            // execute network request
            // assume there is a json object and error object returned
            if error != nil {
                observer.onError(error)
            } else {
                observer.onNext(json)
            }
            observer.onCompleted()
        }.publish()
    }
}

The FlatMap solution

This would execute the network request every time an event is pushed on the execute subject. (execute.onNext()). The problem here is that the onCompleted event is lost as we are transforming a publish subject.

class ViewModel {
    public var userName: Observable<String> {
         self.userDetailsObservable.map {
             return $0["username"]
         }
    }

    public var address: Observable<String> {
         self.userDetailsObservable.map {
             return $0["address"]
         }
    }

    public var execute: PublishSubject<Void>()

    private lazy var userDetailsObservable: Observable<JSON> {
        return self.execute.flatMapLatest { _ in
            Observable.create { observer in
                // execute network request
                // assume there is a json object and error object returned
                if error != nil {
                    observer.onError(error)
                } else {
                    observer.onNext(json)
                }
                observer.onCompleted()
            }
        }.share()
   }
1
I think u can try with refcount instead of connect - Raja Jawahar

1 Answers

1
votes

You should use catchError and return a default value ("" for instance).

It’s required to prevent the observable from being disposed when you receive an error from the API.