Rxswift change value of observed object

Thiryn picture Thiryn · Jul 25, 2018 · Viewed 7.2k times · Source

I am fairly new to Rx and to Reactive Programming in general. I am simply trying to make things work. I'm trying to populate an object after a request to an API (Github's API for the matter):

The object to populate :

class GithubUser {
    var login: String?
    var joinDate: Date?
    var email: String?
}

The way I try to feed it:

class GithubUserRepository {
    static func getUser() -> Observable<GithubUser> {
        let userObservable = BehaviorRelay<GithubUser>(value: GithubUser())
        let login = "Thiryn"
        // GithubService.sharedInstance.getUser does whatever async call to the github API to get a user profile
        GithubService.sharedInstance.getUser(login, success: { (userProfile) in
            userObservable.value.login = login
            userObservable.value.email = userProfile.email
            userObservable.value.joinDate = userProfile.joinedDate
        }) { (error) in
            print(error.localizedDescription)
        }
        return userObservable.asObservable()
    }
}

And how I try to consume

class ViewController: UIViewController {

    let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        GithubUserRepository.getUser().subscribe(onNext: { (user) in
            print(user.login ?? "nope")
        }).disposed(by: self.disposeBag)
    }
}

The result is that I a "nope" the first time, before the request get executed, then I successfully get a result from my request, change the value of the object, but I might do something wrong as the subscribed function is never executed after. Any thoughts? Thanks!

Edit : I based my try on the Reactive Values example from RxSwift

Answer

Shai Mishali picture Shai Mishali · Jul 25, 2018

A couple of clarifications -

First of all, the way to wrap an existing API service into an Observable would be to use Observable.create {}. The way you're doing this at the moment is suboptimal (e.g. using a Relay just to store values)

Second, modifying a reference type isn't considered a "change" on the observable stream. The only thing that is considered a change is actually emitting a new value onto the stream. (e.g. relay.accept(newUser)).

For your situation, the scheme you should follow would be:

func myMethod() -> Observable<DataModel> { 
    return Observable.create { observer in 
        myAPIService.doRequest ( result in 
            ... do some stuff ...
            observer.onNext(result)
            observer.onCompleted()

            // or observer.onError(error) if it occured
        }

        return Disposables.create { }
    }
}

This is the proper way to wrap your API call inside a Rx-y method, that returns an Observable object. You could do the same with Single.create if you'd like to return a Single.

You should read further on how Rx works, it is not as basic as manipulating the current "value" of things, as things in streams don't really have a "value", it is just values being continuously added into an existing stream.