Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarify documentation #27

Open
ashfurrow opened this issue Feb 16, 2016 · 9 comments
Open

Clarify documentation #27

ashfurrow opened this issue Feb 16, 2016 · 9 comments

Comments

@ashfurrow
Copy link
Member

Questions like this one are pretty common and demonstrate incomplete documentation and examples.

@morgz
Copy link

morgz commented Feb 16, 2016

Hey Ash,

So from my initial experience of CocoaAction, the following things confused me:

This code in the readme isn't valid code right? the x = y = z format is confusing and the below code doesn't compile.

action = Action<String, Bool> = Action(workFactory: { input in
    return networkLibrary.checkEmailExists(input)
})

...
action.execute("ash@ashfurrow.com")

Looking at the above example I was expecting something like this to compile, and quite honestly still don't understand why it doesn't! EDIT: I do now! Keep reading

let newAction = Action(workFactory: { (string) -> Observable<Bool> in
        return Observable.just(true)
})

or

let newAction: Action<String, Bool> = Action(workFactory: { (string) -> Observable<Bool> in
       return Observable.just(true)
})

The error is: Declared closure result Observable<Bool> is incompatible with the contextual type Observable<_>

& this one doesn't compile either:

let newAction = Action(workFactory: { input in
      return Observable.empty()
})

From the documentation what I think I'm doing here is creating an Action that I can then call .execute() on at a later date.

OK!!

In writing this I've figured it out. I wasn't giving the compiler enough info on the input generic. The following 2 work:

    let reverseAction = Action<String,String>(workFactory: { (string) -> Observable<String> in
        let reverseString = String(string.characters.reverse())
        return Observable.just(reverseString)
    })


    reverseAction.execute("ARTYFARTY").subscribeNext { (reversedString) -> Void in
        print(reversedString)
    }.dispose()

OR

    let reverseAction = Action(workFactory: { (string: String) -> Observable<String> in
        let reverseString = String(string.characters.reverse())
        return Observable.just(reverseString)
    })

Now I might have figured out what I was doing wrong if the following example, didn't use CocoaAction but instead constructed a plain Action itself:
https://github.com/artsy/eidolon/blob/cb31168fa29dcc7815fd4a2e30e7c000bd1820ce/Kiosk/Bid%20Fulfillment/GenericFormValidationViewModel.swift

Overall, it feels like there are 2 main options:

  1. Construct an Action yourself
  2. Use CocoaAction to create an Action<Void,Void> that you can assign to an rx_action.

& Both need a good complete working example.

@ashfurrow
Copy link
Member Author

Yup yup.

This is the correct code, I'll update the readme:

action : Action<String, Bool> = Action(workFactory: { input in
    return networkLibrary.checkEmailExists(input)
})

Let me know if that clarifies things for now. Otherwise, we should heed these suggestions 👍

@ashfurrow
Copy link
Member Author

Updated: b620bf3

@morgz
Copy link

morgz commented Feb 16, 2016

Nice. Personally I'd have found an example like this useful too:

    // Create an Action that reverses a string
    let reverseAction = Action(workFactory: { (string: String) -> Observable<String> in
        let reverseString = String(string.characters.reverse())
        return Observable.just(reverseString)
    })

    // Create an Action which can be asigned to a UIButton (Action<Void,Void>)
    // This Action executes our reverseAction when the button is clicked
    let buttonAction = Action<Void, Void>(enabledIf: Observable.just(true), workFactory: { _ in
        print("clicked")
        return reverseAction.execute("Blah Blah Blah").flatMap({ (result) -> Observable<Void> in
            return Observable.empty()
        })
    })

    // Assign the action to our button
    button.rx_action = buttonAction

    //Additionally we can observe the progress/errors of the reverseAction whenever it is executed
    reverseAction.elements.observeOn(MainScheduler.instance).subscribeNext { (result) -> Void in
        print("Success. String Reversed: \(result)")
    }.addDisposableTo(self.disposableBag)

    reverseAction.errors.observeOn(MainScheduler.instance).subscribeNext { (error) -> Void in
        print("error happened!")
    }.addDisposableTo(self.disposableBag)

I'm converting this project to RxSwift while learning.

It seems common to define the Action in a ModelView and then just construct a CocoaAction in the viewController which executes the Action. The viewController can then observer the ModelView's Action to update view.

@ashfurrow
Copy link
Member Author

Yeah, we're trying to figure out a general-purpose solution to that, too. See: #17

@zipme
Copy link

zipme commented Apr 22, 2016

Hi guys, am I doing this correctly?

I init my view model by passing in some Drivers props from vc,

class SomeViewModel {
  let loginAction: Action<Void, Void>
  let ...

  init(email: Driver<String>, password: Driver<String>) {
    self.loginAction = Action<Void, Void>() { _ in      
      return Observable
        .combineLatest(email.asObservable(), password.asObservable()) { (email: $0, password: $1) }
        .flatMapLatest({ (params) in
          return provider.login(params)
        })       
        .map { _ in Void() }
    }
   ...
  }
}

and then I assign the loginAction to my button's rx_action button.rx_action = viewModel.loginAction

However, when the action got executed, the onDisposed of the buffer subscription is never called hence the executing of the action is always true

It seems there is a retain cycle somewhere which causes this, any idea?

Thanks!

@ashfurrow
Copy link
Member Author

@zipme this is a good question, and I'm not sure since I've not used Driver yet. If I had to guess, the drivers send new text values from a text field? So I imagine that those signals would continue sending values after the initial login was finished. In this case, you need to tell RxSwift that you only want the first set of parameters. Can you try:

class SomeViewModel {
  let loginAction: Action<Void, Void>
  let ...

  init(email: Driver<String>, password: Driver<String>) {
    self.loginAction = Action<Void, Void>() { _ in      
      return Observable
        .combineLatest(email.asObservable(), password.asObservable()) { (email: $0, password: $1) }
        .take(1) // Note the new line
        .flatMapLatest({ (params) in
          return provider.login(params)
        })       
        .map { _ in Void() }
    }
   ...
  }
}

And let us know if that works?

@zipme
Copy link

zipme commented Apr 23, 2016

@ashfurrow IT WORKS! Thanks Ash! You are awesome! ❤️

@ashfurrow
Copy link
Member Author

Glad to hear it!

Ash Furrow
https://ashfurrow.com/

On April 23, 2016 at 3:56:11 AM, zipme (notifications@github.com(mailto:notifications@github.com)) wrote:

@ashfurrow(https://github.com/ashfurrow) IT WORKS! Thanks Ash! You are awesome!


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub(#27 (comment))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants