Capacitor plugin with ASWebAuthenticationSession: must be used from main thread only - ios

I'm trying to get rid off an annoying warning/error in the xcode console.
I've implemented a custom plugin to open Keycloak using ASWebAuthenticationSession and I'm having issue figuring out how to call the main thread window.
This is the code:
#available(iOS 13.0, *)
#objc(KeycloakPlugin)
public class KeycloakPlugin: CAPPlugin, ObservableObject, ASWebAuthenticationPresentationContextProviding {
var webAuthSession: ASWebAuthenticationSession?
public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
return self.bridge?.webView?.window ?? ASPresentationAnchor()
}
This line compains when I open the external url for the authentication:
return self.bridge?.webView?.window ?? ASPresentationAnchor()
in this case I get:
UIView.window must be used from main thread only
Do you have any idea how to fix this?

Maybe this helps:
https://capacitorjs.com/docs/core-apis/ios
If you wrap your code in DispatchQueue.main.async, it should remove the warning. It also works with DispatchQueue.main.sync, depending on the implementation.
Something like this:
#objc(MyPlugin)
public class MyPlugin: CAPPlugin, ASWebAuthenticationPresentationContextProviding {
#objc func myPluginMethod(_ call: CAPPluginCall) {
// do something here
}
public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
var view: ASPresentationAnchor?
DispatchQueue.main.sync {
view = self.bridge?.webView?.window
// or use async and do something here, e.g. create an implementation instance and pass the view
}
return view ?? ASPresentationAnchor()
}
}

Related

Add delegate to custom iOS Flutter Plugin

I'm working on integrating a custom iOS plugin into my Flutter app, problem is that I'm not getting delegate callbacks from the custom SDK Protocol.
I have to connect a bluetooth device to my app and I from the delegate calls I should receive the device's ID and pair it.
From the Flutter side, I can call the native functions from the customSdk: sdkInstance.scan() and there are even some internal (inside the sdk) prints with the scan results but my delegate calls are not in place.
I think I'm not correctly adding the delegate to the SDK, I can get this to work in a swift native app but not as a Flutter Plugin.
So here's more or less the code:
iOS Code
AppDelegate.swift
import UIKit
import Flutter
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
SwiftIosPlugin.swift
import Flutter
import UIKit
import CustomSDK
public class SwiftIosPlugin: NSObject, FlutterPlugin {
let sdkInstance = CustomSDK.shared // This returns an instance of the SDK
let channel: FlutterMethodChannel
public static func register(with registrar: FlutterPluginRegistrar)
let channel = FlutterMethodChannel(name: "ios_plugin_channel", binaryMessenger: registrar.messenger())
let instance = SwiftIosPlugin(channel)
registrar.addMethodCallDelegate(instance, channel: channel)
registrar.addApplicationDelegate(instance)
}
init (_ channel: FlutterMethodChannel) {
self.channel = channel
super.init()
// In Swift, this is done in viewDidLoad()
// Is this the correct place to do this?
sdkInstance.addDelegate(self)
}
public func handle(_ call: FlutterMethodCall, result: #escaping FlutterResult) {
switch call.method {
case "startScan":
do {
// This is being called and results printed
try sdkInstance.scan()
} catch {
result(FlutterError(code: "400", message: "\(error)", details: nil))
}
case "connect":
sdkInstance.connect(call, result)
default:
result(FlutterMethodNotImplemented)
}
}
}
// These should be called but are not
extension SwiftIosPlugin: CustomSDKDelegate {
// Isn't called when scan() is executeed!
public func onScanDevice(didScan id:String) {
// do logic
}
public func onPairedDevice(didPair id:String) {
// do logic
}
}
Update:
Silly thing that I hope nobody else has this trouble...
Two things to consider:
The problem was some of the delegate's functions public func onScanDevice(didScan id:String) was missing a parameter (even though there weren't any errors pointed out by Xcode).
sdkInstance.addDelegate(self) was called too early in the class "lifecycle".
Be mindful of these things and you won't have any trouble!

Flutter: How to avoid automatic pluggin initialization on Flutter iOS

