“Operation canceled” Realm Error Domain=io.realm.unknown Code=89 - ios

I get the exact same error as #62999928, but not with the same configuration. I'm just trying to access a global Realm file which is on my Realm Cloud (I am NOT on MongoDB and the beta).
I get the “Operation canceled” Realm Error Domain=io.realm.unknown Code=89 error when I try to open the Realm.
Realm Studio:
Opening code:
let url = URL(string: "realms://\(MY_INSTANCE_ADDRESS)/common")!
let config = user.configuration(realmURL: url, fullSynchronization: true)
Realm.asyncOpen(configuration: config) { ... }
Authentication code (using PromiseKit):
private func connectToRealm(_ firebaseUser: User) -> Promise<SyncUser> {
// if realm user already logged in, check if it's the right user
if let user = SyncUser.current {
guard user.identity == firebaseUser.uid else {
// it's not the right user. log out and try again.
user.logOut()
return connectToRealm(firebaseUser)
}
// it's the right user.
return Promise.value(user)
}
return firstly {
// get JWT token with firebase function
Promise {
Functions.functions().httpsCallable("myAuthFunction").call(completion: $0.resolve)
}
}
.compactMap { result in
// extract JWT token from response
return (result?.data as? [String: Any])?["token"] as? String
}
.then { token in
// connect to Realm
Promise {
SyncUser.logIn(with: SyncCredentials.jwt(token), server:MY_AUTH_URL, onCompletion: $0.resolve)
}
}
}
This method is called after logging with Firebase. The auth URL is just https://\(MY_INSTANCE_ADDRESS).
I suspect it's a permission problem, in which case: how can I easily let this file be readable by everyone, but not writable? I'm not planning on creating a lot of theses files, I just want to do this once.
EDIT: I reduced my code to the bare minimum.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let MY_INSTANCE_ADDRESS = [...]
let AUTH_URL = URL(string: "https://\(MY_INSTANCE_ADDRESS)")!
let COMMON_REALM_URL = URL(string: "realms://\(MY_INSTANCE_ADDRESS)/common")!
SyncUser.logIn(with: SyncCredentials.anonymous(), server: AUTH_URL) { (user, error) in
guard error == nil && user != nil else {
print(error)
return
}
let config = user!.configuration(realmURL: COMMON_REALM_URL, fullSynchronization: true)
Realm.asyncOpen(configuration: config) { (realm, error) in
guard error == nil && realm != nil else {
print(error)
return
}
print("Successfully loaded Realm file with \(realm!.objects(MyObjectType.self).count) objects.")
}
}
}
Nothing else is running, I don't load my app window. Still getting the same error.
EDIT 2: changed code according to #Jay's comment.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let MY_INSTANCE_ADDRESS = [...]
let AUTH_URL = URL(string: "https://\(MY_INSTANCE_ADDRESS)")!
let COMMON_REALM_URL = URL(string: "realms://\(MY_INSTANCE_ADDRESS)/common")!
SyncUser.logIn(with: SyncCredentials.anonymous(), server: AUTH_URL) { (user, error) in
guard error == nil && user != nil else {
print(error)
return
}
print("logged in")
let config = SyncUser.current?.configuration(realmURL: COMMON_REALM_URL, fullSynchronization: true)
let realm = try! Realm(configuration: config!)
// $$$
let objectsResult = realm.objects(MyObjectType.self)
self.notificationToken = objectsResult.observe { changes in
print("changes: \(changes)")
if case .initial(let results) = changes {
print("\(results.count) results: \(results)")
}
}
// $$$
}
}
Result:
logged in
changes: initial(Results<MyObjectType> <0x155d2d4e0> (
))
0 results: Results<MyObjectType> <0x155d2d4e0> (
)
No error, but I don't get the content that are on the cloud.
EDIT 3 Okay that's interesting. By changing the code between the $$$ to:
try! realm.write {
realm.create(MyObjectType.self, value: MyObjectType(id: 2021, name_fr: "test_fr", name_eng: "test_eng"))
}
let objectsResult = realm.objects(MyObjectType.self)
print(objectsResult)
I get the result:
logged in
Results<MyObjectType> <0x143fe0510> (
[0] MyObjectType {
id = 2020;
name_fr = test_fr;
name_eng = test_eng;
}
)
So the object is being written. When I run again the same code (without the login part) and changing the object id, I get:
Results<MyObjectType> <0x111d507c0> (
[0] MyObjectType {
id = 2020;
name_fr = test_fr;
name_eng = test_eng;
},
[1] MyObjectType {
id = 2021;
name_fr = test_fr;
name_eng = test_eng;
}
)
So the objects are stored somewhere. However, I don't see these two objects in Realm Studio. In Realm Studio, I see the objects that I added using a JS script, which should be downloaded to the device but are not.
When I run again the code after logging out, the objects are removed. All the URLs are correct, and I checked that no other Realm file is created by running the code.

