CocoaAction / Action with UIAlertController - swift

I'm trying to use Action / CocoaAction library.
The primary usage for now is to show an UIAlertController and when an UIAlertAction button is tapped it has to call a function defined in my viewModel (changeAddress that returns an Observable).
My understanding of this would be:
let ac = CocoaAction(workFactory: {[unowned self] _ in
self.viewModel!.requestChangeAddress()
.subscribeNext({ [unowned self] data in
if let response = data?.result
{
self.showResultOperation(response)
}
})
.addDisposableTo(self.disposeBag)
return .empty()
})
let OKAction = UIAlertAction.Action("OK", style: .Default)
OKAction.rx_action = ac
But unfortunately it doesn't work. The workFactory closure is correctly called but the subscription doesn't take effect. I know something is wrong when I return .empty but I cannot understand how to solve.
How can I correct this? What I'm doing wrong?

great question! Thanks for posting the code.
So your code is getting the observable from requestChangeAddress, but then it's subscribing to it and adding it to a dispose bag. The Action class is actually going to take care of that for you, you only need to return the disposable.
The problem is that you want to do something with the values sent on the action, and returning the observable won't let you do that. So you need to include a call to doOnNext in the observer chain. Here's what it might look like:
let ac = CocoaAction(workFactory: {[unowned self] _ in
return self.viewModel!
.requestChangeAddress()
.doOnNext({ [unowned self] data in
if let response = data?.result
{
self.showResultOperation(response)
}
})
.map { _ in Void() }
})
Using doOn functions in RxSwift is a great way to inject side-effects into your observables when necessary, like it is here.
EDIT: I added the map at the end to fix a compiler error, because the return type from the factory method is Observable<Void>.

Related

RxSwift - How to call method which returns Driver at regular intervals?