I'm working with a plugin that I would like to manually register in iOS only if some condition is meet. Right now, the autogenerated AppDelegate.swift registers all plugins included in the pubspect.yaml with this line:
GeneratedPluginRegistrant.register(with: self)
Is there any way to avoid registering a single plugin?
Thank you
Yes, there is a way (after browsing and reading Flutter iOS engine documentation).
write your plugin in Swift (this language is easier to understand. I am bad in objC ;) )
have AppDelegate.swift (in Swift language too)
create plugin in AppDelegate.swift (below AppDelegate class). This if for simplicity. In my case it is:
public class FlutterNativeTimezonePlugin: NSObject, FlutterPlugin {
public static func addManuallyToRegistry(registry: FlutterPluginRegistry) {
// https://api.flutter.dev/objcdoc/Protocols/FlutterPluginRegistry.html#/c:objc(pl)FlutterPluginRegistry(im)registrarForPlugin:
let registrar = registry.registrar(forPlugin: "flutter_native_timezone")
if let safeRegistrar = registrar {
register(with: safeRegistrar)
}
}
// this is an override of this fucnion:
// https://api.flutter.dev/objcdoc/Protocols/FlutterPlugin.html#/c:objc(pl)FlutterPlugin(cm)registerWithRegistrar:
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "flutter_native_timezone", binaryMessenger: registrar.messenger())
let instance = FlutterNativeTimezonePlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: #escaping FlutterResult) {
switch call.method {
case "getLocalTimezone":
result(NSTimeZone.local.identifier)
case "getAvailableTimezones":
result(NSTimeZone.knownTimeZoneNames)
default:
result(FlutterMethodNotImplemented)
}
}
}
below GeneratedPluginRegistrant.register(with: self) method add the plugin initialisation one: FlutterNativeTimezonePlugin.addManuallyToRegistry(registry: self)
So this is manual way of adding a plugin for the iOS build. Shame there is no documentation.

How can I use Rx Swift PublishRelay with no type just for onCompleted() event?

I have this view model in my code:
import RxSwift
protocol ViewModelInput {
func buttonTouched()
}
protocol ViewModelOutput {
var done : PublishRelay<Bool> { get set }
}
protocol ViewModelType {
var inputs: ViewModelInput { get }
var outputs: ViewModelOutput { get }
}
public final class ViewModel: ViewModelInput, ViewModelOutput, ViewModelType {
var inputs: ViewModelInput { return self }
var outputs: ViewModelOutput { return self }
internal var done = PublishRelay<Bool>.init()
init() {}
func buttonTouched() {
self.outputs.done.accept(true)
}
}
And I'm using it's "output" like this:
// Somewhere else in my app
viewModel.outputs.done
.asObservable()
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] _ in
// whatever
}).disposed(by: disposeBag)
To be honest I don't need that Boolean value with PublishRelay. I don't even need onNext() event. All I need is to notify my coordinator (part of app that uses this view model) about onCompleted(). However there is still some <Bool> generic type added to my output. I don't need any of that. Is there any cleaner way to achieve that?
I though about traits like Completable but as far as I understand I need to emit completed-event inside create() method or use Completable.empty(). Or maybe I don't understand traits that good, I don't know.
Any ideas?
I haven't done any RxSwift in a while, but have you tried making the type PublishRelay<Void>? Once you do that you can just pass () to outputs.done.accept(()) in your buttonTouched() method and not have to worry about passing arbitrary information that isn't needed
I think #Steven0351 is right with the < Void> approach. Just 2 little things:
It should also work by terminating the subject instead of emitting a Void value. It looks cleaner in the subscription as well.
I guess you are subscribing your outputs.done subject in the UI. In that case you might want to use Drivers. That way there's no need to specify observation on main scheduler (among other Drivers advantages).
ViewModel
internal var done = PublishRelay<Void>.init()
func buttonTouched() {
self.outputs.done.onCompleted()
}
ViewController
viewModel.outputs.done
.asDriver()
.drive(onCompleted: { [weak self] in
// whatever
}).disposed(by: disposeBag)

Declaration 'subscribe' cannot override more than one superclass declaration (ReSwift)

