Amplify-ios: How to update subscript / relational model in AppSync

Created on 4 Oct 2020  路  13Comments  路  Source: aws-amplify/amplify-ios

I have the following model

type Process @model{
    id: ID!
    ...
    tasks:[Task]! @connection(keyName:"byProcess",fields:["id"])
}

type Task @model
    @key(name:"byProcess",fields:["processID"],queryField:"tasksByProcess"){
    id:ID!
    processID:ID!
    name: String!
    isFinished:Boolean!
    ...
}

This generates me code and I can access it like this

var task1 = Task(id:"34",processID:"12",name:"Test",isFinished:false)
var task2 = Task(id:"56",processID:"12",name:"Test",isFinished:false)
var process = Process(id: "12",..., tasks:[task1,task2])

Now I want to update task1 to isFinished = true

//update directly 
process.tasks[0].isFinished = true //error: Cannot assign to property: subscript is get-only.
// first update task model and than assign it to process
task1.isFinished = true
process.tasks[0] = task1 // doesn't work either

How can I get the changes from my subscript 'Task' to my 'Process'?

datastore question requesting info

All 13 comments

Hi @chris-mds

Changing Task and then adding to Process will not update the local or backend database. You need to get the Task and then call a save operation on it, then query the Process to get the updated Process containing the Task.

Hi @royjit ,
I will try this solution, when updating at my app works. But unfortunately it doesn't.
I tried the following steps

Creating a simple Task where my variableisFinished = false. This works and it shows up like I expected in DynamoDB.
Now I am fetching this Task. This works as well and I receive all my data.
But now I want to update my Task to isFinished = true and this doesn't work.

