How do I save dictionary (map object) to DynamoDB using Swift - ios

I’m trying to save a dictionary to my DynamoDB table field using low-level API. I couldn’t figure out how to do it with object mapper. There is no example for this in AWS iOS documentation and I’ve tried to research and implement Java / .NET examples of the same subject unsuccessfully.
I want to update only the dictionary field in the row using updateExpression.
I stumbled to this question while searching for answer, but it didn't help: Best way to make Amazon AWS DynamoDB queries using Swift?
Here’s the function to update dynamoDB-table:
func saveMapToDatabase(hashKey:Int, rangeKey:Double, myDict:[Int:Double]){
let nsDict:NSDictionary = NSDictionary(dictionary: myDict)
var dictAsAwsValue = AWSDynamoDBAttributeValue();dictAsAwsValue.M = nsDict as [NSObject : AnyObject]
let updateInput:AWSDynamoDBUpdateItemInput = AWSDynamoDBUpdateItemInput()
let hashKeyValue:AWSDynamoDBAttributeValue = AWSDynamoDBAttributeValue();hashKeyValue.N = String(hashKey)
let rangeKeyValue:AWSDynamoDBAttributeValue = AWSDynamoDBAttributeValue(); rangeKeyValue.N = String(stringInterpolationSegment: rangeKey)
updateInput.tableName = "my_table_name"
updateInput.key = ["db_hash_key" :hashKeyValue, "db_range_key":rangeKeyValue]
//How I usually do low-level update:
//let valueUpdate:AWSDynamoDBAttributeValueUpdate = AWSDynamoDBAttributeValueUpdate()
//valueUpdate.value = dictAsAwsValue
//valueUpdate.action = AWSDynamoDBAttributeAction.Put
//updateInput.attributeUpdates = ["db_dictionary_field":valueUpdate]
//Using the recommended way: updateExpression
updateInput.expressionAttributeValues = ["dictionary_value":dictAsAwsValue]
updateInput.updateExpression = "SET db_dictionary_field = :dictionary_value"
self.dynamoDB.updateItem(updateInput).continueWithBlock{(task:BFTask!)->AnyObject! in
//do some debug stuff
println(updateInput.aws_properties())
println(task.description)
return nil
}
}

I solved it, the problem was that AWS requires dictionary keys to always be in the form of String, any other type is not allowed.
The working solution snippet:
...
updateInput.tableName = "my_table_name"
updateInput.key = ["db_hash_key" :hashKeyValue, "db_range_key":rangeKeyValue]
let dictionaryInRightFormat:NSDictionary = ["stringKey":dictAsAwsValue]
updateInput.expressionAttributeValues = updateInput.updateExpression = "SET db_dictionary_field = :stringKey"

Related

AWS DynamoDB updateItem problems in Swift 4

