How to store relational (one to many or many to one) data with Amplify iOS (AppSync)? - ios

today checking some of the amplify documentation (I know this one says it is a preview in the iOS scenario) but I have ran into a road block.
Assumptions
Amplify is correctly configured in my iOS project. I can push data to Person and query the Amplify.API
The schema has been defined as:
type Person #model {
id: ID!
name: String!
possessions: [Thing] # list of things this person owns.
#connection(keyName: "byPerson", fields: ["id"])
}
type Thing #model
#key(name: "byPerson", fields: ["personId"]) {
id: ID!
name: String!
personId: ID!
ownerOfThings: Person # defining the 'belongsTo' property.
#connection(fields: ["personId"])
}
This generates the following code:
public struct Person: Model {
public let id: String
public var name: String
public var possessions: List<Thing>?
public init(id: String = UUID().uuidString,
name: String,
possessions: List<Thing>? = []) {
self.id = id
self.name = name
self.possessions = possessions
}
}
public struct Person: Model {
public let id: String
public var name: String
public var ownerOfThings: Person?
public init(id: String = UUID().uuidString,
name: String,
ownerOfThings: Person? = nil) {
self.id = id
self.name = name
self.ownerOfThings = ownerOfThings
}
}
Here is where I ran into trouble. Amplify.API doesn't seem be saving my object and its associated data in a single mutation. I have to call it as nested operations to have an effect.
// sample on how I am trying to save data.
var thing = Thing(name: "Long Claw")
let person = Person(
name: "Jon Snow",
possessions: List([ thing ])
)
Amplify.API.mutate(of: person, type: .create) { ev in
// doing something with the event.
print(String(describing: ev)) // this works. It saves the instance to DynamoDB
// unfortunately, it did not save the instance of thing... let's try to correct this.
thing.ownerOfThings = person
Amplify.API.mutate(of: thing, type: .create) { ev2 in
// do something else with this...
print(String(describing: ev2))
// this ^ crashes badly...
}
}
The code above will generate an output similar to:
Result.success(Person(id: "EC4BEEE1-C1A1-4831-AB86-EA1E22D8AD48", name: "Jon Snow", possessions: nil))
GraphQLResponseError<Thing>: GraphQL service returned a successful response containing errors: [Amplify.GraphQLError(message: "Variable \'input\' has coerced Null value for NonNull type \'ID!\'", locations: Optional([Amplify.GraphQLError.Location(line: 1, column: 26)]), path: nil, extensions: nil)]
I've tried declaring the relationship as:
type Person #model {
id: ID!
name: String!
possessions: [Thing] # list of things this person owns.
#connection(keyName: "byPerson", fields: ["id"])
}
type Thing #model
#key(name: "byPerson", fields: ["personId"]) {
id: ID!
name: String!
personId: ID!
# ownerOfThings: Person
# #connection(fields: ["personId"]) # Not belongsTo for you!
}
Or a variation of this, defining the possessions as possessions: [Thing] #connection.
All of them generate various (although some what related) errors, preventing me from storing my data.
So, the question is:
How do you specify the relationship in iOS to save it?

Related

SwiftUI: Why Is an Extension Used for a Custom Struct (Apple iOS App Dev Tutorial)

