JSON file resets after I reopen my application - swift

I am storing in a json file some user credentials (email, password, nickname). When I open the app on simulator and create multiple test accounts all works perfectly.
import Foundation
var users: [UserCredentials] = []
func save (email: String, password: String, nickname: String) {
let newUsers = UserCredentials(email: email, password: password, nickname: nickname)
users.append(newUsers)
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
do {
let data = try encoder.encode(users)
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("users.json")
print(url.path)
try data.write(to: url)
} catch {
print("Error encoding JSON: \(error)")
}
}
The issue comes up when I reset the app and I type a new testuser. The json file resets instead of loading the testusers I saved before. Is there a way where the users stay saved in a file even when I restart the simulator?
Before reset:
```JSON
[
{
"email" : "beforereset#yahoo.com",
"nickname" : "beforereset12",
"password" : "test1234A"
},
{
"email" : "beforereset1#yahoo.com",
"nickname" : "beforereset123",
"password" : "test1234A"
}
]
```
After reset:
```JSON
[
{
"email" : "afterreset#yahoo.com",
"nickname" : "afterreset12",
"password" : "1234testA"
}
]
```
Where are the beforereset emails?

This line
try data.write(to: url)
...deletes the existing file's contents and replaces them. Thus every time save runs, the previous contents of the file are lost. (Unless of course you read those contents from the file into users before saving. But no code that you have shown us does that, so I have to conclude that you're not doing it at all.)
Where are the beforereset emails?
Gone. You erased them.

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()
}

can't open Realm using mongodb sync

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).

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 :)

How to query thousands of values and only return ones in array - Firebase Swift

In firebase realtime database, I have data as such:
"users" : {
"37KfZKDwrieIEKI9juAC4Xm8aPi1" : {
"isConnected" : false,
"isGuestUser" : false,
"lastOnline" : 1510250272022,
"profilePicture" : "5a039030515e78653148",
"userID" : "37KfZKDwrieIEKI9juAC4Xm8aPi1",
"username" : "adsfasdfasdf"
},
"4D1GNiRH5NeRxpmTNg6JhJ3iTck1" : {
"isConnected" : false,
"isGuestUser" : true,
"lastOnline" : 1510077502788,
"profilePicture" : "5a01f2648278b6652011",
"userID" : "4D1GNiRH5NeRxpmTNg6JhJ3iTck1",
"username" : "ihoho"
},
"5UA3INZ7i0dnNtgX0ai5ABhjxh43" : {
"isConnected" : false,
"isGuestUser" : true,
"lastOnline" : 1512610102474,
"profilePicture" : "5a14df775a34f2388873",
"userID" : "5UA3INZ7i0dnNtgX0ai5ABhjxh43",
"username" : "jlkjlkjlkj"
},...
I am using an external API that returns a Json that looks like this
"candidates" : [
{
"enrollment_timestamp" : "1510182689539",
"subject_id" : "37KfZKDwrieIEKI9juAC4Xm8aPi1",
},
{
"enrollment_timestamp" : "1513557650425",
"subject_id" : "CKUVZ7XtY9VKJakn1lBV7MVW1702",
},
{
"enrollment_timestamp" : "1507578748901",
"subject_id" : "l7VDdtGFpMe8BRbrlCyAciTvONk1",
},...
The ultimate goal is to get all the users from the json from the external api that are online. This requires listening to the 'isConnected' endpoint in each user, and determining if it is true or false.
Now this is impossible using firebase and my current data structure, because firstly firebase does not support multiple query parameters, so I cannot do where userID = subjectID && where isConnected == true, and secondly, more importantly, firebase does not let me do the equivalent of WHERE IN, i.e. mapping userID's to an array of potential userIDs supplied client side (fyi subjectID === userID)
So what I did was restructure my data like so;
"onlineUsers" : {
"Aze1x7jTZIbPyyPmlUYWVdEyAd73" : true,
"CQdmMxkmqBerRGOQpFblx7SO4D33" : true,
"EptMK62Kt1Sp1EIb5meuKHVpUqs1" : true,
"J2X65KauDlSvkN4Yp5V4IF0sTnx1" : true,
"KnYEqsekY9YXV3ayOx082xw8VQX2" : true,
"aGLrKH31YvRKrB8KYQmZ4sA122m1" : true,
"ab3JZyw9YMdpW3mXkZc2BjYkxej2" : true,
"gpQic1EzSnXL9x5DhaoXxcWrGF22" : true,
"qQaBPMaDOrXrWddsijfMJWusuGG3" : true,
"tDWEUoKS4mUdQ1bWeiLTlhwCSoD3" : true
},
Now all I have to do to get all online users that are in the external api json is query onlineUsers with the condition parameter checking by the key. That way I know if the result is null, the user is not online:
static func queryDatabase(child: String, queryEqual: String, keyOf: Int, completionHandler: #escaping (_ return: AnyObject?, _ error: String?) -> Void){
print("querying db with child ", child)
let ref = databaseReference.child(child).queryOrderedByKey().queryEqual(toValue: queryEqual)
ref.observe(.value, with:{ (snapshot: DataSnapshot) in
print("subjectID: ", queryEqual, "at key ", keyOf)
print("queryResult", snapshot)
if let value = (snapshot.value as? [String: AnyObject])?[queryEqual] {
print("unwrapped snapshot dict value from key: ", value)
completionHandler(value, nil)
}else{
print("no value for key \(queryEqual) so setting return as nil")
completionHandler(nil, nil)
}
}){ (error) in
print(error.localizedDescription)
completionHandler(nil, error.localizedDescription )
}
}
This function would be called simple by looping through the external api json, and calling this function ^^ every iteration, something like:
for (key, valueSubjectID) in arrayOfOrderedMatches.enumerated(){
//call function queryDatabase here
queryDatabase(child: "onlineUsers", queryEqual: valueSubjectID, keyOf: key, completionHandler:{ (response, error) in
//it would return the user if they were online here
)}
}
Now. This works. It is the most efficient way I can possible think of doing it after looking around the internet and considering every possibility. However, now onto the big problem: the json from the api can be thousands and thousands of users long. This means thousands of listeners would be being attached to check each individual user to see if they are online. This, as I have been told by a Firebase developer, is not ideal. I should not be attaching thousands of listeners. Additionally, for some reason, listeners stop being added at around 3500, i'm guessing because it bugs out. I can't remove the listeners if the query returns null (ie offline) because of the way the cache offline persistence works, and even if I could I don't think this would solve the problem. Is there any way this problem can be solved?
Let me restate the objective.
The app is provided a large list of user id's (as JSON) and the goal is to know which of those users are online.
Here's two options:
We start with a users node
users
uid_0
name: "Frank"
online: true
uid_1
name: "Jeff"
online: false
When the JSON of the users we want to check is received, we convert it to an Array for easy access.
Then, we have a couple of options
1) Iterate over the array and observeSingleEvent for each uid to get it's current status. This won't be a query since we can directly access the uid so it will be fairly lightweight by comparison to a query.*
let arrayToTest = [String]() //the array of uid's to test
for uid in arrayToTest {
let thisUserRef = self.ref.child("users").child(uid)
thisUserRef.observeSingleEvent(of: .value, with: { snapshot in
let dict = snapshot.value as! [String: Any]
let status = dict["online"] as! Bool
print("uid: \(uid) online status is: \(status)")
})
}
2) Let Firebase do the heavy lifting by adding a .childAdded event to the users node, which will iterate over all of the users one at a time.
Compare the uid in the snapshot to the uid's in the array and if it matches, get it's status (on/offline) and if it doesn't exist in the array, ignore it.
let usersRef = self.ref.child("users")
let arrayToTest = [String]() //the array of uid's to test
usersRef.observe(.childAdded, with: { snapshot in
let dict = snapshot.value as! [String: Any]
let uid = dict["user_id"] as! String
if arrayToTest.contains(uid) {
let status = dict["online"] as! Bool
print("uid: \(uid) online status is: \(status)")
}
})
Option 2) will generally be better as it's only loading one at a time and Firebase is doing all of the work - we're just checking to see if the uid from the snapshot exists in code.
3)
As a third option we can leverage a query. Note that in the first two options we retrieved our data using the lightweight observe function. Queries are heavier by comparison and utilize more resources - however, the snapshot results are limited to only those users that are logged in, and if there are a significant amount of users may be the best option.
let usersRef = self.ref.child("users")
let queryRef = usersRef.queryOrdered(byChild: "online").queryEqual(toValue: true)
let arrayToTest = [String]() //the array of uid's to test
queryRef.observe(.childAdded, with: { snapshot in
let dict = snapshot.value as! [String: Any]
let uid = dict["user_id"] as! String
if arrayToTest.contains(uid) {
print("uid: \(uid) online status is: true")
}
})
*Firebaser's will frown on 1) as they strongly recommend against doing observes in a tight loop. So 2) if you want to get confirm every user exists and 3) if you're just interested in online users.

firebase snapshot value to dictionary

In my app there is a data object named room that consists of the following properties...
class Room: NSObject {
var rid: String?
var owner: String?
var groupChatName: String?
var groupChatDescription: String?
var members: AnyObject?
}
Here is the corresponding JSON
"A632CA68-40D9-4F3A-B8C8-245457057443" : {
"groupChatDescription" : "Test Description",
"groupChatName" : "Test Name",
"members" : {
"WuqCAt4mM3h0P0X1m7hVZ7NQyLC2" : {
"username" : "Steve"
}
},
"owner" : "Steve"
},
When I retrieve these values from my database, I put them in a dictionary
func fetchAllRooms(){
Database.database().reference().child("rooms").observe(.childAdded, with: {(snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let room = Room()
room.rid = snapshot.key
room.setValuesForKeys(dictionary)
self.rooms.append(room)
print("\(room.members)")
DispatchQueue.main.async(execute: {
self.collectionView?.reloadData()
})
}
print("end of room snap")
}, withCancel: nil)
}
The data matches up correctly however handling the members is proving more difficult than I originally expected.
print("\(room.members)") Returns
Optional({
WuqCAt4mM3h0P0X1m7hVZ7NQyLC2 = {
username = "Steve";
};
})
That is a lot of extraneous text. Especially considering that this is only one member and I need to return a list of members in app.
What I believe would be best for my case is to create a dictionary for the members within the current dictionary. considering each member will have additional attributes such as a reference to their profile image.
So I could call something like print("\(room.members.usernames)") and get all the usernames and still be able to use the other attributes later on.
But I don't know if this is the best solution or even where to begin. I was hoping FireBase would have some built in methods to handle this but so far I haven't been able to find anything.
Any answers, suggestions, and or references are greatly appreciated.