Hi all im trying to create an ios app that sync data using Realm Object server
but there is something im missing
i installed corretly Realm object server on my server (remote)
i can access the dashboard correctly
i can login with Syncuser
but i have problem syncing data.. when i open the app i login the user but after that ( im unable to sync anyting )
i searched online for a full example but without success
any one can suggest a simple and easy example for use realm sync with swift?
Thanks
This is my actual implementation for a chat app using RealmSwift. I used a model object called Message defined below:
class Message: Object {
dynamic var userId = ""
dynamic var name = ""
dynamic var text = ""
dynamic var dateCreated = NSDate()
}
Next I used created a controller for sending and sync messages:
// Instance variables
var messages: Results<Message>!
var token: NotificationToken!
var configuration: Realm.Configuration!
override func viewDidLoad() {
super.viewDidLoad()
// Your viewDidLoad implementation
self.setupRealm()
}
Next the setup methods for Realm. As mentioned in Real Documentation, if a Realm has read-only permissions, then you must use the asyncOpen API as described in Asynchronously Opening Realms. Opening a read-only Realm without asyncOpen will cause an error.
private func setupRealm() {
let serverURL = URL(string: "http://your.server.ip:9080")!
let credentials = SyncCredentials.usernamePassword(username: "example#mail.com", password: "password")
SyncUser.logIn(with: credentials, server: serverURL) { user, error in
if let user = user {
let syncURL = URL(string: "realm://your.server.ip:9080/~/Message")!
let syncConfig = SyncConfiguration(user: user, realmURL: syncURL)
self.configuration = Realm.Configuration(syncConfiguration: syncConfig)
self.setupDataSource()
} else if let error = error {
// handle error
debugPrint("error: \(error.localizedDescription)")
}
}
}
private func setupDataSource() {
Realm.asyncOpen(configuration: self.configuration) { realm, error in
if let realm = realm {
// Realm successfully opened, with all remote data available
self.messages = realm.objects(Message.self).sorted(byKeyPath: "dateCreated", ascending: true)
self.token = self.messages.addNotificationBlock({ (changes: RealmCollectionChange) in
debugPrint("Message update event")
self.collectionView.reloadData()
})
} else if let error = error {
// Handle error that occurred while opening or downloading the contents of the Realm
debugPrint("error: \(error.localizedDescription)")
}
}
}
deinit {
self.token?.stop()
}
Finally the sendMessage() method that I use to send the message:
func sendMessage(id: String, name: String, text: String) {
let message = Message()
message.userId = id
message.name = name
message.text = text
message.dateCreated = NSDate()
Realm.asyncOpen(configuration: self.configuration) { realm, error in
if let realm = realm {
// Realm successfully opened, with all remote data available
try! realm.write() {
realm.add(message)
}
} else if let error = error {
// Handle error that occurred while opening or downloading the contents of the Realm
debugPrint("error: \(error.localizedDescription)")
}
}
}
Hope it helps!
Bye!
Related
Error Domain=MCOErrorDomain Code=5 "Unable to authenticate with the current session's credentials." UserInfo={NSLocalizedDescription=Unable to authenticate with the current session's credentials.}
I put this code in my project.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
prepareImapSession()
}
var imapsession:MCOIMAPSession = MCOIMAPSession()
var error : Error? = nil
func prepareImapSession()
{
// CONFIGURE THAT DEPENDING OF YOUR NEEDS
imapsession.hostname = "imap.gmail.com" // String
imapsession.username = my email // String
imapsession.password = password // String
imapsession.port = 993 // UInt32 number
imapsession.authType = MCOAuthType.saslLogin
imapsession.connectionType = MCOConnectionType.TLS
DispatchQueue.main.asyncAfter(deadline: .now() + 8) { [self] in
self.useImapWithUIDS()
}
imapsession.connectOperation()
}
func useImapWithUIDS() {
// There is more than one option here, explore depending of your needs
// let kind = MCOIMAPMessagesRequestKind()
// let headers = kind.union(MCOIMAPMessagesRequestKind.headers)
// let request = headers.union(MCOIMAPMessagesRequestKind.flags)
let requestKind: MCOIMAPMessagesRequestKind = [.headers, .flags]
let folder : String = "INBOX"
// HERE ALSO EXPLORE DEPENDING OF YOUR NEEDS, RANGE IT IS THE RANGE OF THE UIDS THAT YOU WANT TO FETCH, I SUGGEST TO YOU TO CHANGE THE // NUMBER ONE IF YOU HAVE A LOWER BOUND TO FETCH EMAIL
let uids : MCOIndexSet = MCOIndexSet(range: MCORangeMake(1, UINT64_MAX))
let fetchOperation = imapsession.fetchMessagesOperation(withFolder: folder, requestKind: requestKind, uids: uids)
fetchOperation?.start
{ [self] (err, msg, vanished) -> Void in
if (err != nil)
{
self.error = err
NSLog((err?.localizedDescription)!)
}
else
{
guard let msgs = msg as? [MCOIMAPMessage]
else
{
print("ERROR GETTING THE MAILS")
return
}
for i in 0..<msgs.count
{
// THE SUBJECT
let subject = msgs[i].header.subject
// THE uid for this email. The uid is unique for one email
let uid = msgs[i].uid
self.useImapFetchContent(uidToFetch: uid)
// The sequenceNumber like the nomber say it is the sequence for the emails in the INBOX from the first one // (sequenceNumber = 1) to the last one , it not represent always the same email. Because if you delete one email then //next one will get the sequence number of that email that was deleted
let sequenceNumber = msgs[i].sequenceNumber
}
}
}
}
// MARK: - EXTRACT THE CONTENT OF ONE EMAIL, IN THIS FUNCTION YOU NEED THE uid, THE UNIQUE NUMBER FOR ONE EMAIL
func useImapFetchContent(uidToFetch uid: UInt32) {
let operation: MCOIMAPFetchContentOperation = imapsession.fetchMessageOperation(withFolder: "INBOX", uid: uid)
operation.start { (Error, data) in
if (Error != nil)
{
NSLog("ERROR")
return
}
let messageParser: MCOMessageParser = MCOMessageParser(data: data)
// IF YOU HAVE ATTACHMENTS USE THIS
let attachments = messageParser.attachments() as? [MCOAttachment]
// THEN YOU NEED THIS PROPERTIE, IN THIS EXAMPLE I TAKE THI FIRST, USE WHAT EVER YOU WANT
let attachData = attachments?.first?.data
// FOR THE MESSAGEPARSER YOU CAN EPLORE MORE THAN ONE OPTION TO OBTAIN THE TEXT
let msgPlainBody = messageParser.plainTextBodyRendering()
}
}
}
I using the mailcore2 framework. I got error description Unable to authenticate with the current session's credentials.
It can be related with 2nd factor authentication on your account. It was described on Apple forum.
I have development and production firebase projects, and I am managing using below method,
In my AppDelegate didFinishLaunchingWithOptions,
// Configure with manual options.
let secondaryOptions = FirebaseOptions(googleAppID: "1:82121212:ios:212121212121212", gcmSenderID: "872909116998")
secondaryOptions.bundleID = "com.testApp.test"
secondaryOptions.apiKey = "AIzasskjkkjhkdhksadhsadaskdhks"
secondaryOptions.clientID = "82121212-asdsdasdasdasdasdadsdsd.apps.googleusercontent.com"
secondaryOptions.databaseURL = "https://test-myApp.firebaseio.com"
secondaryOptions.storageBucket = "test-myApp.appspot.com"
// Configure an alternative FIRApp.
FirebaseApp.configure(name: "secondary", options: secondaryOptions)
guard let secondary = FirebaseApp.app(name: "secondary")
else { assert(false, "Could not retrieve secondary app") }
let secondaryDb = Database.database(app: secondary)
and in my another view controller while fetching messages from the firebase database my application is crashing with
"*** Terminating app due to uncaught exception 'FIRAppNotConfigured', reason: 'Failed to get default Firebase Database instance. Must call [FIRApp configure] (FirebaseApp.configure() in Swift) before using Firebase Database.'"
my ChatViewController
class ChatViewController : UIViewController {
var ref : DatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
getChatMessages()
}
#objc func getChatMessages() {
firebase = Database.database().reference(fromURL: url)
Auth.auth().signIn(withCustomToken: firBaseAuthToken, completion: { (user, error) in
if error != nil{
}
else
{
let userAuthData = user?.additionalUserInfo
self.uid = (user?.user.uid)!.replacingOccurrences(of: ".", with: "")
self.successFireBaseAuth(urlString: klUrl)
}
})
}
func successFireBaseAuth(urlString : String)
{
ref = Database.database().reference(withPath: urlString)
ref.observe(.value, with: { snapshot in
})
}
By the looks of it you are accessing the wrong instance of app.
guard let secondary = FirebaseApp.app(name: "secondary")
else { assert(false, "Could not retrieve secondary app") }
let secondaryDb = Database.database(app: secondary)
Your own code in AppDelegate, I'm assuming thats how you should get the right database instance.
but you are using
Database.database()
When trying to get messages.
If you want to use the shared instance use
FirebaseApp.configure(options: secondaryOptions)
when configuring
I am creating an iPhone app in swift 4.0. I'm connecting over to Firestore, which currently works. The problem that I'm having is that a random character is document is created each time a user is authenticated. I want to change this so the email address is the registered document in the DB so the next time I login with the same credentials its uses the same document.
let databaseRef = Database.database().reference(fromURL: “*****************”)
let storage = Storage.storage().reference()
let db = Firestore.firestore()
var ref: DocumentReference? = nil
var handle: AuthStateDidChangeListenerHandle?
var checkBtnTitle:Bool = true
override func viewDidLoad() {
super.viewDidLoad()
if Auth.auth().currentUser != nil {
let currentUser = Auth.auth().currentUser
btnLogOut.title = "LogOut"
btnLogin.title = "Login"
var ref: DocumentReference? = nil
ref = db.collection("Solicitor").addDocument(data: [
"userID": currentUser?.email! as Any,
"Name": currentUser?.displayName as Any
]) { err in
if let err = err {
print("Error adding document: \(err)")
} else {
print("Document added with ID: \(ref!.documentID)")
}
}
} else {
btnLogOut.title = "Login"
btnLogin.title = ""
}
// google signin
GIDSignIn.sharedInstance()?.uiDelegate = self
When you call addDocument() Firestore create a new document with an auto-generated ID. If you want to control the ID of the document, you should use the document() and setData() methods. For example:
ref = db.collection("Solicitor").document(currentUser?.email!).setDate(data: [
"userID": currentUser?.email! as Any,
"Name": currentUser?.displayName as Any
])
For more on this, see the Firebase documentation on adding documents.
thank you for your assistance on this one. I finally was able to get it to save... I didn't need the 'ref =' part in the DB.
let currentUser = Auth.auth().currentUser
let businessEmail = Auth.auth().currentUser?.email!
db.collection("Solicitor").document(businessEmail!).setData([
On a side note, im still getting the auto-generated ID as well as the document ID (email address) of the user. Does anyone have any suggestions on how to stop this behaviour?
I'm newish to Swift and new to Firestore and am running into an issue that I can't solve. I have the code below which is supposed to check for a document at the UserReference location in Firestore. If it doesn't exist, it should create a new document that contains the pertinent user information that I have previously grabbed from facebook.
This what UserReference looks like self.UserReference = self.db.collection("users").document(UserID as! String) where the UserID is from the Facebook graph request. Next, it'll run a transaction to pull the document, update the user doc with the latest facebook info (assuming this is not a new user), and then update the doc to Firebase Firestore.
let db = Firestore.firestore()
var fbUserUpdate = User(
firstName: String(),
lastName: String(),
email: String(),
<extra stuff>)
func updateFacebookInfoToFirestore(completion: #escaping (_: User?, _: Error?) -> Void) {
// Check if user exists, if not create new user
UserReference!.getDocument { (document, error) in
if document != nil {
// continue
} else {
self.UserReference.setData(self.fbUserUpdate.dictionary)
}
}
// Now pull data and update based on most recent facebook info.
db.runTransaction({ (transaction, errorPointer) -> Any? in
// Read data from Firestore inside the transaction, so we don't accidentally
// update using staled client data. Error if we're unable to read here.
let UserSnapshot: DocumentSnapshot
do {
try UserSnapshot = transaction.getDocument(self.UserReference!)
} catch let error as NSError {
errorPointer?.pointee = error
return nil
}
// This will overwrite the fbUserUpdate Struct with the updated information.
guard var dbUser = User(dictionary: UserSnapshot.data()) else {
let error = NSError(domain: "Domain", code: 0, userInfo: [
NSLocalizedDescriptionKey: "Unable to write to userdata at Firestore path: \(self.UserReference!.path)"
])
errorPointer?.pointee = error
return nil
}
// Update from Facebook
dbUser.firstName = self.fbUserUpdate.firstName
dbUser.lastName = self.fbUserUpdate.lastName
dbUser.email = self.fbUserUpdate.email
// Load new data to firestore
transaction.setData(dbUser.dictionary, forDocument: self.UserReference!, options: SetOptions.merge())
return nil
}) { (object, error) in
if let error = error {
print(error)
} else {
// nothing yet
}
}
}
However, when I run this in my app, when I get to the UserReference!.getDocument closure, it skips over the closure and then the transaction doesn't work as intended because the try UserSnapshot = transaction.getDocument(self.UserReference!) returns a null since no document exists.
I believe the issue is in the .getDocument closure, but I don't know where I'm going wrong. I've tried to emulate the FriendlyEats firestore example code as best I can but I'm stuck and am in need of an extra set of eyes.
Has anyone gone through this API and has figured it out?
This is my third time trying to get this to work by following this guide
I am using the swift version of this guide.
Google Apps Script Guide
And it always give me the same errors.
import UIKit
class ViewController: UIViewController {
private let kKeychainItemName = "Google Apps Script Execution API"
private let kClientID = "493692471278-3mf6bo212flgjopl06hrjfeepphe70h4.apps.googleusercontent.com"
private let kScriptId = "Mj0RNm2ZtohFurieBLPwnxYAb4Jnnku4P"
// If modifying these scopes, delete your previously saved credentials by
// resetting the iOS simulator or uninstall the app.
private let scopes = ["https://www.googleapis.com/auth/drive"]
private let service = GTLService() // error Use of unresolved identifier 'GTLService'
let output = UITextView()
// When the view loads, create necessary subviews
// and initialize the Google Apps Script Execution API service
override func viewDidLoad() {
super.viewDidLoad()
output.frame = view.bounds
output.editable = false
output.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 20, right: 0)
output.autoresizingMask = UIViewAutoresizing.FlexibleHeight |
UIViewAutoresizing.FlexibleWidth
// error*** Binary operator '|' cannot be applied to two 'UIViewAutoresizing' operands
view.addSubview(output);
// Error**Use of unresolved identifier 'GTMOAuth2ViewControllerTouch'
if let auth = GTMOAuth2ViewControllerTouch.authForGoogleFromKeychainForName(
kKeychainItemName,
clientID: kClientID,
clientSecret: nil) {
service.authorizer = auth
}
}
// When the view appears, ensure that the Google Apps Script Execution API service is authorized
// and perform API calls
override func viewDidAppear(animated: Bool) {
if let authorizer = service.authorizer,
canAuth = authorizer.canAuthorize where canAuth {
callAppsScript()
} else {
presentViewController(
createAuthController(),
animated: true,
completion: nil
)
}
}
// Calls an Apps Script function to list the folders in the user's
// root Drive folder.
func callAppsScript() {
output.text = "Getting folders..."
let baseUrl = "https://script.googleapis.com/v1/scripts/\(kScriptId):run"
let url = GTLUtilities.URLWithString(baseUrl, queryParameters: nil)
// error ** Use of unresolved identifier 'GTLUtilities'
// Create an execution request object.
var request = GTLObject()
// Error** Use of unresolved identifier 'GTLObject'
request.setJSONValue("getFoldersUnderRoot", forKey: "function")
// Make the API request.
service.fetchObjectByInsertingObject(request,
forURL: url,
delegate: self,
didFinishSelector: "displayResultWithTicket:finishedWithObject:error:")
}
// Displays the retrieved folders returned by the Apps Script function.
func displayResultWithTicket(ticket: GTLServiceTicket,
finishedWithObject object : GTLObject,
error : NSError?) {
if let error = error {
// The API encountered a problem before the script
// started executing.
showAlert("The API returned the error: ",
message: error.localizedDescription)
return
}
if let apiError = object.JSON["error"] as? [String: AnyObject] {
// The API executed, but the script returned an error.
// Extract the first (and only) set of error details and cast as
// a Dictionary. The values of this Dictionary are the script's
// 'errorMessage' and 'errorType', and an array of stack trace
// elements (which also need to be cast as Dictionaries).
let details = apiError["details"] as! [[String: AnyObject]]
var errMessage = String(
format:"Script error message: %#\n",
details[0]["errorMessage"] as! String)
if let stacktrace =
details[0]["scriptStackTraceElements"] as? [[String: AnyObject]] {
// There may not be a stacktrace if the script didn't start
// executing.
for trace in stacktrace {
let f = trace["function"] as? String ?? "Unknown"
let num = trace["lineNumber"] as? Int ?? -1
errMessage += "\t\(f): \(num)\n"
}
}
// Set the output as the compiled error message.
output.text = errMessage
} else {
// The result provided by the API needs to be cast into the
// correct type, based upon what types the Apps Script function
// returns. Here, the function returns an Apps Script Object with
// String keys and values, so must be cast into a Dictionary
// (folderSet).
let response = object.JSON["response"] as! [String: AnyObject]
let folderSet = response["result"] as! [String: AnyObject]
if folderSet.count == 0 {
output.text = "No folders returned!\n"
} else {
var folderString = "Folders under your root folder:\n"
for (id, folder) in folderSet {
folderString += "\t\(folder) (\(id))\n"
}
output.text = folderString
}
}
}
// Creates the auth controller for authorizing access to Google Apps Script Execution API
private func createAuthController() -> GTMOAuth2ViewControllerTouch {
// Error** Use of undeclared type 'GTLServiceTicket' let scopeString = " ".join(scopes) // Error* 'join' is unavailable: call the 'joinWithSeparator()' method on the sequence of elements
return GTMOAuth2ViewControllerTouch(
scope: scopeString,
clientID: kClientID,
clientSecret: nil,
keychainItemName: kKeychainItemName,
delegate: self,
finishedSelector: "viewController:finishedWithAuth:error:"
)
}
// Handle completion of the authorization process, and update the Google Apps Script Execution API
// with the new credentials.
func viewController(vc : UIViewController,
finishedWithAuth authResult : GTMOAuth2Authentication, error : NSError?)
// Error** Use of undeclared type 'GTMOAuth2Authentication' {
if let error = error {
service.authorizer = nil
showAlert("Authentication Error", message: error.localizedDescription)
return
}
service.authorizer = authResult
dismissViewControllerAnimated(true, completion: nil)
}
// Helper for showing an alert
func showAlert(title : String, message: String) {
let alert = UIAlertView(
title: title,
message: message,
delegate: nil,
cancelButtonTitle: "OK"
)
alert.show()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I find it hard to believe that google would make a guide and have it not work for the current version of xcode. It even says at the bottom of their guide that it was last updated February 2016.
Wanted to see if anyone has had any luck with following this guide in the past.
is there another swift guide for this google API?
Thank you in advance.
1)Looks like you aren't getting the header files. You need a Bridging-Header.h. That step is omitted from the Quickstart; see the Google Drive Quickstart for the step you need to take.
2)You can just comment out the following line, it doesn't seem to be necessary.
output.autoresizingMask = UIViewAutoresizing.FlexibleHeight |
UIViewAutoresizing.FlexibleWidth
3)Those changes will fix the compile problems. I found that after fixing the compile problems, it would not link and run on a phone.
The Quickstart guide has you link the GTL.framework with your app using Xcode's 'Link Binary with Libraries'. I instead installed it using Cocoapods (https://cocoapods.org/pods/Google-API-Client) and was then able to run my app.