I am a RxSwift beginner and making a app with RxSwift + MVVM.
I have a method which calls API and converts to RxCocoa.Driver in ViewModel class like below.
func fetch() -> Driver<HomeViewEntity> {
apiUseCase.fetch(query: HomeViewQuery())
.map { data in
HomeViewEntity(userName: data.name,
emailAddress: data.email
}
.asDriver(onErrorRecover: { [weak self] error in
if let printableError = error as? PrintableError {
self?.errorMessageRelay.accept(AlertPayload(title: printableError.title, message: printableError.message))
}
return Driver.never()
})
}
Now, I'd like to call this fetchListPlace() method at regular intervals a.k.a polling (e.g. each 5 minutes) at ViewController.
How to do that????
I think interval is suit in this case, but I can't get an implementation image....
Here you go:
func example(_ fetcher: Fetcher) -> Driver<HomeViewEntity> {
Driver<Int>.interval(.seconds(5 * 60))
.flatMap { _ in fetcher.fetch() }
}
Also note,
Returning a Driver.never() from your recovery closure is probably a bad idea. Prefer Driver.empty() instead.
I'm not a fan of putting a side effect in the recovery closure in the first place. I think it would be better to have the fetch() return a Driver<Result<HomeViewEntity, Error>> instead and move the side effect to the end of the chain (in a subscribe or a flatMap.)

is this a proper completion handler?

I had a very slow bottom sheet, showing up blank then after a while loading data. I tried to apply a completionHandler isLoadedCompletionHandler, solution worked, but my colleague told me this is not a "completion handler". Could you explain me why this is working. And how? Is this a proper completion handler?
func buttonDetailTapped(with travelSolutionId: String) {
guard let currentPurchaseSolution = purchaseSolutions.value.first(where: { $0.xmlId == purchaselSolutionId }) else {return}
getAllPurchaseDetail(searchId: searchId.value, solutionId: purchaseSolutionId)
.subscribe(onNext: { [weak self] purchaseDetails in
let isLoadedCompletionHandler: ([PurchaseDetail]) -> Void = { theArray in
self?.result.onNext(.showPurhcaseSolutionDetails(purchaseDetails, currentTravelSolution))
}
isLoadedCompletionHandler(purchaseDetails)
})
.disposed(by: disposeBag)
}
A completion handler is a function that you pass into your function, which usually gets called upon the completion of some asynchronous task.
Your function of buttonDetailTapped contains no parameters that are functions (i.e. (Thing, Error) -> Void) which are called upon completion, and thus you cannot know from calling this function when it completes.
Your function may go ahead and do other things when it's done, but there is no completion handler.
isLoadedCompletionHandler is not a completion handler because it is called immediately after it is assigned.
A completion handler is a closure that you pass into a function, that will be called when whatever that function is doing asynchronously completes. You are not passing isLoadedCompletionHandler anywhere.
You could have just written
getAllPurchaseDetail(searchId: searchId.value, solutionId: purchaseSolutionId)
.subscribe(onNext: { [weak self] purchaseDetails in
self?.result.onNext(.showPurhcaseSolutionDetails(purchaseDetails, currentTravelSolution))
})
.disposed(by: disposeBag)
and achieved the same result.

Subscribe to view controller property without nested subscribe loop

How can I make this subscribe loop not be nested? I can't seem to figure out how you would go about doing this because I push the view controller in the main subscribe loop, and not just set a value.
button.rx.tap.subscribe(onNext: { _ in
let viewController = MyViewController()
self.navigationController.pushViewController(viewController)
viewController.myPublishRelay.asObservable().subscribe(onNext: { value in
// do something with value
})
})
You desire two different side effects, so it makes sense to have two subscription. To prevent from nesting, you could do something in the lines of this:
let viewControllerToPresent = button.rx.tap.map { _ in
return MyViewController()
}.share()
viewControllerToPresent.subscribe(onNext: { [unowned self] viewController in
self.view.pushViewController(viewController)
}.disposed(by: self.disposeBag)
viewControllerToPresent.flatMapLatest { viewController in
viewController.myPublishRelay.asObservable()
}.subscribe(onNext: { value in
// do something with value
}.disposed(by: self.disposeBag)
The call to share is important here, otherwise the mapping after rx.tap would occur twice, making us subscribe to the publish relay of a view controller that is not the one we presented.
You can use .sample() or .combineLatest(), depending on how does your publishRelay update.
For example, Observable.combineLatest(myPublishRelay, button.rx.tap) { $0 }.subscribe(onNext: { value ...
See http://rxmarbles.com for reference on operators.
Whenever I see a nested subscribe I think of flatMap. Something like this should work:
button.rx.tap
.flatMap { _ in
let viewController = MyViewController()
self.navigationController.pushViewController(viewController)
return viewController.myPublishRelay.asObservable()
}
.subscribe(onNext: { value in
// do something with value
})

RxSwift: how to build up the observable function call chain other than use callback?

I'm trying to solve an async sequential problem, for example:
There are one 'OriginalData' in myclass, and I want to do some sequential operations to it: operationA, operationB and operationC:
operationA accepts OriginalData and return outputA, after it finishes, then operationB should take the outputA as input and return outputB, and move to operationC..
What I've done was using the callbacks:
// pseudocode
class Myclass {
func operationA(inputA, callback: operationB)
func operationB(inputB, callback: operationC)
..
}
As a result, if using callbacks, it will result in a callback hell and lots trouble. I turned into RxSwift, but not sure how to use RxSwift to solve it.
(P.S I've already read the RxSwift's official document, but still cannot make my idea clear. Best Appreciated for your helps!)
I think you can solve this problem by using PublishSubject as follows:
let operationA = PublishSubject<String>()
let operationB = PublishSubject<String>()
let operationC = PublishSubject<String>()
let disposeBag = DisposeBag()
operationA.asObserver()
.subscribe(onNext:{element in
operationB.onNext(element)
})
.disposed(by: disposeBag)
operationB.asObserver()
.subscribe(onNext:{element in
operationC.onNext(element)
})
.disposed(by: disposeBag)
operationC.asObserver()
.subscribe(onNext:{element in
print(element)
})
.disposed(by: disposeBag)
operationA.onNext("A")

How to test a function that gets into the main thread in Swift with RxSwift and XCTest?

I came across this problem when testing my View:
In my ViewModel I call to an asynchronous operation and when the response arrives, I use a PublishSubject to produce a change in my View. In my View, I call DispatchQueue.main.async in order to hide or show a button.
ViewModel
let refreshButtons = PublishSubject<Bool>(true)
refreshButtons.onNext(true)
View
model.refreshButtons.asObservable()
.subscribe(onNext: {
[unowned self] success in
self.updateButtons(success)
})
.addDisposableTo(disposable)
private func updateButtons(_ show:Bool) {
DispatchQueue.main.async{
button.isHidden = !show
}
}
Now I don't know how to unit test that refreshButtons.onNext(true) will hide or show my button.
The solutions I can think of are:
Overriding the method and having an async expectation, but for that I need to make the method public, what I don't want, or
Dispatching the main queue in my ViewModel and not in the view, what it sounds odd to me, but might me ok.
How can I solve this?
Thank you in advance.
You could use an async expectation based on a predicate in your unit test to wait an see if the button is not hidden anymore.
func testButtonIsHidden() {
// Setup your objects
let view = ...
let viewModel = ...
// Define an NSPredicate to test your expectation
let predicate = NSPredicate(block: { input, _ in
guard let _view = input as? MyView else { return false }
return _view.button.isHidden == true
})
// Create an expectation that will periodically evaluate the predicate
// to decided whether it's fulfilled or not
_ = expectation(for: predicate, evaluatedWith: view, handler: .none)
// Call the method that should generate the behaviour you are expecting.
viewModel.methodThatShouldResultInButtonBeingHidden()
// Wait for the
waitForExpectationsWithTimeout(1) { error in
if let error = error {
XCTFail("waitForExpectationsWithTimeout errored: \(error)")
}
}
}
Something worth noting is that the value you pass to the NSPredicate should be a class. That is because classes are passed by reference, so value inside the predicate block will be the same as the one touched by your view model. If you were to pass a struct or enum though, which are passed by copy, the predicate block would receive a copy of the value as it is at the time of running the setup code, and it will always fail.
If instead you prefer to use UI tests as suggested by #Randall Wang in his answer, then this post might be useful for you: "How to test UI changes in Xcode 7". Full disclosure, I wrote that post.
First of all, You don't need test private method
If you want to test if the button is hidden or not,try UI testing
here is the WWDC of UI testing.
https://developer.apple.com/videos/play/wwdc2015/406/