Related

Firestore query completion

I'm calling a Firestore query that does come back, but I need to ensure completion before moving on with the rest of the code. So I need a completion handler...but for the life of me I can't seem to code it.
As advised by comments I have tried to use the async / await calls:
function:
// get user info from db
func getUser() async {
self.db.collection("userSetting").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let userTrust = document.data()["userTrust"] as! String
let userGrade = document.data()["userGrade"] as! String
let userDisclaimer = document.data()["userDisclaimer"] as! String
var row = [String]()
row.append(userTrust)
row.append(userGrade)
row.append(userDisclaimer)
self.userArray.append(row)
// set google firebase analytics user info
self.userTrustInfo = userTrust
self.userGradeInfo = userGrade
}
}
}
}
Called by:
internal func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
db = Firestore.firestore()
Database.database().isPersistenceEnabled = true
Task {
do {
let userInfo = await getUser()
}
} return true }
I used a Task as didFinishLauncingWithOptions is synchronous and not asynchronous
However, the getUser() still isn't completed before didFinishLauncingWithOptions moves on.
I need the data from getUser as the very next step uses the data in the array, and without it I get an 'out of bounds exception' as the array is still empty.
Also tried using dispatch group within the func getUser(). Again with no joy.
Finally tried a completion handler:
func getUser(completion: #escaping (Bool) -> Void) {
self.db.collection("userSetting").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let userTrust = document.data()["userTrust"] as! String
let userGrade = document.data()["userGrade"] as! String
let userDisclaimer = document.data()["userDisclaimer"] as! String
var row = [String]()
row.append(userTrust)
row.append(userGrade)
row.append(userDisclaimer)
self.userArray.append(row)
// set google firebase analytics user info
self.userTrustInfo = userTrust
self.userGradeInfo = userGrade
completion(true)
}
}
}
}
Nothing works. The getUser call isn't completed before the code moves on. Can someone please help. I've searched multiple times, looked at all linked answers but I can not make this work.I'm clearly missing something easy, please help

Firebase Google SignIn Ios updated Documentation not working

