Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RxSwift Using Variables Correctly

I am trying to convert a project to use RxSwift and MVVM. I have a service that syncs a list of data from Parse on every app launch and I basically want to make sure I am taking the correct approach.

What I have done is made a Variable subject and then allow my models to listen to this. ParseService:

let rx_parseMushrooms = Variable<[ParseMushroom]>([])

MushroomLibraryModel:

_ = parseService.rx_parseMushrooms
    .asObservable()
    .map { (parseMushrooms:[ParseMushroom]) -> [Mushroom] in
        let mushrooms = parseMushrooms.map { (parseMushroom:ParseMushroom) -> Mushroom in
            let mushroom = Mapper<Mushroom>().map(parseMushroom.dictionaryWithValuesForKeys(parseMushroom.allKeys()))
            return mushroom!
        }

        return mushrooms
    }
    .subscribeNext({ (mushrooms:[Mushroom]) -> Void in
        self.mushrooms = mushrooms
        print(mushrooms)
    })

I do the same for expressing the sync state.

ParseService:

struct SyncState {
    enum State {
        case Unsynced, ConnectingToServer, SyncingInfo, FetchingImageList, SyncingImages, SyncComplete, SyncCompleteWithError
    }

    var infoToSync = 0
    var imagesToSync = 0
    var imagesSynced = 0

    var state = State.Unsynced
}

let rx_syncState = Variable(SyncState())

I then update the variable a la

self.rx_syncState.value = self.syncState

SyncViewModel:

_ = parseService.rx_syncState
     .asObservable()
     .subscribeNext { [weak self] (syncState:ParseService.SyncState) -> Void in
          switch syncState.state {
              //show stuff based on state struct
          }
      }

Anyways, I would greatly appreciate if someone can tell me if this is a good way of going about it or if I am misusing RxSwift (and guide me on how I should be doing this).

Cheers!

like image 515
Manny Avatar asked Jan 08 '16 01:01

Manny


1 Answers

Hmm... Here is an article about using Variable (note that Variable is a wrapper around BehaviorSubject.)

http://davesexton.com/blog/post/To-Use-Subject-Or-Not-To-Use-Subject.aspx

In your case, you already have a cold observable (the network call,) so you don't need a Subject/Variable. All you need to do is publish the observable you already have and use replay(1) to cache the value. I would expect a class named something like ParseServer that contains a computed property named something like mushrooms.

To help get the mushrooms out of parse, you could use this (this will create the cold observable you need):

extension PFQuery {

    var rx_findObjects: Observable<[PFObject]> {
        return Observable.create { observer in
            self.findObjectsInBackgroundWithBlock({ results, error in
                if let results = results {
                    observer.on(.Next(results))
                    observer.on(.Completed)
                }
                else {
                    observer.on(.Error(error ?? RxError.Unknown))
                }
            })
            return AnonymousDisposable({ self.cancel() })
        }
    }

}

And then you would have something like:

class ParseServer {
    var mushrooms: Observable<[Mushroom]> {
        return PFQuery(className: "Mushroom").rx_findObjects
        .map { $0.map { Mushroom(pfObject: $0) } }
        .publish()
        .replay(1)
    }
}

I think the above is correct. I didn't run it through a compiler though, much less test it. It might need editing.

The idea though is that the first time you call myParseServer.mushrooms the system will call Parse to get the mushrooms out and cache them. From then on, it will just return the previous cashed mushrooms.

like image 172
Daniel T. Avatar answered Nov 12 '22 17:11

Daniel T.