can't open Realm using mongodb sync - swift

I can't open a Realm. There is something wrong with my set up. This is the code in the app:
var configuration = user.configuration(partitionValue: "user=\(user.id)")
configuration.objectTypes = [User.self]
Realm.asyncOpen(configuration: configuration) { [weak self](userRealm, error) in
self!.setLoading(false);
guard error == nil else {
fatalError("Failed to open realm: \(error!)"). ///// here was my error before the edit (I changed the Permission Settings in Sync)
}
Error code: "Fatal error: Failed to open realm: Error Domain=io.realm.unknown Code=89 "Operation canceled" UserInfo={Category=realm.basic_system, NSLocalizedDescription=Operation canceled, Error Code=89"
Screenshot of Mongodb Sync configuration
EDIT:
Replaced the Permission settings in Sync with the ones mentioned in the Task Tracker app and that got it connected:
EDIT: the configuration settings were changed to the below as per the suggestions from Jay.
var configuration = user.configuration(partitionValue: "\(currentUser.id!)")
Here is my Scheme definition for the User Collection:
{
"properties": {
"_id": {
"bsonType": "string"
},
"_partition": {
"bsonType": "string"
},
"name": {
"bsonType": "string"
}
},
"required": [
"_id",
"_partition",
"name"
],
"title": "User"
}
This is my User class in Xcode:
class User: Object {
#objc dynamic var _id: String = ""
#objc dynamic var _partition: String = ""
#objc dynamic var name: String = ""
override static func primaryKey() -> String? {
return "_id"
}
}
EDIT: I got this going to establish a connection by updating the Sync Permissions, so the app does not crash any more. However, now I am getting this message:
Connection to daemon was invalidated
Signup successful!
Log in as user: y
Login succeeded!
2020-10-25...Sync: Connection[1]: Session[1]: client_reset_config = false, Realm exists = false, async open = false, client reset = falseSync: Connection[1]: Session[1]: client_reset_config = false, Realm exists = false, async open = false, client reset = false
2020-10-25...Sync: Connection[1]: Connected to endpoint '13.54.209.90:443' (from '.....:52139')
2020-10-25...Sync: Connection[1]: Reading failed: End of input
2020-10-25...Sync: Connection[1]: Connection closed due to error```
I don't understand that the error "("Failed to open realm: (error!)")" in my above code is not triggered, but then in the log it says that the realm does not exist! So, what's going on here?
EDIT : this is the user table in mongodb, so I created some Users successfully.
EDIT : This is the log from mongodb
As we can see the User Id and the Request ID are not the same! I guess that the two IDs should be the same string in order to be authenticated, right??
I am following the Task Tracker app tutorial from the mongodb webpage for Swift to add the user login to my app. What am I missing here?

As a complete guess, your config string is not correct
var configuration = user.configuration(partitionValue: "user=\(user.id)")
As that the partition value resolves to
partitionValue: user=Optional("5f1b586f757611faec257d88")
Try this
guard let user = your_app.currentUser() else {
print("no user")
return
}
guard let userId = user.id else {
print("no user")
return
}
var configuration = user.configuration(partitionValue: "user=\(userId)")
More to the point though, the partition value you're attempting to use is this string
user=5f1b586f757611faec257d88
and I think what you really want is use the user id
5f1b586f757611faec257d88
That's where I would start. If you're trying to leverage Realm rules, then something like _partitionKey: "team_id=1234" would work but that goes beyond the scope of the original question (and adds another layer of complexity - get it working first, then explore the rules).

Related

SwiftUI: Check if Firebase RealtimeDatabase has a specific Child the register the value or return error

I am currently building an app with an account system.
Firebase is very new to me, that's why I watched a lot of tutorials, and now its working fine.
I want to implement that the user can choose a unique username at the registration. My problem is, I really don't know how to check if this name is already taken.
I found some code for that, but that's not working, I will show you the code for the RegistrationService file.
I hope someone can explain to me how to implement this username verification. It should return an error if the username is already taken and do continue the registration if its a valid username.
Thank you!
import Combine
import Firebase
import FirebaseDatabase
import Foundation
enum RegistrationKeys: String {
case firstName
case lastname
case info
case username
}
protocol RegisterService {
func register(with details: RegistrationDetails) -> AnyPublisher<Void, Error>
}
final class RegisterServiceImpl: RegisterService {
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 {
// Success on User creation
if let uid = res?.user.uid {
let values =
[
RegistrationKeys.firstName.rawValue: details.firstName,
RegistrationKeys.lastname.rawValue: details.lastName,
RegistrationKeys.info.rawValue: details.info,
] as [String: Any]
let db = Database.database(url: "theurl")
Database.database(url: "the url")
.reference()
.child("usernames")
.child("\([RegistrationKeys.info.rawValue: details.username] as [String : Any])")
// here should be the check and then continue if its valid
db
.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()
}
}
I can see two possibilities to solve your problem:
If the e-mail can serve as the username
Firebase authentication already sends back an error message in case the e-mail (the one used when creating the user) already exists. If the e-mail passed in the following function is not unique, an error will be thrown:
Auth.auth()
.createUser(
withEmail: details.email,
password: details.password
) { res, error in
if let err = error {
promise(.failure(err))
If an additional username besides the e-mail is required
If you need usernames in addition to the e-mails, you can store them under a node "usernames", like we see in your example. Personally, I would hash them instead of storing them plain.
The structure could simply be:
{
usernames: {
username_1: true,
username_2: true,
...
username_n: true
}
}
The example below checks to see if a new username exists and stores the result in the variable isUsernameTaken:
let db = Database.database(url: "the url").reference()
let newUsername = "seeIfItIsTaken"
db.child("usernames").child(newUsername).getData() { error, snapshot in
guard error == nil else {
print("Found error \(error)")
return
}
let isUsernameTaken = snapshot.exists()
}

amplify subscription using auth in swift

I want to set up a list of live list of "Move"s so I used this snippet from the amplify docs.
func createSubscription() {
subscription = Amplify.API.subscribe(request: .subscription(of: Move.self, type: .onCreate))
dataSink = subscription?.subscriptionDataPublisher.sink {
if case let .failure(apiError) = $0 {
print("Subscription has terminated with \(apiError)")
} else {
print("Subscription has been closed successfully")
}
}
receiveValue: { result in
switch result {
case .success(let createdTodo):
print("Successfully got todo from subscription: \(createdTodo)")
case .failure(let error):
print("Got failed result with \(error.errorDescription)")
}
}
}
Schema auth rules
type Move
#model
#auth( rules: [
{ allow: owner, ownerField: "owner", operations: [create, update, delete, read] },
])
{
But since I added auth to the "move" type I get this error. GraphQLResponseError<Move>: GraphQL service returned a successful response containing errors: [Amplify.GraphQLError(message: "Validation error of type MissingFieldArgument: Missing field argument owner # \'onCreateMove\'", locations: nil, path: nil, extensions: nil)]
and Recovery suggestion: The list of GraphQLError contains service-specific messages
So everything is working locally but I think I need to pass the authorization to the request but I can't find any way to do it. any Ideas how I might get this request to process properly?
got it working by writng my own request and passing the owner field directly
extension GraphQLRequest {
static func newMoves() -> GraphQLRequest<Move> {
let operationName = "getMove"
let document = """
subscription MySubscription {
onCreateMove(owner: "MyUser") {
accerationMagnitude
id
}
}
"""
return GraphQLRequest<Move>(document: document,
// variables: [],
responseType: Move.self,
decodePath: operationName)
}
}

SQLite.Swift giving Error 14 on Connection

I have a routine, where the user chooses to pick a file location, then I try to create the file at that location.
First, I ask the user to choose a file path as follows:
func ChooseCreateDBPath() -> String
{
var path : String = ""
let savePanel = NSSavePanel()
savePanel.canCreateDirectories = true
savePanel.showsTagField = true
savePanel.nameFieldStringValue = "imported.sqlite"
if (savePanel.runModal() == NSApplication.ModalResponse.OK) {
let result = savePanel.url
if (result != nil) {
path = result!.path
}
}
return path
}
Having obtained the string to the path where the DB will be created, the next routine runs.
do {
self.dbConn = try Connection(strFilePath)
self.update_ui(message: "Created database: \(strFilePath)\n")
} catch {
self.update_ui(message: "Failed to create database : \(error)\n")
}
.... this seems to run no problem. I even see the sqlite file appear in the chosen location.
Now, I try to create the table and fields....
do {
let leads = Table("leads")
let idlead = Expression<Int64>("idlead")
let email = Expression<String>("email")
try self.dbConn!.run(leads.create { t in
t.column(idlead, primaryKey: true)
t.column(email)
}
)
try self.dbConn!.run(leads.createIndex(email))
} catch {
self.update_ui(message: "Failed to create tables and indexes : \(error)\n")
}
This then gives me:
"Failed to create tables and indexes : unable to open database file (code: 14)"
What I don't understand is how it can create the file, yet not be able to work with it? Any pointers would be much appreciated.
Cheers
Jase
I found the issue. The macOS sandboxing doesn't like it when you have files outside the sandbox. Even though the user can choose the file, that doesn't give SQLite the permission to write temp journal files there, and so everything fails. My fix was simply turn to off the Sandbox.

aws appsync offline : getting error Variable id was not provided

I am using AWS AppSync for mobile development (iOS) for offline/Online Capabilities
I am trying to save data in offline mode. But I am getting error "Variable id was not provided/ Missing value"
When app comes to online it automatically syncing to DynamoDB but the issue is only in offline mode unable to fetch saved record
Here is the code using in the application
`
let userObjInput = userObjectInput(id: "id", firstName: "firstname", lastName: "lastName")
let CategoryInputs = CreateUserCategoryInput(categoryName: "categoryValue" , user: userObjInput)
let mutation = CategoryMutation(input: CategoryInputs)
appSyncClient?.perform(mutation: mutation, queue: .main, optimisticUpdate: { (transaction) in
do {
let selectionSets = try transaction?.read(query: query)
try transaction?.update(query: GetUserCategoriesOfUserQuery(id: "id")) { (data: inout GetUserCategoriesOfUserQuery.Data) in
data.getAllCategoriesForUser?.append(GetUserCategoriesOfUserQuery.Data.GetAllCategoriesForUser?.init(GetUserCategoriesOfUserQuery.Data.GetAllCategoriesForUser.init(id: (UUID().uuidString), categoryName: CategoryInputs.categoryName!, isDeleted: false, user: GetUserCategoriesOfUserQuery.Data.GetAllCategoriesForUser.User?.init(GetUserCategoriesOfUserQuery.Data.GetAllCategoriesForUser.User.init(id: userObjInput.id!, firstName: userObjInput.firstName!, lastName: userObjInput.lastName!)))))
} catch {
print(error.localizedDescription)
}
}, conflictResolutionBlock: nil, resultHandler: { (result, error) in
if error == nil {
fetchCategories()
} else {
print(error?.localizedDescription)
}
})`
For those who have problem with optimistic UI missing value. I've found one trick to temporary solve the issue by passing parameter using Custom Request Header from client app.
Before, your query would look like this allDiaries(author: String): [Diary]
Just change it to => allDiaries: [Diary]
So your request mapping would look like below:
{
"version" : "2017-02-28",
"operation" : "Scan",
"filter" : {
"expression" : "author = :author",
"expressionValues" : {
":author" : { "S" : "$context.request.headers.author" }
}
}
}
Reference: How to pass AWS AppSync custom request header in iOS client?
Hope it is useful! Goodluck :)

Firebase audience based on user properties

I need to test some features of my app with just a previously selected group of users.
I created an Audience where user_id exactly matches 123456. 123456 being my own ID.
In Remote Config I created a Condition that matches users in the Audience above.
Then I created a parameter in Remote Config called feature_available and for the condition, I set a return value of true. The Default value is false.
In my app I set up Firebase:
FIRApp.configure()
let remoteConfig = FIRRemoteConfig.remoteConfig()
if let remoteConfigSettings = FIRRemoteConfigSettings(developerModeEnabled: false) {
remoteConfig.configSettings = remoteConfigSettings
}
remoteConfig.setDefaultsFromPlistFileName("FirebaseRemoteConfigDefaults")
And set the user ID:
FIRAnalytics.setUserID("123456")
Then I fetch from Firebase:
var expirationDuration: Double
// If in developer mode cacheExpiration is set to 0 so each fetch will retrieve values from the server.
expirationDuration = remoteConfig.configSettings.isDeveloperModeEnabled ? 0 : 3600
remoteConfig.fetch(withExpirationDuration: TimeInterval(expirationDuration)) { (status, error) -> Void in
if status == .success {
remoteConfig.activateFetched()
} else {
assertionFailure("Firebase config not fetched. Error \(error!.localizedDescription)")
}
}
The last thing I do is to get the value from Firebase and check if I have the feature enable:
let featureIsAvailable = remoteConfig["feature_available"].boolValue
if featureIsAvailable { ... }
The problem is that every single time the value returns from Firebase it is false and I can't manage to get it to return the correct value that matches that Audience I created.
I also tried to do it setting a user property instead of using setUserID() and had the same result.
Any suggestions?
I've run into similar issues before, sometimes it can take a while for the fetch to finish. The check if the feature is available needs to be done when the config is successfully fixed. Something like this hopefully works for you as well:
var featureIsAvailable : Bool?
override func viewDidLoad() {
super.viewDidLoad()
configureRemoteConfig()
fetchConfig()
}
func configureRemoteConfig() {
remoteConfig = FIRRemoteConfig.remoteConfig()
// Create Remote Config Setting to enable developer mode.
// Fetching configs from the server is normally limited to 5 requests per hour.
// Enabling developer mode allows many more requests to be made per hour, so developers
// can test different config values during development.
let remoteConfigSettings = FIRRemoteConfigSettings(developerModeEnabled: true)
remoteConfig.configSettings = remoteConfigSettings!
remoteConfig.setDefaultsFromPlistFileName("RemoteConfigDefaults")
}
func fetchConfig() {
var expirationDuration: Double = 3600
// If in developer mode cacheExpiration is set to 0 so each fetch will retrieve values from
// the server.
if (self.remoteConfig.configSettings.isDeveloperModeEnabled) {
expirationDuration = 0
}
// cacheExpirationSeconds is set to cacheExpiration here, indicating that any previously
// fetched and cached config would be considered expired because it would have been fetched
// more than cacheExpiration seconds ago. Thus the next fetch would go to the server unless
// throttling is in progress. The default expiration duration is 43200 (12 hours).
remoteConfig.fetch(withExpirationDuration: expirationDuration) { (status, error) in
if (status == .success) {
print("Config fetched!")
self.remoteConfig.activateFetched()
let featureIsAvailable = self.remoteConfig["feature_available"]
if (featureIsAvailable.source != .static) {
self.featureIsAvailable = featureIsAvailable.boolValue
print("should the feature be available?", featureIsAvailable!)
}
} else {
print("Config not fetched")
print("Error \(error)")
}
self.checkIfFeatureIsAvailable()
}
}
func checkIfFeatureIsAvailable() {
if featureIsAvailable == false {
// Don't show new feature
} else {
// Show new feature
}
}
I sent an email to the Firebase team requesting support and they told me that the issue was a bug in Xcode 8(.0 and .1) using Swift.
Updating to the latest version released today, 8.2, fixed the issue.