I've been trying to integrate google sign into my ios project but as they have updated their API I Can't find the solution as everyone is using GIDSignIn.sharedInstance()?.delegate and it doesn't exist anymore.
Documentation
so what I understood from the documentation is:
after setting up GoogleSignIn dependency->
added URL Schemes.
Implement the application:openURL:options: method of your app delegate.
#available(iOS 9.0, *)
func application(_ application: UIApplication, open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any])
-> Bool {
return GIDSignIn.sharedInstance.handle(url)
}
Added a view in Storyboard and assigned class GIDSignInButton.
4.Pass the presenting view controller and client ID for your app to the Google Sign In sign-in method and create a Firebase auth
credential from the resulting Google auth token: (documentation)
(not quite sure where to add this, Right now I am adding it to the ViewDidLoad method of my loginViewController , without changing anything)
guard let clientID = FirebaseApp.app()?.options.clientID else { return }
// Create Google Sign In configuration object.
let config = GIDConfiguration(clientID: clientID)
// Start the sign in flow!
GIDSignIn.sharedInstance.signIn(with: config, presenting: self) { [unowned self] user, error in
if let error = error {
// ...
return
}
guard
let authentication = user?.authentication,
let idToken = authentication.idToken
else {
return
}
let credential = GoogleAuthProvider.credential(withIDToken: idToken,
accessToken: authentication.accessToken)
// ..AuthCode.
}
5. Then finally adding authentication below credential :
Auth.auth().signIn(with: credential) { authResult, error in
if let error = error {
let authError = error as NSError
if isMFAEnabled, authError.code == AuthErrorCode.secondFactorRequired.rawValue {
// The user is a multi-factor user. Second factor challenge is required.
let resolver = authError
.userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver
var displayNameString = ""
for tmpFactorInfo in resolver.hints {
displayNameString += tmpFactorInfo.displayName ?? ""
displayNameString += " "
}
self.showTextInputPrompt(
withMessage: "Select factor to sign in\n\(displayNameString)",
completionBlock: { userPressedOK, displayName in
var selectedHint: PhoneMultiFactorInfo?
for tmpFactorInfo in resolver.hints {
if displayName == tmpFactorInfo.displayName {
selectedHint = tmpFactorInfo as? PhoneMultiFactorInfo
}
}
PhoneAuthProvider.provider()
.verifyPhoneNumber(with: selectedHint!, uiDelegate: nil,
multiFactorSession: resolver
.session) { verificationID, error in
if error != nil {
print(
"Multi factor start sign in failed. Error: \(error.debugDescription)"
)
} else {
self.showTextInputPrompt(
withMessage: "Verification code for \(selectedHint?.displayName ?? "")",
completionBlock: { userPressedOK, verificationCode in
let credential: PhoneAuthCredential? = PhoneAuthProvider.provider()
.credential(withVerificationID: verificationID!,
verificationCode: verificationCode!)
let assertion: MultiFactorAssertion? = PhoneMultiFactorGenerator
.assertion(with: credential!)
resolver.resolveSignIn(with: assertion!) { authResult, error in
if error != nil {
print(
"Multi factor finanlize sign in failed. Error: \(error.debugDescription)"
)
} else {
self.navigationController?.popViewController(animated: true)
}
}
}
)
}
}
}
)
} else {
self.showMessagePrompt(error.localizedDescription)
return
}
// ...
return
}
// User is signed in
// ...
}
The auth code is taken from the documentation, it's not updated and surely I won't need this many details just UId.
I don't know what the problem is but it doesn't seem to work. On running, just Google sign-in button appears and on clicking it nothing happens
It appears that there is some problem with GIDSignInButton. Made A custom button and it is working fine

Enabling cache for AWS AppSync Client iOS Swift