I am trying to update an item in my dynamoDB noSQL database. Having some troubles implementing this in swift as there is no swift documentation yet.
I was able to create an item in the database successfully, updating an item seems to be a whole other monster.
Swift Code:
var updatedValue: AWSDynamoDBAttributeValue = AWSDynamoDBAttributeValue()
updatedValue.s = self.UserID
let dynamo: AWSDynamoDB = AWSDynamoDB()
let AddToHistory = Users()
AddToHistory?._campany = self.CompanyTextBox.text!
AddToHistory?._personalSite = self.PersonalTitleTextBox.text!
AddToHistory?._facebook = self.FacebookTextBox.text!
AddToHistory?._linkedIn = self.LinkedInTextBox.text!
AddToHistory?._title = self.TitleTextBox.text!
AddToHistory?._bio = self.BioTextBox.text!
let updateInput: AWSDynamoDBUpdateItemInput = AWSDynamoDBUpdateItemInput()
updateInput.tableName = "myTableName"
updateInput.key = ["_userId": updatedValue]
let updatedCompany = AWSDynamoDBAttributeValue()
updatedCompany?.s = AddToHistory?._campany
let updatedFacebook = AWSDynamoDBAttributeValue()
updatedFacebook?.s = AddToHistory?._facebook
let updatedLinkedIn = AWSDynamoDBAttributeValue()
updatedLinkedIn?.s = AddToHistory?._linkedIn
let updatedPersonalSite = AWSDynamoDBAttributeValue()
updatedPersonalSite?.s = AddToHistory?._personalSite
let updatedTitle = AWSDynamoDBAttributeValue()
updatedTitle?.s = AddToHistory?._title
let updatedBio = AWSDynamoDBAttributeValue()
updatedBio?.s = AddToHistory?._bio
updateInput.expressionAttributeValues = [
"_campany" : updatedCompany!,
"_facebook" : updatedFacebook!,
"_linkedIn" : updatedLinkedIn!,
"_personalSite" : updatedPersonalSite!,
"_title" : updatedTitle!,
"_bio" : updatedBio!,
]
updateInput.returnValues = AWSDynamoDBReturnValue.updatedNew
dynamo.updateItem(updateInput).continueOnSuccessWith(block: { (task:AWSTask!) -> AnyObject! in
if (task.error == nil) {
}
return nil
}
)
Not getting any warnings or errors in the editor, however when I run the app and press the button which runs this code, I get this exception thrown:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: '- init is not a valid
initializer. Use + defaultDynamoDB or + DynamoDBForKey: instead.'
Not sure what I am missing here, it must be something to do with the way I am initializing the dynamoDB object. Tried accessing a default method for init, but there is no such method. :(
Any help would be greatly appreciated, thanks in advance!
Thanks to Jake.lange's comment, I realized I could have used the object mapper that i used to create items to update them as well. Heres the code incase others run into this problem :)
//db connection mapper
let objectMapper = AWSDynamoDBObjectMapper.default()
//new instancer of User class
let itemToUpdate:CheckaraUsers = CheckaraUsers()
//populate
itemToUpdate._userId = UserID
itemToUpdate._firstName = FirstName
itemToUpdate._lastName = LastName
itemToUpdate._campany = AddToHistory?._campany
itemToUpdate._facebook = AddToHistory?._facebook
itemToUpdate._linkedIn = AddToHistory?._linkedIn
itemToUpdate._personalSite = AddToHistory?._personalSite
itemToUpdate._title = AddToHistory?._title
itemToUpdate._bio = AddToHistory?._bio
//save to dynamoDB
objectMapper.save(itemToUpdate, completionHandler:{(error: Error?) -> Void in
if let error = error {
print("Amazon DynamoDB Save Error: \(error)")
}
print("Saved Information!!!")
})
Your definition was wrong.
Change this:
let dynamo: AWSDynamoDB = AWSDynamoDB()
to:
let dynamo: AWSDynamoDB = AWSDynamoDB.default()

Save multiple data with a loop in FirebaseDatabase - Swift IOS

with my code I would save multiple data with a loop in my Firebase Database. I have used a while loop to save some strings in my Database but my app saves only the last book and I don't know how to fix this problem. Any ideas?
let refUsers = FIRDatabase.database().reference().child("Users").child("User" + tag_login).child(user_key).child("Books").child("Others")
let key = refUsers.childByAutoId().key
let multipleBooksValues = ["multipleBooks": "Yes", "read": "Yes"] as NSDictionary
refUsers.child(key).setValue(multipleBooksValues)
let refBooks = FIRDatabase.database().reference().child("Books").child("User's books").child(book_key)
var bookNumber = 0
let numberOfBooks = bookList.count
while bookNumber < numberOfBooks {
let book = bookList[bookNumber]
let values = ["book_key\(bookNumber)" : book.book_key!] as NSDictionary
refUsers.child(key).child("multipleBooksNumber").setValue(values)
refBooks.updateChildValues(["onGoingNegotiations" : "Yes", "other_user_key" : self.user_key, "other_tag_login": self.tag_login])
refUsers.child(key).child("multipleBooksNumber").observe(.value, with: { (snapshot) in
let numberChildren = Int(snapshot.childrenCount - 1)
if numberChildren == bookNumber{
bookNumber += 1
}
})
}
Thank you in advance.
Each next book in your loop is overwriting the previous book. The easiest way to prevent this is to call setValue() one level deeper in the tree:
while bookNumber < numberOfBooks {
let book = bookList[bookNumber]
refUsers.child(key).child("multipleBooksNumber/\(bookNumber)").setValue(book.book_key!)
}
Note though that the Firebase documentation and blog recommend against using arrays like this for storing data. Either store the books under their natural key:
refUsers.child(key).child("multipleBooksNumber/\(book.book_key!)").setValue(true)
Or store them under so-called push IDs:
refUsers.child(key).child("multipleBooksNumber").childByAutoId().setValue(book.book_key!);
Maybe the problem is that you save all books to the same path and they are being re-written one by another all the time, so only the last one is being saved in the end?
You specify your key once
let key = refUsers.childByAutoId().key
and then save all values to path
refUsers.child(key).child("multipleBooksNumber").setValue(values)

How to convert array to UnsafeMutablePointer<UnsafeRawPointer?> Swift 3.0?

Here was my workable code in the previous version of Swift:
let imageOptionsDictKeys = [ kCVPixelBufferPixelFormatTypeKey, kCVPixelBufferWidthKey, kCVPixelBufferHeightKey, kCVPixelBufferOpenGLESCompatibilityKey, kCVPixelBufferIOSurfacePropertiesKey]
let imageOptionsDictValues = [ cvPixelFormatType, frameW, frameH, boolYES]
var keyCallbacks = kCFTypeDictionaryKeyCallBacks
var valueCallbacks = kCFTypeDictionaryValueCallBacks
let imageOptions = CFDictionaryCreate(kCFAllocatorDefault, UnsafeMutablePointer(imageOptionsDictKeys), UnsafeMutablePointer(imageOptionsDictValues), 4, &keyCallbacks, &valueCallbacks)
After changes in the Swift 3.0 I have to convert my keys and values arrays into UnsafeMutablePointer<UnsafeRawPointer?> for creating CFDictionary.
This way:
let imageOptionsDictKeysPointer = UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: 1)
imageOptionsDictKeysPointer.initialize(to: imageOptionsDictKeys)
gives an Bad Access error.
And after reading documentation I am trying to compile this code:
let imageOptionsDictKeys = [kCVPixelBufferPixelFormatTypeKey, kCVPixelBufferWidthKey, kCVPixelBufferHeightKey, kCVPixelBufferOpenGLESCompatibilityKey]
let imageOptionsDictKeysRawPointer = Unmanaged.passUnretained(imageOptionsDictKeys).toOpaque()
let imageOptionsDictKeysPointer = UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: 1)
imageOptionsDictKeysPointer.initialize(to: imageOptionsDictKeysRawPointer)
let imageOptionsDictValues = [ cvPixelFormatType, frameW, frameH, boolYES]
let imageOptionsDictValuesRawPointer = Unmanaged.passUnretained(imageOptionsDictValues).toOpaque()
let imageOptionsDictValuesPointer = UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: 1)
imageOptionsDictValuesPointer.initialize(to: imageOptionsDictValuesRawPointer)
let imageOptions = CFDictionaryCreate(kCFAllocatorDefault, imageOptionsDictKeysPointer, imageOptionsDictValuesPointer, 4, &keyCallbacks, &valueCallbacks)
but error Generic parameter 'Instance' could not be inferred appears in the lines Unmanaged.passUnretained(array).toOpaque()
I have no idea how to create CFDictionary programmatically now.
I just solved a similar issue converting arrays to UnsafeMutablePointer< UnsafeMutablePointer<T>> which you can find here:
Swift 3 UnsafeMutablePointer initialization for C type float**
To convert swift arrays using the same scheme, use UnsafeMuTablePointer as suggested here: http://technology.meronapps.com/2016/09/27/swift-3-0-unsafe-world-2/

