IOS SwiftUI - #main -> Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffee1a57ff0) - ios

In the code below you can see the error and the code used in my xxApp.swift folder.
I was creating Sign In and a Sign Up function, and everything worked very well, but then I got this error when clicking on the register button.
If you need more code just let me know, thanks in advance!
Here is the code :
import SwiftUI
import Firebase
final class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
final class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}`final class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
}
#main -> "Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffee1a57ff0)"
struct PSMAApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
#StateObject var sessionService = SessionServiceImpl()
var body: some Scene {
WindowGroup {
NavigationView{
switch sessionService.state {
case .loggedIn:
HomeView()
.environmentObject(sessionService)
case .loggedOut:
LoginView()
}
}
}
}
}
Here is the register Button - ViewModel :
import Foundation
import Combine
enum RegistrationState {
case successfull
case failed(error: Error)
case na
}
protocol RegistrationViewModel {
func register()
var hasError: Bool { get }
var service: RegistrationService { get }
var state: RegistrationState { get }
var userDetails: RegistrationDetails { get }
init(service: RegistrationService)
}
final class RegistrationViewModelImpl: ObservableObject, RegistrationViewModel {
#Published var hasError: Bool = false
#Published var state: RegistrationState = .na
let service: RegistrationService
var userDetails: RegistrationDetails = RegistrationDetails.new
private var subscriptions = Set<AnyCancellable>()
init(service: RegistrationService) {
self.service = service
setupErrorSubscriptions()
}
func register() {
service
.register(with: userDetails)
.sink { [weak self] res in
switch res {
case .failure(let error):
self?.state = .failed(error: error)
default: break
}
} receiveValue: { [weak self] in
self?.state = .successfull
}
.store(in: &subscriptions)
}
}
private extension RegistrationViewModelImpl {
func setupErrorSubscriptions() {
$state
.map { state -> Bool in
switch state {
case .successfull,
.na:
return false
case .failed:
return true
}
}
.assign(to: &$hasError)
}
}
Here is Register - RegistrationService :
import Combine
import Foundation
import Firebase
import FirebaseDatabase
enum RegistrationKeys: String {
case username
}
protocol RegistrationService {
func register(with details: RegistrationDetails) -> AnyPublisher<Void, Error>
}
final class RegistrationServiceImpl: RegistrationService {
func register(with details: RegistrationDetails) -> AnyPublisher<Void, Error> {
Deferred {
Future { promise in
Auth.auth()
.createUser(withEmail: details.email,
password: details.password) {res, error in
if let err = error {
promise(.failure(err))
} else {
if let uid = res?.user.uid {
let values = [
RegistrationKeys.username.rawValue: details.username] as [String : Any]
Database.database()
.reference()
.child("users")
.child(uid)
.updateChildValues(values) {
error, ref in
if let err = error {
promise(.failure(err))
} else {
promise(.success(()))
}
}
} else {
promise(.failure(NSError(domain: "Invalid user ID", code: 0, userInfo: nil)))
}
}
}
}
}
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
}
Here is Registraion - SessionService :
import Combine
import Foundation
import FirebaseAuth
enum SessionState {
case loggedIn
case loggedOut
}
protocol SessionService {
var state: SessionState { get }
var userDetails: SessionUserDetails? { get }
func logout()
}
final class SessionServiceImpl: ObservableObject, SessionService {
#Published var state: SessionState = .loggedOut
#Published var userDetails: SessionUserDetails?
private var handler: AuthStateDidChangeListenerHandle?
init() {
setupFirebaseAuthHandler()
}
func logout() {
try? Auth.auth().signOut()
}
}
private extension SessionServiceImpl {
func setupFirebaseAuthHandler() {
handler = Auth
.auth()
.addStateDidChangeListener { [weak self] res, user in
guard let self = self else { return }
self.state = user == nil ? .loggedOut : .loggedIn
if let uid = user?.uid {
self.handleRefresh(with: uid)
}
}
}
func handleRefresh(with uid: String) {
Database
.database()
.reference()
.child("users")
.child(uid)
.observe(.value) { [weak self] snapshot in
guard let self = self,
let value = snapshot.value as? NSDictionary,
let username = value[RegistrationKeys.username.rawValue] as? String else{
return
}
DispatchQueue.main.async {
self.userDetails = SessionUserDetails(username: username)
}
}
}
}
Here is the Register - Model :
struct RegistrationDetails {
var email: String
var username: String
var password: String
}
extension RegistrationDetails {
static var new: RegistrationDetails {
RegistrationDetails(email: "",
username: "",
password: "")
}
}
Here is the Registration - View :
import SwiftUI
struct RegisterView: View {
#StateObject private var vm = RegistrationViewModelImpl(
service: RegistrationServiceImpl()
)
var body: some View {
NavigationView {
VStack(spacing: 32) {
VStack(spacing: 16) {
InputTextFieldView(text: $vm.userDetails.email,
placeholder: "Email",
keyboardType: .emailAddress,
sfSymbol: "envelope")
InputPasswordView(password: $vm.userDetails.password,
placeholder: "Password",
sfSymbol: "lock")
Divider()
InputTextFieldView(text: $vm.userDetails.username,
placeholder: "Username",
keyboardType: .namePhonePad,
sfSymbol: nil)
}
ButtonComponentView(title: "Sign up") {
vm.register()
}
}
.padding(.horizontal, 15)
.navigationTitle("Register")
.applyClose()
.alert(isPresented: $vm.hasError,
content: {
if case .failed(let error) = vm.state {
return Alert(
title: Text("Error"),
message: Text(error.localizedDescription))
} else {
return Alert(
title: Text("Error"),
message: Text("Something went wrong"))
}
})
}
}
}
struct RegisterView_Previews: PreviewProvider {
static var previews: some View {
RegisterView()
.preferredColorScheme(.dark)
}
}

Related

Network request using Combine doesn't execute

I have the following classes that perform a network call -
import SwiftUI
import Combine
struct CoinsView: View {
private let coinsViewModel = CoinViewModel()
var body: some View {
Text("CoinsView").onAppear {
self.coinsViewModel.fetchCoins()
}
}
}
class CoinViewModel: ObservableObject {
private let networkService = NetworkService()
#Published var data = String()
var cancellable : AnyCancellable?
func fetchCoins() {
cancellable = networkService.fetchCoins().sink(receiveCompletion: { _ in
print("inside receive completion")
}, receiveValue: { value in
print("received value - \(value)")
})
}
}
class NetworkService: ObservableObject {
private var urlComponents : URLComponents {
var components = URLComponents()
components.scheme = "https"
components.host = "jsonplaceholder.typicode.com"
components.path = "/users"
return components
}
var cancelablle : AnyCancellable?
func fetchCoins() -> AnyPublisher<Any, URLError> {
return URLSession.shared.dataTaskPublisher(for: urlComponents.url!)
.map{ $0.data }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}
What I want to achieve currently is just to print the JSON result.
This doesn't seem to work, and from debugging it never seems to go inside the sink{} method, therefor not executing it.
What am I missing?
After further investigation with Asperi's help I took the code to a clean project and saw that I have initialized a struct that wraps NSPersistentContainer which causes for some reason my network requests not to work. Here is the code, hopefully someone can explain why it prevented my networking to execute -
import SwiftUI
#main
struct BasicApplication: App {
let persistenceController = BasicApplciationDatabase.instance
#Environment(\.scenePhase)
var scenePhase
var body: some Scene {
WindowGroup {
CoinsView()
}
.onChange(of: scenePhase) { newScenePhase in
switch newScenePhase {
case .background:
print("Scene is background")
persistenceController.save()
case .inactive:
print("Scene is inactive")
case .active:
print("Scene is active")
#unknown default:
print("Scene is unknown default")
}
}
}
}
import CoreData
struct BasicApplciationDatabase {
static let instance = BasicApplciationDatabase()
let container : NSPersistentContainer
init() {
container = NSPersistentContainer(name: "CoreDataDatabase")
container.loadPersistentStores { NSEntityDescription, error in
if let error = error {
fatalError("Error: \(error.localizedDescription)")
}
}
}
func save(completion : #escaping(Error?) -> () = {_ in} ){
let context = container.viewContext
if context.hasChanges {
do {
try context.save()
completion(nil)
} catch {
completion(error)
}
}
}
func delete(_ object: NSManagedObject, completion : #escaping(Error?) -> () = {_ in} ) {
let context = container.viewContext
context.delete(object)
save(completion: completion)
}
}

Admob sdk v8.0+ rewarded ad in SwiftUI

I am trying to make rewarded ad from Admob in my SwiftUI app but faced some problems. I am using official documentation that is written in obj-c and trying to make swift class from it.
Here what i have
final class Rewarded: NSObject, GADFullScreenContentDelegate {
let token = Bundle.main.object(forInfoDictionaryKey: "GADApplicationIdentifier") as? String
var rewardedAd = GADRewardedAd()
var rewardFunction: (() -> Void)? = nil
override init() {
super.init()
LoadRewarded()
}
func LoadRewarded() {
let req = GADRequest()
GADRewardedAd.load(withAdUnitID: token!, request: req, completionHandler: { gad, error in
print(error)
})
}
func showAd(rewardFunction: #escaping () -> Void){
let root = UIApplication.shared.windows.first?.rootViewController
do {
try self.rewardedAd.canPresent(fromRootViewController: root!)
self.rewardFunction = rewardFunction
self.rewardedAd.present(fromRootViewController: root!, userDidEarnRewardHandler: rewardFunction)
} catch let error {
print(error)
}
}
func rewardedAd(_ rewardedAd: GADRewardedAd, userDidEarn reward: GADAdReward) {
if let rf = rewardFunction {
rf()
}
}
func rewardedAdDidDismiss(_ rewardedAd: GADRewardedAd) {
self.rewardedAd = GADRewardedAd()
LoadRewarded()
}
}
In method showAd i have an exception nilerror from invocation of try self.rewardedAd.canPresent(fromRootViewController: root!) and have no idea what to do with that. I didn't find any tutorial that shows how to set up it with version of sdk 8+, can you please help me to figure out what is the problem.
Found a solution, Google has paper on migration to v8+ sdk. So, from https://medium.com/#michaelbarneyjr/how-to-integrate-admob-ads-in-swiftui-fbfd3d774c50 and https://developers.google.com/admob/ios/migration#swift_7 I made a working rewarded ad with version of sdk 8.5 in SwiftUI
final class Rewarded: NSObject, GADFullScreenContentDelegate {
var rewardedAd: GADRewardedAd?
var rewardFunction: (() -> Void)? = nil
override init() {
super.init()
LoadRewarded()
}
func LoadRewarded(){
let request = GADRequest()
GADRewardedAd.load(withAdUnitID: Bundle.main.object(forInfoDictionaryKey: "GADApplicationIdentifier") as! String,
request: request, completionHandler: { (ad, error) in
if let error = error {
print("Rewarded ad failed to load with error: \(error.localizedDescription)")
return
}
self.rewardedAd = ad
self.rewardedAd?.fullScreenContentDelegate = self
}
)
}
func showAd(rewardFunction: #escaping () -> Void){
let root = UIApplication.shared.windows.first?.rootViewController
if let ad = rewardedAd {
ad.present(fromRootViewController: root!,
userDidEarnRewardHandler: {
let reward = ad.adReward
rewardFunction()
}
)
} else {
print("Ad wasn't ready")
}
}
func rewardedAd(_ rewardedAd: GADRewardedAd, userDidEarn reward: GADAdReward) {
if let rf = rewardFunction {
rf()
}
}
}
I found this code. I think it can help you:
import SwiftUI
import GoogleMobileAds
import UIKit
final class Rewarded: NSObject, GADRewardedAdDelegate{
var rewardedAd:GADRewardedAd = GADRewardedAd(adUnitID: rewardID)
var rewardFunction: (() -> Void)? = nil
override init() {
super.init()
LoadRewarded()
}
func LoadRewarded(){
let req = GADRequest()
self.rewardedAd.load(req)
}
func showAd(rewardFunction: #escaping () -> Void){
if self.rewardedAd.isReady{
self.rewardFunction = rewardFunction
let root = UIApplication.shared.windows.first?.rootViewController
self.rewardedAd.present(fromRootViewController: root!, delegate: self)
}
else{
print("Not Ready")
}
}
func rewardedAd(_ rewardedAd: GADRewardedAd, userDidEarn reward: GADAdReward) {
if let rf = rewardFunction {
rf()
}
}
func rewardedAdDidDismiss(_ rewardedAd: GADRewardedAd) {
self.rewardedAd = GADRewardedAd(adUnitID: rewardID)
LoadRewarded()
}
}
struct ContentView:View{
var rewardAd:Rewarded
init(){
self.rewardAd = Rewarded()
}
var body : some View{
Button(action: {
self.rewardAd.showAd(rewardFunction: {
print("Give Reward")
}
}){
Text("My Button")
}
}
}
Here is the link where I find the snippet: https://medium.com/#michaelbarneyjr/how-to-integrate-admob-ads-in-swiftui-fbfd3d774c50

Unable to implement Apple Pay in SwiftUI

I'm trying to implement Apple Pay in my SwiftUI app and I'm stuck at showing the button.
I have done that by using UIViewRepresentable
import SwiftUI
import UIKit
import PassKit
import Foundation
struct ApplePayButton: UIViewRepresentable {
func makeCoordinator() -> Coordinator {
Coordinator()
}
func updateUIView(_ uiView: PKPaymentButton, context: Context) {
}
func makeUIView(context: Context) -> PKPaymentButton {
let paymentButton = PKPaymentButton(paymentButtonType: .plain, paymentButtonStyle: .black)
return paymentButton
}
class Coordinator: NSObject, PKPaymentAuthorizationViewControllerDelegate {
func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
//
}
func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, handler completion: #escaping (PKPaymentAuthorizationResult) -> Void) {
print("did authorize payment")
}
func paymentAuthorizationViewControllerWillAuthorizePayment(_ controller: PKPaymentAuthorizationViewController) {
print("Will authorize payment")
}
}
}
class ApplePayManager: NSObject {
let currencyCode: String
let countryCode: String
let merchantID: String
let paymentNetworks: [PKPaymentNetwork]
let items: [PKPaymentSummaryItem]
init(items: [PKPaymentSummaryItem],
currencyCode: String = "EUR",
countryCode: String = "AT",
merchantID: String = "c.c.c",
paymentNetworks: [PKPaymentNetwork] = [PKPaymentNetwork.masterCard, PKPaymentNetwork.visa]) {
self.items = items
self.currencyCode = currencyCode
self.countryCode = countryCode
self.merchantID = merchantID
self.paymentNetworks = paymentNetworks
}
func paymentViewController() -> PKPaymentAuthorizationViewController? {
if PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: paymentNetworks) {
let request = PKPaymentRequest()
request.currencyCode = self.currencyCode
request.countryCode = self.countryCode
request.supportedNetworks = paymentNetworks
request.merchantIdentifier = self.merchantID
request.paymentSummaryItems = items
request.merchantCapabilities = [.capabilityCredit, .capabilityDebit]
return PKPaymentAuthorizationViewController(paymentRequest: request)
}
return nil
}
}
I do not want to use PKPaymentAuthorizationController because I want to use the native button.
When I click at the button I get this error:
[General] Payment request is invalid: Error Domain=PKPassKitErrorDomain Code=1 "Invalid in-app payment request" UserInfo={NSLocalizedDescription=Invalid in-app payment request, NSUnderlyingError=0x600003aeebb0 {Error Domain=PKPassKitErrorDomain Code=1 "PKPaymentRequest must contain an NSArray property 'paymentSummaryItems' of at least 1 valid objects of class PKPaymentSummaryItem" UserInfo={NSLocalizedDescription=PKPaymentRequest must contain an NSArray property 'paymentSummaryItems' of at least 1 valid objects of class PKPaymentSummaryItem}}}
View:
struct PaymentView: View {
#Environment(\.presentationMode) private var presentationMode
#ObservedObject var requestViewModel: RequestViewModel
var applePayManager = ApplePayManager(items: [
PKPaymentSummaryItem(label: "Some Product", amount: 9.99)
])
var body: some View {
NavigationView {
VStack {
Text("By paying you agree to give the package to transporter.")
// requestViewModel.respondToRequest(status: button.status)
ApplePayButton()
.frame(width: 228, height: 40, alignment: .center)
.onTapGesture {
applePayManager.paymentViewController()
}
}
.navigationBarTitle("Payment")
.navigationBarItems(trailing: Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
Text("Done")
})
}
}
}
What am I doing wrong here?
Just in case someone else is struggling like me: Here is the full code.
import Foundation
import PassKit
class PaymentHandler: NSObject, ObservableObject {
func startPayment(paymentSummaryItems: [PKPaymentSummaryItem]) {
// Create our payment request
let paymentRequest = PKPaymentRequest()
paymentRequest.paymentSummaryItems = paymentSummaryItems
paymentRequest.merchantIdentifier = "merchant.de.xxx"
paymentRequest.merchantCapabilities = .capability3DS
paymentRequest.countryCode = "AT"
paymentRequest.currencyCode = "EUR"
paymentRequest.requiredShippingContactFields = [.phoneNumber, .emailAddress]
paymentRequest.supportedNetworks = [.masterCard, .visa]
// Display our payment request
let paymentController = PKPaymentAuthorizationController(paymentRequest: paymentRequest)
paymentController.delegate = self
paymentController.present(completion: { (presented: Bool) in })
}
}
/**
PKPaymentAuthorizationControllerDelegate conformance.
*/
extension PaymentHandler: PKPaymentAuthorizationControllerDelegate {
func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didAuthorizePayment payment: PKPayment, completion: #escaping (PKPaymentAuthorizationStatus) -> Void) {
completion(.success)
print("paymentAuthorizationController completion(.success)")
}
func paymentAuthorizationControllerDidFinish(_ controller: PKPaymentAuthorizationController) {
print("DidFinish")
}
func paymentAuthorizationControllerWillAuthorizePayment(_ controller: PKPaymentAuthorizationController) {
print("WillAuthorizePayment")
}
}
struct PaymentButton: UIViewRepresentable {
func updateUIView(_ uiView: PKPaymentButton, context: Context) { }
func makeUIView(context: Context) -> PKPaymentButton {
return PKPaymentButton(paymentButtonType: .plain, paymentButtonStyle: .automatic)
}
}
Use it in the view:
PaymentButton()
.frame(width: 228, height: 40, alignment: .center)
.onTapGesture {
paymentHandler.startPayment(paymentSummaryItems: paymentSummaryItems)
}

How to unit test RxCocoa BehaviorRelay

I am starting out with unit testing RxSwift Driver. And I am having issues testing a Driver.
This is the code structure of my ViewModel:
import Foundation
import RxSwift
import RxCocoa
class LoginViewViewModel {
private let loginService: LoginService
private let _loading = BehaviorRelay<Bool>(value: false)
private let _loginResponse = BehaviorRelay<LoginResponse?>(value: nil)
private let _phoneMessage = BehaviorRelay<String>(value: "")
private let _pinMessage = BehaviorRelay<String>(value: "")
private let _enableButton = BehaviorRelay<Bool>(value: false)
var loginResponse: Driver<LoginResponse?> { return _loginResponse.asDriver() }
var loading: Driver<Bool> { return _loading.asDriver() }
var phoneMessage: Driver<String> { return _phoneMessage.asDriver() }
var pinMessage: Driver<String> { return _pinMessage.asDriver() }
var enableButton: Driver<Bool> { return _enableButton.asDriver() }
private let phone = BehaviorRelay<String>(value: "")
private let pin = BehaviorRelay<String>(value: "")
private let disposeBag = DisposeBag()
init(phone: Driver<String>, pin: Driver<String>, buttonTapped: Driver<Void>, loginService: LoginService) {
self.loginService = loginService
phone
.throttle(0.5)
.distinctUntilChanged()
.drive(onNext: { [weak self] (phone) in
self?.phone.accept(phone)
self?.validateFields()
}).disposed(by: disposeBag)
pin
.throttle(0.5)
.distinctUntilChanged()
.drive(onNext: { [weak self] (pin) in
self?.pin.accept(pin)
self?.validateFields()
}).disposed(by: disposeBag)
buttonTapped
.drive(onNext: { [weak self] () in
self?.loginUser(phone: self!.phone.value, pin: self!.pin.value)
}).disposed(by: disposeBag)
}
private func validateFields() {
guard phone.value.count > 0 else {
return
}
_enableButton.accept(false)
guard pin.value.count > 0 else {
return
}
_enableButton.accept(true)
_phoneMessage.accept("")
_pinMessage.accept("")
}
private func loginUser(phone: String, pin: String) {
_loading.accept(true)
_phoneMessage.accept("")
_pinMessage.accept("")
loginService.loginUser(phone: phone, pin: pin) { [weak self] (response, error) in
self?._loading.accept(false)
if let error = error {
if error.message! == "Invalid credentials" {
self?._phoneMessage.accept("Invalid Phone Number")
self?._pinMessage.accept("Invalid Pin Provided")
}
} else {
response?.saveUserInfo()
self?._loginResponse.accept(response)
}
}
}
}
And my UnitTest looks like this:
class LoginViewViewModelTest: XCTestCase {
private class MockLoginService: LoginService {
func loginUser(phone: String, pin: String, completion: #escaping LoginService.LoginDataCompletion) {
guard phone == "+17045674568", pin == "1234" else {
let loginresponse = LoginResponse(message: "Login Successfully", status: true, status_code: 200, data: LoginData(access_token: "adadksdewffjfwe", token_type: "bearer", expires_in: 3600, expiry_time: "today", user: User(id: "1dsldsdsjkj", name: "RandomGuy", phone: "12345", pin_set: true, custom_email: false, email: "somerandom#email.com")))
completion(loginresponse, nil)
return
}
let akuError = AKUError(status: false, message: "Invalid Credential.", status_code: "404")
completion(nil, akuError)
}
}
var viewModel: LoginViewViewModel!
var scheduler: SchedulerType!
var phone: BehaviorRelay<String>!
var pin: BehaviorRelay<String>!
var buttonClicked: BehaviorRelay<Void>!
override func setUp() {
super.setUp()
phone = BehaviorRelay<String>(value: "")
pin = BehaviorRelay<String>(value: "")
buttonClicked = BehaviorRelay<Void>(value: ())
let loginService = MockLoginService()
viewModel = LoginViewViewModel(phone: phone.asDriver(), pin: pin.asDriver(), buttonTapped: buttonClicked.asDriver(), loginService: loginService)
scheduler = ConcurrentDispatchQueueScheduler(qos: .default)
}
override func tearDown() {
super.tearDown()
}
func testLoginButtonClicked_Loading() {
let loadingObservable = viewModel.loading.asObservable().subscribeOn(scheduler)
phone.accept("12345")
pin.accept("12345")
buttonClicked.accept(())
let loadingState = try! loadingObservable.skip(0).toBlocking().first()!
XCTAssertNotNil(loadingState)
XCTAssertEqual(loadingState, true)
}
}
My question:
I am trying to track the state of the loading driver variable. But, it's always false. Even after writing a debugger for checking the states, it only prints out one value and, it's always false.
I decided to add a break point to the code, and I noticed
let loadingState = try! loadingObservable.skip(0).toBlocking().first()! only gets called once the function is done executing.
Is there a way to test for the loading state?
Is it necessary to test for the loading state?
Thanks.
I believe the problem is that RxBlocking only deals with the first event that is emitted. You need to look at a series of events. Look into using RxTest instead. Here is a unit test using RxTest that passes with the view model you created:
class LoginLoadingTests: XCTestCase {
var scheduler: TestScheduler!
var result: TestableObserver<Bool>!
var bag: DisposeBag!
override func setUp() {
super.setUp()
scheduler = TestScheduler(initialClock: 0)
result = scheduler.createObserver(Bool.self)
bag = DisposeBag()
}
func testLoading() {
let loginService = MockLoginService { phone, pin, response in
self.scheduler.scheduleAt(20, action: { response(nil, RxError.unknown) })
}
let tap = scheduler.createHotObservable([.next(10, ())])
let viewModel = LoginViewViewModel(phone: Driver.just("9876543210"), pin: Driver.just("1234"), buttonTapped: tap.asDriver(onErrorJustReturn: ()), loginService: loginService)
viewModel.loading
.drive(result)
.disposed(by: bag)
scheduler.start()
XCTAssertEqual(result.events, [
.next(0, false),
.next(10, true),
.next(20, false)
])
}
}
struct MockLoginService: LoginService {
init(loginUser: #escaping (_ phone: String, _ pin: String, _ response: #escaping (LoginResponse?, Error?) -> Void) -> Void) {
_loginUser = loginUser
}
func loginUser(phone: String, pin: String, response: #escaping (LoginResponse?, Error?) -> ()) {
_loginUser(phone, pin, response)
}
let _loginUser: (_ phone: String, _ pin: String, _ response: #escaping (LoginResponse?, Error?) -> Void) -> Void
}

AWSMobileHubHelper add User pool as AWSIdentityProvider, how to handle `func login(completionHandler: (AnyObject, NSError) -> Void)`?

I want to add AWS User pool as a AWSIdentityProvider to my iOS Application,
But there is little guide for the AWSMobileHubHelper.
So I've try it by my self.
In my LoginViewController I treat AWS User pool as other provider like this:
final
class LoginViewController: UIViewController {
#IBOutlet weak var usernameTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var loginButton: UIButton!
var passwordAuthenticationCompletion: AWSTaskCompletionSource!
var completionHandler: ((AnyObject, NSError) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
AWSFacebookSignInProvider.sharedInstance().setPermissions(["public_profile"]);
AWSGoogleSignInProvider.sharedInstance().setScopes(["profile", "openid"])
AWSGoogleSignInProvider.sharedInstance().setViewControllerForGoogleSignIn(self)
}
#IBAction private func facebookLogin() {
handleLogin(signInProvider: AWSFacebookSignInProvider.sharedInstance())
}
#IBAction private func googleLogin() {
handleLogin(signInProvider: AWSGoogleSignInProvider.sharedInstance())
}
#IBAction private func myLogin() {
handleLogin(signInProvider: LoginProvider.sharedInstance())
}
private func handleLogin(signInProvider signInProvider: AWSSignInProvider) {
title = "Loging ..."
AWSIdentityManager.defaultIdentityManager().loginWithSignInProvider(signInProvider) { (result, error) in
switch error {
case let error? where error.domain != "success":
print("Login failed.")
default:
print("Login succeed.")
}
}
}
}
My LoginProvider code:
final
class LoginProvider: NSObject {
static func sharedInstance() -> LoginProvider {
return _sharedInstance
}
static private let _sharedInstance = LoginProvider()
lazy var pool: AWSCognitoIdentityUserPool = {
let serviceConfiguration = AWSServiceConfiguration(region: .USEast1, credentialsProvider: nil)
let poolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: "clientId", clientSecret: "clientSecret", poolId: "poolId")
AWSCognitoIdentityUserPool.registerCognitoIdentityUserPoolWithConfiguration(serviceConfiguration, userPoolConfiguration: poolConfiguration, forKey: "UserPool")
let result = AWSCognitoIdentityUserPool(forKey: "UserPool")
result.delegate = self
return result
}()
}
extension LoginProvider: AWSCognitoIdentityInteractiveAuthenticationDelegate {
func startPasswordAuthentication() -> AWSCognitoIdentityPasswordAuthentication {
return loginViewController
}
func startMultiFactorAuthentication() -> AWSCognitoIdentityMultiFactorAuthentication {
fatalError("Identity MultiFactor Authentication Not Supportted!")
}
private var loginViewController: LoginViewController {
return LoginViewController.sharedInstance
}
}
extension LoginProvider: AWSIdentityProvider {
var identityProviderName: String {
return pool.identityProviderName
}
func token() -> AWSTask {
return pool.token()
}
}
extension LoginProvider: AWSSignInProvider {
var loggedIn: Bool {
#objc(isLoggedIn) get {
return currentUser?.signedIn ?? false
}
}
var imageURL: NSURL? {
return nil
}
var userName: String? {
return currentUser?.username
}
func login(completionHandler: (AnyObject, NSError) -> Void) {
loginViewController.completionHandler = completionHandler
loginViewController.doLogin()
}
func logout() {
currentUser?.signOut()
}
func reloadSession() {
currentUser?.getSession()
}
func interceptApplication(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
return true
}
func interceptApplication(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
return false
}
private var currentUser: AWSCognitoIdentityUser? {
return pool.currentUser()
}
}
I've extended my LoginViewController to support the process:
extension LoginViewController: AWSCognitoIdentityPasswordAuthentication {
func doLogin() {
pool.getUser(usernameTextField.text!).getSession(usernameTextField.text!, password: passwordTextField.text!, validationData: nil).continueWithBlock { task -> AnyObject? in
print("pool.getUser().getSession")
print("task.result:", task.result)
print("task.error:", task.error)
if let session = task.result as? AWSCognitoIdentityUserSession {
print("session.idToken:", session.idToken?.tokenString)
print("session.accessToken:", session.accessToken?.tokenString)
print("session.refreshToken:", session.refreshToken?.tokenString)
print("session.expirationTime:", session.expirationTime)
}
switch (task.result, task.error) {
case let (_, error?):
self.completionHandler?("", error)
self.completionHandler = nil
case let (result?, _):
self.completionHandler?(result, NSError(domain: "success", code: -1, userInfo: nil))
self.completionHandler = nil
default: break
}
return nil
}
}
func getPasswordAuthenticationDetails(authenticationInput: AWSCognitoIdentityPasswordAuthenticationInput, passwordAuthenticationCompletionSource: AWSTaskCompletionSource) {
self.passwordAuthenticationCompletion = passwordAuthenticationCompletionSource
}
func didCompletePasswordAuthenticationStepWithError(error: NSError?) {
switch error {
case let error?:
Queue.Main.execute {
self.completionHandler?("", error)
self.completionHandler = nil
}
default: break
}
}
private var pool: AWSCognitoIdentityUserPool! {
return LoginProvider.sharedInstance().pool
}
}
extension LoginViewController {
#nonobjc static let navigationController = UIStoryboard(name: "Login", bundle: nil).instantiateInitialViewController() as! UINavigationController
static var sharedInstance: LoginViewController {
return navigationController.viewControllers[0] as! LoginViewController
}
}
Now I can login to my user pool, but I can't integrate it with federated id pool, It think I've give a wrong result when use completionHandler.
In LoginViewController's func doLogin() , when i get the getSession()'s result which is AWSCognitoIdentityUserSession, How can I convert it to other result that completionHandler needed?

Resources