I am using AWS AppSync for creating my iOS application. I want to leverage the offline mutation as well as query caching provided by AppSync. But when I am turning my internet off, I am not getting any response. Rather its showing an error as "The Internet connection appears to be offline.". This seems to be rather an Alamofire exception than an AppSync exception. This is because the query is not getting cached inside my device. Following is my code snippet to initialize the client.
do {
let appSyncClientConfig = try AWSAppSyncClientConfiguration.init(url: AWSConstants.APP_SYNC_ENDPOINT, serviceRegion: AWSConstants.AWS_REGION, userPoolsAuthProvider: MyCognitoUserPoolsAuthProvider())
AppSyncHelper.shared.appSyncClient = try AWSAppSyncClient(appSyncConfig: appSyncClientConfig)
AppSyncHelper.shared.appSyncClient?.apolloClient?.cacheKeyForObject = { $0["id"] }
} catch {
print("Error in initializing the AppSync Client")
print("Error: \(error)")
UserDefaults.standard.set(nil, forKey: DeviceConstants.ID_TOKEN)
}
I am caching the token in the UserDefaults at the time of fetching the session, and then whenever the AppSyncClient is called, it fetches the latest token by calling the getLatestAuthToken() method of my MyCognitoUserPoolsAuthProvider: AWSCognitoUserPoolsAuthProvider. This is returning the token stored in the UserDefaults -
// background thread - asynchronous
func getLatestAuthToken() -> String {
print("Inside getLatestAuthToken")
var token: String? = nil
if let tokenString = UserDefaults.standard.string(forKey: DeviceConstants.ID_TOKEN) {
token = tokenString
return token!
}
return token!
}
My query pattern is the following
public func getUserProfile(userId: String, success: #escaping (ProfileModel) -> Void, failure: #escaping (NSError) -> Void) {
let getQuery = GetUserProfileQuery(id: userId)
print("getQuery.id: \(getQuery.id)")
if appSyncClient != nil {
print("AppSyncClient is not nil")
appSyncClient?.fetch(query: getQuery, cachePolicy: CachePolicy.returnCacheDataElseFetch, queue: DispatchQueue.global(qos: .background), resultHandler: { (result, error) in
if error != nil {
failure(error! as NSError)
} else {
var profileModel = ProfileModel()
print("result: \(result)")
if let data = result?.data {
print("data: \(data)")
if let userProfile = data.snapshot["getUserProfile"] as? [String: Any?] {
profileModel = ProfileModel(id: UserDefaults.standard.string(forKey: DeviceConstants.USER_ID), username: userProfile["username"] as? String, mobileNumber: userProfile["mobileNumber"] as? String, name: userProfile["name"] as? String, gender: (userProfile["gender"] as? Gender).map { $0.rawValue }, dob: userProfile["dob"] as? String, profilePicUrl: userProfile["profilePicUrl"] as? String)
} else {
print("data snapshot is nil")
}
}
success(profileModel)
}
})
} else {
APPUtilites.displayErrorSnackbar(message: "Error in the user session. Please login again")
}
}
I have used all the 4 CachePolicy objects provided by AppSync, i.e,
CachePolicy.returnCacheDataElseFetch
CachePolicy.fetchIgnoringCacheData
CachePolicy.returnCacheDataDontFetch
CachePolicy.returnCacheDataAndFetch.
Can someone help me in implementing the cache properly for my iOS app so that I can do queries without the internet also?
Okay so I found the answer myself. The databaseUrl is an optional argument. It does not come in the suggestions when we are initializing the AWSAppSyncClientConfiguration object.
So the new way in which I initialized the client is the following
let databaseURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(AWSConstants.DATABASE_NAME, isDirectory: false)
do {
let appSyncClientConfig = try AWSAppSyncClientConfiguration.init(url: AWSConstants.APP_SYNC_ENDPOINT,
serviceRegion: AWSConstants.AWS_REGION,
userPoolsAuthProvider: MyCognitoUserPoolsAuthProvider(),
urlSessionConfiguration: URLSessionConfiguration.default,
databaseURL: databaseURL)
AppSyncHelper.shared.appSyncClient = try AWSAppSyncClient(appSyncConfig: appSyncClientConfig)
AppSyncHelper.shared.appSyncClient?.apolloClient?.cacheKeyForObject = { $0["id"] }
} catch {
print("Error in initializing the AppSync Client")
print("Error: \(error)")
}
Hope it helps.

iOS didFinishLaunchingWithOptions: no URL although the app is opened by tapping a link

