Observable.from subscribe closure called twice - rx-swift

When executed the code below will print "subscribe" twice. Why? RxSwift 5.0
import UIKit
import RxSwift
class ViewController: UIViewController {
let data = Data(repeating: 100, count: 1_000_000_000_0)
override func viewDidLoad() {
super.viewDidLoad()
let disposeBag = DisposeBag()
Observable<Data>
.from(data)
.subscribe( { _ in print("subscribe") } )
.disposed(by: disposeBag)
}
}

This is a correct behaviour. You're creating an Observable that only contains a single value which causes two events to fire:
Next Event (containing your data object)
Complete Event that indicates that the stream has completed.
I suspect that in what you're after is the first (Next) event. Change your code from:
Observable<Data>
.from(data)
.subscribe( { _ in print("subscribe") } )
.disposed(by: disposeBag)
To:
Observable<Data>
.from(data)
.subscribe(onNext: { _ in print("subscribe") } )
.disposed(by: disposeBag)
Which will give you just the Next event containing your Data object.

The closure in subscribe is called for onNext and onCompleted events that's why print twice

Related

Is it possible to use async/await syntax for UIActivityViewController's completionWithItemsHandler property?

I have this function:
struct Downloader {
static func presentDownloader(
in viewController: UIViewController,
with urls: [URL],
_ completion: #escaping (Bool) -> Void
) {
DispatchQueue.main.async {
let activityViewController = UIActivityViewController(
activityItems: urls,
applicationActivities: nil
)
activityViewController.completionWithItemsHandler = { _, result, _, _ in
completion(result)
}
viewController.present(
activityViewController,
animated: true,
completion: nil
)
}
}
}
It simply creates a UIActivityViewController and passes the completionWithItemsHandler as a completion block into the static func.
I am now trying to get rid of all the #escaping/closures as much as I can in order to adopt the new async/await syntax, however I don't know if I can do something here with this.
I started adding an async to my function, and realized that Xcode shows completionWithItemsHandler with an async keyword, but I really have no idea if I can achieve what I want here.
Thank you for your help
Yes, you can do that but you need to write yourself little wrapper over it. So it will look something like that:
#MainActor
func askToShareFile(url: URL) async {
let avc = UIActivityViewController(activityItems: [url], applicationActivities: nil)
present(avc, animated: true)
return await withCheckedContinuation { continuation in
avc.completionWithItemsHandler = { activity, completed, returnedItems, activityError in
print("Activity completed: \(completed), selected action = \(activity), items: \(returnedItems) error: \(activityError)")
if completed {
continuation.resume()
} else {
if activity == nil {
// user cancelled share sheet by closing it
continuation.resume()
}
}
}
}
}
However, important note here that, from my experimentation as of today (iOS 15.5), I can see completion handler is NOT called properly when, on share sheet, user selects any app that handle our file by copying it (activityType = com.apple.UIKit.activity.RemoteOpenInApplication-ByCopy).
If you do some changes - be careful, as you might loose continuation here so please test it yourself too.

How to use RxSwift to implement the following logic with UITextfield?

The TextCode has a UItextField on the right side.
The PassCode has a UItextField on the right side.
Use RxSwift to implement the following logic.
SelectAlertItem AlertViewControll sheet style has three options: A, B, C. Now I can implement the selection logic.
I don't know how to use RxSwift to implement the following logic.
The following is my key logic: Only when the selectedItem is B. TextCodeTextField text must be copied to PassCodeTextField at the end of editing.
In other word, SelectType is B, TextCodeTextField input "11111" and editingend, then PassCodeTextField will be "11111".
How to use RxSwift to implement the following logic with UITextfield?
Here's how to do it using my Cause Logic Effect architecture (with notes):
import Cause_Logic_Effect
import RxCocoa
import RxSwift
import UIKit
final class ViewController: UIViewController {
var textCodeField: UITextField!
var selectTypeAction: UIButton!
var selectTypeLabel: UILabel!
var passCodeField: UITextField!
let disposeBag = DisposeBag()
}
extension ViewController {
func connect() {
// when user taps the selectTypeAction, display an action sheet alert to
// get the selection type from the user.
let selectType = selectTypeAction.rx.tap
.flatMapFirst(
presentScene(animated: true, over: selectTypeAction, scene: {
UIAlertController(
title: nil,
message: "Select Item:",
preferredStyle: .actionSheet
)
.scene { $0.connectChoice(choices: ["A", "B", "C"]) }
})
)
.share()
// when the user selects "B" grab the last value entered in the
// textCodeField and push it to the passCodeField
selectType
.compactMap { $0 }
.filter { $0 == "B" }
.withLatestFrom(textCodeField.rx.text)
.bind(to: passCodeField.rx.text)
.disposed(by: disposeBag)
// when the user selects a value push the value to the selectTypeLabel
selectType
.bind(to: selectTypeLabel.rx.text)
.disposed(by: disposeBag)
}
}

How do I test a view controller after subscription to an Observable is done?