I'm having a problem when overriding a function from the ReSwift Pod. I've got the following mock class:
import Foundation
import Quick
import Nimble
import RxSwift
#testable import MainProject
#testable import ReSwift
class MockReSwiftStore: ReSwift.Store<MainState> {
var dispatchDidRun: Bool = false
var subscribeWasTriggered: Bool = false
init() {
let reducer: Reducer<MainState> = {_, _ in MainState() }
super.init(reducer: reducer, state: nil)
}
required init(
reducer: #escaping (Action, State?) -> State,
state: State?,
middleware: [(#escaping DispatchFunction, #escaping () -> State?) -> (#escaping DispatchFunction) -> DispatchFunction]) {
super.init(reducer: reducer, state: state, middleware: middleware)
}
override func subscribe<SelectedState, S>(
_ subscriber: S,
transform: ((Subscription<MainState>) -> Subscription<SelectedState>)?)
where S: StoreSubscriber,
S.StoreSubscriberStateType == SelectedState {
subscribeWasTriggered = true
}
}
}
And when overriding the subscribe method I'm getting following errors
Then when using autocomplete it also shows 2 occurences:
However when looking for the original function there's only one which looks like this
open func subscribe<SelectedState, S: StoreSubscriber>(
_ subscriber: S, transform: ((Subscription<State>) -> Subscription<SelectedState>)?
) where S.StoreSubscriberStateType == SelectedState
{
// Create a subscription for the new subscriber.
let originalSubscription = Subscription<State>()
// Call the optional transformation closure. This allows callers to modify
// the subscription, e.g. in order to subselect parts of the store's state.
let transformedSubscription = transform?(originalSubscription)
_subscribe(subscriber, originalSubscription: originalSubscription,
transformedSubscription: transformedSubscription)
}
This is my compiler output
I'm out of ideas so any help is greatly appreciated
Thanks!
Here is your issue:
class Some<T> {
func echo() {
print("A")
}
}
extension Some where T: Equatable {
func echo() {
print("B")
}
}
class AnotherSome: Some<String> {
override func echo() {
print("Doesn't compile")
}
}
The problem is: ReSwift developers declare Store.subscribe behavior as a part of interface and as a part of extension (I am not sure why they chose to do it instead of introducing other objects). Swift can't figure out which part you are trying to override and thus it doesn't compile. Afaik there are no language instruments which allow you to resolve this issue.
A possible solution is to implement MockStore as a StoreType and use Store object to implement behavior for StoreType interface.

Is this the best way to convert Swift protocol to RxDelegateProxy?

Sorry I could not come up with better title than that, Ill modify it if anybody suggests a better one after.
I have a protocol
#objc public protocol MyCollectionViewProtocol {
func scrollViewShouldScrollToTop()
}
I have declared it to be #objc because unfortunately DelegateProxy does not work with non NSObject protocols (I assume, if somebody can clarify that, will be a great help)
My collectionView
public class MyCollectionView: UICollectionView {
weak var cvDelegate : MyCollectionViewProtocol?
... //rest of the code isnt related to this question in particular
Now I declare delegate proxy as
open class RxMyCollectionViewDelegateProxy : DelegateProxy<MyCollectionView, MyCollectionViewProtocol>
, DelegateProxyType
, MyCollectionViewProtocol {
public static func currentDelegate(for object: MyCollectionView) -> MyCollectionViewProtocol? {
return object.cvDelegate
}
public static func setCurrentDelegate(_ delegate: MyCollectionViewProtocol?, to object: MyCollectionView) {
object.cvDelegate = delegate
}
public weak private(set) var collectionView: MyCollectionView?
internal lazy var shouldScrollPublishSubject: PublishSubject<Void> = {
let localSubject = PublishSubject<Void>()
return localSubject
}()
public init(collectionView: ParentObject) {
self.collectionView = collectionView
super.init(parentObject: collectionView, delegateProxy: RxMyCollectionViewDelegateProxy.self)
}
// Register known implementations
public static func registerKnownImplementations() {
self.register { RxMyCollectionViewDelegateProxy(collectionView: $0) }
}
//implementation of MyCollectionViewProtocol
public func scrollViewShouldScrollToTop() {
shouldScrollPublishSubject.onNext(())
self._forwardToDelegate?.scrollViewShouldScrollToTop()
}
deinit {
shouldScrollPublishSubject.onCompleted()
}
}
Finally I declare my Reactive extension for MyCollectionView as
extension Reactive where Base: MyCollectionView {
public var delegate: DelegateProxy<MyCollectionView, MyCollectionViewProtocol> {
return RxMyCollectionViewDelegateProxy.proxy(for: base)
}
public var shouldScrollToTop: ControlEvent<Void> {
let source = RxMyCollectionViewDelegateProxy.proxy(for: base).shouldScrollPublishSubject
return ControlEvent(events: source)
}
}
Finally, I use it as
collectionView.rx.shouldScrollToTop.debug().subscribe(onNext: { (state) in
print("I should scroll to top")
}, onError: { (error) in
print("errored out")
}, onCompleted: {
print("completed")
}, onDisposed: {
print("Disposed")
}).disposed(by: disposeBag)
Question
Because none of the online tutorials(Raywenderlich)/courses (Udemy)/Books(Raywenderlich) explains how to convert the swift protocol to Rx style am confused as what I am doing is correct or wrong. The code works but even the worst designed code might work, hence I wanna be sure what am doing is correct or am messing something. I wrote the above code following the approach used in UIScrollView+Rx.swift and RxScrollViewDelegateProxy.swift
Though the code above works only for protocols without any return type example method I used above func scrollViewShouldScrollToTop() has no return type associated with it. I could not imagine how could I use the DelegateProxy above to convert the protocol methods with return types, like numberOfRowsInSection which has Int as return type.
I happened to look at the RxDataSource implementation and realized in order to convert cellForRowAtIndexPath RxDataSource constructor expects you to pass the block as a init parameter and executes it whenever tableView calls cellForRowAtIndexPath in its proxyDelegate.
Now I could do the same thing if thats the only way out. Need to know is that how am supposed to code it or can I modify ProxyDelegate implementation above to convert the protocol method with return types.

Resources