I am opening my app with a link handled by Branch:
https://my.link?param=test
In didFinishLaunchingWithOptions:
Branch.getInstance()?.initSession(launchOptions: launchOptions, andRegisterDeepLinkHandler: { params, error in
if error == nil {
if let param = params?["param"] as? String {
print(param)
}
}
}
This correctly prints test in the console.
However, I am also trying to fetch the url before, but trying to fetch launchOptions?[.url], I get nil as a result.
So how can I get the url? Thanks for your help.
You need to use UIApplicationLaunchOptionsKey
Try this:
if let url = launchOptions?[UIApplicationLaunchOptionsKey.url] as? URL {
// Your code
}
else if let activityDictionary = launchOptions?[UIApplicationLaunchOptionsKey.userActivityDictionary] as? [AnyHashable: Any] { //Universal link
for key in activityDictionary.keys {
if let userActivity = activityDictionary[key] as? NSUserActivity {
if let url = userActivity.webpageURL {
// your code
}
}
}
}

PFUser logInWithAuthTypeInBackground (Error: Twitter auth configuration missing)

I'm trying to create PFUser with twitter login.
I'm getting below error while I'm trying to call this method.
https://github.com/parse-community/Parse-SDK-iOS-OSX
version: swift: 2.3, xcode: 8.2.1, parse:1.15.0, twitterKit: 2.8.1, TwitterCore: 2.8.0
Twitter.sharedInstance().logInWithCompletion { (session, error) -> Void in
if let session = session {
PFUser.registerAuthenticationDelegate(self, forAuthType: "twitter")
let params: [String: String] = [
"id": (session.userID)!,
"screen_name": (session.userName)!,
"auth_token": (session.authToken)!,
"auth_token_secret": (session.authTokenSecret)!,
"consumer_key": Twitter.sharedInstance().authConfig.consumerKey,
"consumer_secret": Twitter.sharedInstance().authConfig.consumerSecret
]
PFUser.logInWithAuthTypeInBackground("twitter", authData: params).continueWithBlock { task -> AnyObject? in
print(task.error) //[Error]: Twitter auth configuration missing (Code: 1, Version: 1.15.0)
return task.completed
}
}
Twitter auth configuration missing (Code: 1, Version: 1.15.0)
Here's an example view controller of what you're trying to achieve. I ran into problems using "Twitter.sharedInstance.loginWithCompletion({})" block.
import Fabric
import TwitterKit
import Parse
import Firebase
class LoginViewController: UIViewController {
var window: UIWindow?
var user: User?
// MARK: Login logic
override func viewDidLoad() {
let logInButton = TWTRLogInButton(logInCompletion: { session, error in
if let unwrappedSession = session {
let parseuser = PFUser()
let usersName = unwrappedSession.userName
let usersID = unwrappedSession.userID
let profileImage = UIImage(named: "userProfileImage")
parseuser.username = usersName
parseuser.password = usersID
parseuser.email = "email#placeholder.com"
// set currentUser profile image as 'parseuser["profileImage"]'
let image = UIImage(named: "userProfileImage")
let imageData = UIImagePNGRepresentation(image!) as Data!
let file = PFFile(name: "profileImage.png", data: imageData! as Data)
parseuser["profileImage"] = file
parseuser.signUpInBackground(block: { (result, error) -> Void in
if error == nil && result == true {
//var userObjectID =
self.user = User(userObjectID: parseuser.objectId!, userID: usersID, userName: usersName, email: parseuser.email!, profileImage: profileImage!)
self.saveUser()
// user successfully added
print ("Object saved")
self.goToApp()
} else {
//TODO: Check error message and display reason to user
}
})
}
if (session != nil) {
let authToken = session?.authToken
let authTokenSecret = session?.authTokenSecret
let credential = FIRTwitterAuthProvider.credential(withToken: authToken!, secret: authTokenSecret!)
FIRAuth.auth()?.signIn(with: credential, completion: { (user, error) in
if (error != nil) {
print(error)
return
}
print("Twitter login successful..")
})
} else {
if (error != nil) {
print(error)
}
}
})
logInButton.center = self.view.center
self.view.addSubview(logInButton)
THEN, in ur AppDelegate
import Firebase
import Fabric
import TwitterCore
import TwitterKit
import Parse
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
Parse.enableLocalDatastore()
let parseConfiguration = ParseClientConfiguration(block: { (ParseMutableClientConfiguration) -> Void in
ParseMutableClientConfiguration.applicationId = "com.your.identifier"
ParseMutableClientConfiguration.clientKey = "your-client-key"
ParseMutableClientConfiguration.server = "https://your-url.herokuapp.com/parse"
})
Parse.initialize(with: parseConfiguration)
Twitter.sharedInstance().start(withConsumerKey: "your-consumer-key",
consumerSecret: "your-consumer-secret")
Fabric.with([Twitter.self])
FIRApp.configure()
This is not optimized in terms of imports/etc. but this will work. You will need to set up a firebase account, and put the Google-Service-Info.plist file within ur project structure, as well as get a fabric key and put it in ur info.plist file but you can find examples of how-to online. Just a solution.

Resources