var finishedTask = task
finishedTask.isFinished = true
print(finishedTask) // console shows the right task and isFinished is successfully set to true
Amplify.API.mutate(request: .update(finishedTask)){ event in
    switch event{
    case .failure(let error):print(error)
    case .success(let result):print(result) // operation success, but isFinished from my task is still false
    }
...

I tried also Amplify.API.updateMutation ... but this also doesn't updated any value from my task, though the operation was signed as successfully performed

Hey @chris-mds,

I have just created a project using the latest version of Amplify with the following schema:

type Process @model {
  id: ID!
  tasks: [Task]! @connection(keyName: "byProcess", fields: ["id"])
}

type Task
  @model
  @key(name: "byProcess", fields: ["processID"], queryField: "tasksByProcess") {
  id: ID!
  processID: ID!
  name: String!
  isFinished: Boolean!
}

Then I populate the database with one Process and one Task using Amplify.API.mutate(request: .create(...)). After that I:

  1. Query the tasks using Amplify.API.query()
  2. Grab the tasks and toggle the isFinished based on the current value.
  3. Call Amplify.API.mutate(request: .update(...)) on the task with the changed field

The field is being updated as expected. I can see isFinished switching between true/false every time I run my logic. I can confirm that both the response from the step number 3 and the value in the DynamoDB console are changed as expected.

I could not reproduce this, were you able to find a solution to your issue?

Hi @drochetti,

still doesn't work and I figured out this isn't a problem due to the iOS API, because in the console it doesn't work either looking to my screenshot.

Bildschirmfoto 2020-10-14 um 14 22 47

Maybe there is something wrong with the resolver? But doesn't changed anything in the resolvers..

Hey @chris-mds, that's odd. Did you change anything in your resolvers? If you're using the default ones it should work.

Also, check the version of the CLI. If the error persists I recommend you cut an issue in the CLI repo.

Hi @drochetti,

found the issue. My conflict resolution flow rejected the update:

Bildschirmfoto 2020-10-14 um 18 23 47

Now my question is, why cant we see that issue as en error or feedback from the call and how can I solve it? I can disable conflict detection, but later, when I want to add DataStore I think I will face the same issue?! Any suggestion?

Hey @chris-mds, I couple of points about your latest finding:

  1. When you provision the backend to use DataStore (i.e. enable _conflict detection_), the schema is annotated with metadata relevant to distributed data reconciliation. Tables are decorated with _version, _lastChangedAt and _deleted. Once that's enabled, using the Amplify.API category with that backend becomes a challenge, which means it's up to the developer to provide those fields. You can read more about that here: https://docs.amplify.aws/lib/datastore/how-it-works/q/platform/ios#writing-data-from-the-appsync-console
  2. As for your question _"why cant we see that issue as en error or feedback from the call and how can I solve it?"_, the conflict resolution is a special type of event and it's not handled as part of the error handling logic. There is a conflictHandler that can be setup and has capabilities such as .applyRemote, .applyLocal and .retry(model) so you can make a decision of what to do with the conflict. Read more here: https://docs.amplify.aws/lib/datastore/conflict/q/platform/ios#custom-configuration

Keep in mind though that in your case the conflict only happened because the _version was missing. Like mentioned on point 1, using Amplify.API alongside with Amplify.DataStore can be challenging, you will have to manage the metadata fields yourself. We do intend to add support for that scenario in the future, but it's not something we're actively working at right now.

Hey @drochetti ,
updating works with the DataStore-save operation.
Would it be a good approach to query and subscribe with API framework and do mutations with DataStore operations?
I need to query with API because I can build my own Queries for relational models using an extension for the GraphQLRequest model.
I know this is some kind of weird, but my customer's environment needs to get (query) data from the backend when they are online and after this they change the location to an place without internet connection and work and mutate the queried data at this offline location since the end of the day. After working it should sync their mutations to the backend

I know this is some kind of weird, but my customer's environment needs to get (query) data from the backend when they are online and after this they change the location to an place without internet connection and work and mutate the queried data at this offline location since the end of the day. After working it should sync their mutations to the backend

Haha it doesn't sound weird, it's interesting. Do you mean the DataStore cloud sync engine is not enough to guarantee the offline/online data sync? The scenario you describe is exactly what DataStore attempts to enable by default, so I would like to understand if there's something missing.

I need to query with API because I can build my own Queries for relational models using an extension for the GraphQLRequest model.

Can you elaborate on this? Could you query on the local storage with DataStore.query() instead? Assuming the data is synchronized between the API and the local storage, of course.

From what I can tell so far, it seems like we might have a path forward that doesn't depend on you managing the data between API and DataStore yourself. Maybe there is some requirement nuance I'm missing.

Hi @drochetti ,

I have the feeling I misunderstood something fundamental here lol.
The query operation on DataStore only queries the local storage? So how can I query a list of items from my table with the DataStore API? When I have a table of lets say 100k items, this whole table isn't stored locally?
And how can I list items respectively work with my GSI I extra created with an @connection directive in my schema, so I can query between relations?

Looking to my schema at my first post, how could I fetch a list of tasks from a specific process, when I only have the Amplify.DataStore.query operation, which only takes a model or model and id as an input?

@chris-mds

The query operation on DataStore only queries the local storage? So how can I query a list of items from my table with the DataStore API?

Yes, it only queries the local storage, but it also keeps a cloud sync process going when online to make sure the local and remote data are in sync.

When I have a table of lets say 100k items, this whole table isn't stored locally?

By default it syncs all the data the logged user has access to. If you believe you need further optimization, we're actively working on a feature that allows you to specify a filter for a subset of data you need to sync.

And how can I list items respectively work with my GSI I extra created with an @connection directive in my schema, so I can query between relations?
Looking to my schema at my first post, how could I fetch a list of tasks from a specific process, when I only have the Amplify.DataStore.query operation, which only takes a model or model and id as an input?

If you rely on DataStore to query the data locally, you don't even need the GSI. The DataStore.query() is overloaded. You can query by id like you pointed out but you can also query using predicates, for example:

Amplify.DataStore.query(Task.self, where: Task.keys.processID == process.id) {
    // handle result
}

You can read more here: https://docs.amplify.aws/lib/datastore/data-access/q/platform/ios#predicates

Okay now its gettin clearer.
Will wait for the subset filter to use DataStore and will begin with an OnlyOnline-solution and APIPlugin
Thank you for your help @drochetti .

@chris-mds If I may, I would recommend you try using Amplify.DataStore if you're aiming for offline support. I would imagine your development dataset is not too large and you can always optimize the sync query later instead of converting a codebase from API to DataStore.

Good luck in your project!

Was this page helpful?
0 / 5 - 0 ratings