In the Apple [iOS App Dev Tutorial] (as of Jan 22)(https://developer.apple.com/tutorials/app-dev-training), under the Creating a Navigation Hierarchy section, we extend a struct that we have defined.
I have tried reading the Swift documentation about extensions, but don't really understand why this is done here. The documentation talks about extending system types such as adding extra properties to the system type Double, but not adding extra properties to something that we have full control over, such as our own structure.
I am sure it is a best practice, as this is an Apple tutorial, but they don't really do a good job of explaining it.
This is an example of the code they want you to write:
import SwiftUI
struct DailyScrum: Identifiable {
let id: UUID
var title: String
var attendees: [Attendee]
var lengthInMinutes: Int
var theme: Theme
init(id: UUID = UUID(), title: String, attendees: [String], lengthInMinutes: Int, theme: Theme) {
self.id = id
self.title = title
self.attendees = attendees.map { Attendee(name: $0) }
self.lengthInMinutes = lengthInMinutes
self.theme = theme
}
}
extension DailyScrum {
struct Attendee: Identifiable {
let id: UUID
var name: String
init(id: UUID = UUID(), name: String) {
self.id = id
self.name = name
}
}
}
I am uncertain why the Attendee structure has to be defined within an extension. For example, this works too:
import SwiftUI
struct Attendee: Identifiable {
let id: UUID
var name: String
init(id: UUID = UUID(), name: String) {
self.id = id
self.name = name
}
}
struct DailyScrum: Identifiable {
let id: UUID
var title: String
var attendees: [Attendee]
var lengthInMinutes: Int
var theme: Theme
init(id: UUID = UUID(), title: String, attendees: [String], lengthInMinutes: Int, theme: Theme) {
self.id = id
self.title = title
self.attendees = attendees.map { Attendee(name: $0) }
self.lengthInMinutes = lengthInMinutes
self.theme = theme
}
}
I'm just not sure why you would do one over the other.
It is styling and also focuses the code in this case
Your type as originally written is
DailyScrum.Attendee
If you change it to the second version you will have 2 separate independent types.
Attendee
and
DailyScrum
Without looking at the tutorial I would assume that there is another type of Attendee. Such as..
Conference.Attendee
or
WeeklyScrum.Attendee
But as you pointed out this would only make sense if they are different like a Homonym. Some samples are...
Bird.Crane
Construction.Crane
or
Dog.Bark
Tree.Bark
If all you are using is an id and a name there is no point to have a separate data structure.
In the tutorial, I believe that it is a pattern choice so that the data model more closely mirrors the SwiftUI layout for ease of understanding.
Attendees only appear as a child view of the detailView using ForEach(...) to generate a VStack. Therefore, in this case, where MVVM isn't being used, it makes sense to clearly map the data model to the view, and make it explicit in the code pattern that the Attendee type only occurs within an instance of a DailyScrum, just as attendees details only appear within the detailView.

Protocols and Enums in Swift with Apollo

I am using Apollo for Swift in an iOS app. I have multiple types that all represent the same object. These types are auto-generated from a schema file and look something like this.
struct CurrentUser {
var id: String
...
}
struct MyUser {
var id: String
...
}
Basically Apollo generates multiple Swift types (one for each query) for the same underlying data type.
I want to create a new struct that unifies these types.
I would like to do something like this:
protocol UserProtocol {
var id: String { get }
}
struct User {
var id: String
...
init(_ data: UserProtocol) {
self.id = data.id
...
}
}
This approach however gives me an error when I try to construct a user object, telling me that "Type MyUser does not conform to UserProtocol". If I try to coerce the type with data as! UserProtocol I get a crash.
The only solution I've found is the following:
enum UserType {
case .currentUser(CurrentUser)
case .myUser(MyUser)
}
struct User {
var id: String
...
init(_ data: UserType) {
switch data {
case .myUser(let user):
self.id = data.id
...
case .currentUser(let user):
self.id = data.id
...
}
}
}
This approach works, but it leads to a lot of duplicated code in the init function. Is there a better way to do this in Swift?
I suspect the problem is that you need to explicitly conform the Apollo generated types to your protocol:
extension CurrentUser: UserProtocol { }
extension MyUser: UserProtocol { }
Remember that Swift is not duck-typed like some other languages, so a type with member var id: String is not UserProtocol until you declare it as such.
If for some reason you need to do some transformation of the Apollo types to fit the app models in the future, those extensions are a good place to do that, too.

How to replace/update dictionary in Array of Dictionary based on Type

I'm getting below JSON response from server, and displaying phone number on screen.
Now user can change/update any of phone number, so we have to update particular mobile number in same object and send it to server.
"phone_numbers": [
{
"type": "MOBILE",
"number": "8091212121"
},
{
"type": "HOME",
"number": "4161212943"
},
{
"type": "BUSINESS",
"number": "8091212344"
}
]
My model class is looks like this:
public struct Contact: Decodable {
public let phone_numbers: [Phone]?
}
public struct Phone: Decodable {
public let type: PhoneType?
public let number: String?
}
I'm struggling to update this JSON object for particular phone number.
For example, if I want to update BUSINESS number only in above array, What's best way to do it.
I'm using XCode 11 and Swift 5.
Because all your properties are defined as constants (let), nothing can be updated. You have to initialize and return a new Contact object with the updated phone numbers.
If you change the properties to var, then you can update:
public enum PhoneType: String, Decodable {
case mobile = "MOBILE"
case home = "HOME"
case business = "BUSINESS"
}
public struct Contact: Decodable {
public var phone_numbers: [Phone]?
mutating func update(phoneNumber: String, for type: PhoneType) {
guard let phone_numbers = self.phone_numbers else { return }
for (i, number) in phone_numbers.enumerated() {
if number.type == type {
self.phone_numbers![i].number = phoneNumber
}
}
}
}
public struct Phone: Decodable {
public var type: PhoneType?
public var number: String?
}
var contact = try! JSONDecoder().decode(Contact.self, from: jsonData)
contact.update(phoneNumber: "123456", for: .business)
I'm struggling to update this JSON object for particular phone number.
It shouldn't be a JSON object when you update it. Think of JSON as just a format for transferring data. Once transferred, you should parse it into something that you can work with, like an array of dictionaries or whatever. If you've done that, then more specific questions you might ask are:
How can I find a specific entry in an array?
How can I modify the fields of a struct?
How can I replace one entry in an array with another?
After looking at the definitions of your structures, I think the problem you're having probably has to do with how you've declared them:
public struct Phone: Decodable {
public let type: PhoneType?
public let number: String?
}
Because you used let to declare type and number, those fields cannot be changed after initialization. If you want the fields of a Phone struct to be modifiable, you need to declare them with var instead of let.
The same thing is true for your Contact struct:
public struct Contact: Decodable {
public let phone_numbers: [Phone]?
}
You've declared phone_numbers as an immutable array because you used let instead of var. If you want to be able to add, remove, or modify the array in phone_numbers, you need to use var instead.
The struct declarations you have right now work fine for reading the data from JSON because all the components of the JSON data are constructed using the values from the JSON. But again, you'll need to make those structs modifiable by switching to var declarations if you want to be able to make changes.
There are a couple of ways to approach this (I'm assuming PhoneType is an enum you have somewhere)
You can iterate over the array and guard for only business numbers, like so
for phone in phone_numbers{
guard phone.type == .MOBILE else { continue }
// Code that modifies phone
}
You can filter and iterate over the array, like so
phone_numbers.filter {$0.type == .BUSINESS }.forEach { phone in
// Modify phone here
}
You can then modify the right value in the array with it's index, like this
for (phoneIndex, phone) in phone_numbers.enumerated() {
guard phone.type == .BUSINESS else { continue }
phone_numbers[phoneIndex].type = ANOTHER_TYPE
}
Some can argue that the second is preferred over the first, because it is an higher order function, but in my day to day activities, I tend to use both and believe that this is a matter of taste

Realm browser returns empty properties

I have a User class which contains an Agency class.
class User: Object {
dynamic var agency: Agency?
...
}
class Agency: Object {
var name: String = ""
...
}
And when i print user.agency i get all the info, but when i print user.agency?.name i get an empty String.
The name property of Agency needs to be dynamic.

Using nested nodes from mutation payload to update local store

Let's say I have a typical todo application. There's a CreateTodo mutation that takes a string and returns CreateTodoPayload with the newly created edge. The schema looks like this:
type Todo implements Node {
id: String!
content: String!
user: User!
}
type User implements Node {
id: String!
todos: [Todo]
}
input CreateTodoInput {
clientMutationId: String!
content: String!
}
type CreateTodoPayload {
clientMutationId: String!
todo: Todo
}
How can I specify that this mutation will update the user node via todo using FIELDS_CHANGE? fieldIDs requires me to specify an immediate field on the payload, so I can make it work by also returning the user, but that seems counter to what Relay is trying to accomplish.
Similarly with NODE_DELETE, RANGE_ADD and RANGE_DELETE (even though this example doesn't use connections), parentName is a string for a field name on the payload and it seems one cannot specify a nested field, i.e., todo.user.

Resources