In the below example, how do I wait until the success callback is called to start asserting in my view controller tests?
class VC : NSViewController {
let observable: Observable<Int>
let compositeDisposable = CompositeDisposable()
override viewDidLoad() {
self.compositeDisposable.insert(
self.observable.toArray()
.observeOn(MainScheduler.instance)
.subscribe(
// draw onto the VC
)
)
}
}

RxSwift - Button Tap using Publish Subject

So I have a button inside my ViewController which is connected to ViewModel and than whenever the button is tapped, in my coordinator I navigate to another screen. The code is like this:
VC
btnShowShopsMap.rx.tap
.bind(to: viewModel.selectShowMap)
VM
let selectShowMap: AnyObserver<Void>
let showShopMap: Observable<Void>
//Inside init
let _selectShowMap = PublishSubject<Void>()
selectShowMap = _selectShowMap.asObserver()
showShopMap = _selectShowMap.asObservable()
Coordinator
viewModel.showShopMap
.subscribe(onNext: { _ in self.showShopMap()})
.disposed(by: userShopVC.disposeBag)
Is it possible to refactor above code? rather than using PublishSubject is there any other way to do what i am doing using PublishSubject
My VC, VM & Coordinator Flow
Coordinator
func showLoginScreen(logout: Bool = false) {
guard let viewController = LoginViewController.instantiate(storyboard: .main) else { return }
viewController.viewModelFactory = { inputs in
let viewModel = LoginViewModel(inputs: inputs)
viewModel.showHome
.subscribe(onNext: { isLogged in
if isLogged {
self.showHomeScreen()
}
})
.disposed(by: viewController.disposeBag)
inputs.showOnboarding
.subscribe(onNext: { _ in
self.showOnboardingScreen()
})
.disposed(by: viewController.disposeBag)
return viewModel
}
navController.pushViewController(viewController, animated: true)
VC
var viewModelFactory: (LoginViewModel.UIInputs) -> LoginViewModel = { _ in fatalError("factory not set")}
let inputs = LoginViewModel.UIInputs(userNumber: txtUserNumber.rx.text.orEmpty.asDriver(),
password: txtPassword.rx.text.orEmpty.asDriver(),
loginTapped: btnLogin.rx.tap.asSignal(),
userNumberLostFocus: txtUserNumber.rx.controlEvent(.editingDidEnd).asSignal(),
passwordLostFocus: txtPassword.rx.controlEvent(.editingDidEnd).asSignal(),
indicator: indicator,
showOnboarding: btnShowOnboarding.rx.tap.asObservable())
VM
struct UIInputs {
let userNumber: Driver<String>
let password: Driver<String>
let loginTapped: Signal<Void>
let userNumberLostFocus: Signal<Void>
let passwordLostFocus: Signal<Void>
let indicator: ActivityIndicator
let showOnboarding: Observable<Void>
}
init(inputs: UIInputs) {}
Assuming the view controller owns and instantiates the view model, you could pass the tap control event as an observable to the view model initializer, which then exposes it as an observable for the coordinator to subscribe to:
// VC:
let viewModel = ViewModel(..., showShopMap: btnShowShopMap.rx.tap.asObservable())
// VM:
let showShopMap: Observable<Void>
init(..., showShopMap: Observable<Void>) {
self.showShopMap = showShopMap
}
I try not to use subjects whenever possible and instead just expose transformed observables that were passed in.
I found very easy and simple way to solve my issue and avoid using Subject, As there was no logic related to my button in VM, I don't need pass my Button tap to my VM either by using Observable or using Subject. Instead I directly accessed my button in my Coordinator like this:
viewController.btnShowOnboarding.rx.tap
.subscribe(onNext: { _ in
self.showOnboardingScreen()
})
.disposed(by: viewController.disposeBag)

Can't get variable from Notification?

I want to write a project has a notification can print data which stored in main viewcontroller, but when notification fired, always get empty data.
In the ViewController
arrPerson = [String]()
override func viewDidLoad() {
super.viewDidLoad()
arrPerson.append("Peter")
arrPerson.append("Jack")
setNotification()
}
func printTheName() {
print("In printTheName")
print("arrPerson:\(arrPerson.count)")
for x in arrPerson {
print(x)
}
}
func setNotification() {
let notification = UNMutableNotificationContent()
notification.title = "title"
notification.subtitle = ""
notification.body = "body"
notification.sound = UNNotificationSound.default()
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: true)
let request = UNNotificationRequest(identifier: "check", content: notification, trigger: trigger)
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.add(request) { (error) in
if error != nil {
print("notificationCenter.add ERROR:\(error)")
// Handle any errors.
}
}
}
In the Appdelegate
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("In userNotificationCenter")
ViewController().printTheName()
}
The code will print out "userNotificationCenter","In printTheName","arrPerson:0".
I need get data from notification. Why arrPerson.count is 0 when call printTheName from notification?
Try to programatically instantiate the viewController using a window in your appdelegate.
You will be able to fetch the count then in your notification center.Or since your viewController class is the landing screen, Call the userNotifationCenter method in your main Class and not here.

Resources