Firebase uploading file getting optional string in filename

I was implementing Firebase friendly chat sample while uploading image. Getting response with optional string. what is missed?
Optional(FIRStorageMetadata 0x7fc84aced790: {
bucket = "abc.appspot.com";
contentEncoding = identity;
contentType = "image/jpeg";
downloadTokens = "abctoken";
generation = 1231231;
metageneration = 1;
name = "Optional(\"S5CKnKUykANdxSvZ3wujpMXHTvg1\")/494509700277/asset.JPG";
timeCreated = "2016-09-02T11:49:00.114Z";
updated = "2016-09-02T11:49:00.114Z";
})
My code:
let filePath = "(FIRAuth.auth()!.currentUser!.uid)/(Int(NSDate.time‌​IntervalSinceReferen‌​ceDate() * 1000))/(referenceUrl.lastPathComponent!)"
was written with optional value (?) in sample so i convert it (!)
In swift you must force unwrap value to avoid this (but make sure, it's not nil).
"\(optionalValue!)"
Or unwrap it in standard way
if let value = optionalValue {
someFIRMethod("\(value)")
}

Create Entity programmatically (Core Data)

Is there a way to create a Entity programmatically on Core Data with swift2?
I searched for it, but I doesn't found something.
There are only a few tutorials on the Web (possibly only one).
I am not a fan of Xcode's GUI tools (Nibs, Storyboards, XCDataModeld, etc), so creating everything (from DB to UI) in code is usual thing for me.
The article referenced by #Lubos (2 minutes after I added a link to it in comments, hmm...) is written in ObjC.
So, here is a Swift code:
internal var _model: NSManagedObjectModel {
let model = NSManagedObjectModel()
// Create the entity
let entity = NSEntityDescription()
entity.name = "DTCachedFile"
// Assume that there is a correct
// `CachedFile` managed object class.
entity.managedObjectClassName = String(CachedFile)
// Create the attributes
var properties = Array<NSAttributeDescription>()
let remoteURLAttribute = NSAttributeDescription()
remoteURLAttribute.name = "remoteURL"
remoteURLAttribute.attributeType = .StringAttributeType
remoteURLAttribute.optional = false
remoteURLAttribute.indexed = true
properties.append(remoteURLAttribute)
let fileDataAttribute = NSAttributeDescription()
fileDataAttribute.name = "fileData"
fileDataAttribute.attributeType = .BinaryDataAttributeType
fileDataAttribute.optional = false
fileDataAttribute.allowsExternalBinaryDataStorage = true
properties.append(fileDataAttribute)
let lastAccessDateAttribute = NSAttributeDescription()
lastAccessDateAttribute.name = "lastAccessDate"
lastAccessDateAttribute.attributeType = .DateAttributeType
lastAccessDateAttribute.optional = false
properties.append(lastAccessDateAttribute)
let expirationDateAttribute = NSAttributeDescription()
expirationDateAttribute.name = "expirationDate"
expirationDateAttribute.attributeType = .DateAttributeType
expirationDateAttribute.optional = false
properties.append(expirationDateAttribute)
let contentTypeAttribute = NSAttributeDescription()
contentTypeAttribute.name = "contentType"
contentTypeAttribute.attributeType = .StringAttributeType
contentTypeAttribute.optional = true
properties.append(contentTypeAttribute)
let fileSizeAttribute = NSAttributeDescription()
fileSizeAttribute.name = "fileSize"
fileSizeAttribute.attributeType = .Integer32AttributeType
fileSizeAttribute.optional = false
properties.append(fileSizeAttribute)
let entityTagIdentifierAttribute = NSAttributeDescription()
entityTagIdentifierAttribute.name = "entityTagIdentifier"
entityTagIdentifierAttribute.attributeType = .StringAttributeType
entityTagIdentifierAttribute.optional = true
properties.append(entityTagIdentifierAttribute)
// Add attributes to entity
entity.properties = properties
// Add entity to model
model.entities = [entity]
// Done :]
return model
}
This code is equal to this CD model (created in Xcode's GUI):
Creating models in code is much more complicated than using GUI.
But, IMO, it is faster and safer than loading CoreData model file to get your model (what if no file exists? or the file is damaged?).
By 'safer' I mean that you don't have to handle disk IO errors related to reading CoreData model from disk (your model is in code, there is no need in model file). Average CoreData user just don't want to handle these errors because its easier to terminate an application
It is possible to define core data model programmatically. I found a good example, though it is written in Objective C. I am sure it is working also for Swift 2. You just need to rewrite it. Should take a few minutes.
https://www.cocoanetics.com/2012/04/creating-a-coredata-model-in-code/

Resources