Amplify has re-imagined the way frontend developers build fullstack applications. Develop and deploy without the hassle.

Page updated Apr 29, 2024

Manipulating data

Amplify iOS v1 is now in Maintenance Mode until May 31st, 2024. This means that we will continue to include updates to ensure compatibility with backend services and security. No new features will be introduced in v1.

Please use the latest version (v2) of Amplify Library for Swift to get started.

If you are currently using v1, follow these instructions to upgrade to v2.

Amplify libraries should be used for all new cloud connected applications. If you are currently using the AWS Mobile SDK for iOS, you can access the documentation here.

Create and update

To write data to the DataStore, pass an instance of a model to Amplify.DataStore.save():

Amplify.DataStore.save(
Post(title: "My first post",
description: "Amplify.DataStore is awesome!",
status: .draft)
) {
switch $0 {
case .success:
print("Created a new post successfully")
case .failure(let error):
print("Error creating post - \(error.localizedDescription)")
}
}
let saveSink = Amplify.DataStore.save(
Post(
title: "My first post",
description: "Amplify.DataStore is awesome!",
status: .draft
)
)
.sink {
if case let .failure(error) = $0 {
print("Error creating post - \(error.localizedDescription)")
}
}
receiveValue: {
print("Created a new post successfully: \($0)")
}

The save method creates a new record, or in the event that one already exists in the local store, it updates the record.

let existingPost: Post = /* get an existing post */
existingPost.title = "[updated] My first post"
Amplify.DataStore.save(existingPost) {
switch $0 {
case .success:
print("Updated the existing post")
case .failure(let error):
print("Error updating post - \(error.localizedDescription)")
}
}
let existingPost: Post = /* get an existing post */
existingPost.title = "[updated] My first post"
let saveSink = Amplify.DataStore.save(existingPost).sink {
if case let .failure(error) = $0 {
print("Error updating post - \(error.localizedDescription)")
}
}
receiveValue: {
print("Updated the existing post: \($0)")
}

Avoid working with stale data!

Model instances which store values, such as those from the results of a DataStore.Query operation, can become stale and outdated when their properties are updated. This can be the result of a manual change or even a side effect of real time data being received by the application. In order to ensure you are performing mutations on the latest committed state to the system, either perform a query directly before the DataStore.save() operation or observe the model to keep the state updated at all times and perform mutations on that latest state referencing the model instance. The preceding example demonstrates one approach. The following example demonstrates the observeQuery approach.

import SwiftUI
import Amplify
import Combine
/*
Example showing how to observe the model and keep the state updated before performing a save. This uses the
`@Published` property for views to observe and re-render changes to the model.
*/
class PostViewModel: ObservableObject {
@Published var post: Post?
var subscription: AnyCancellable?
init() {
}
func observe(postId: String) {
self.subscription = Amplify.DataStore.observeQuery(for: Post.self,
where: Post.keys.id == postId).sink { completion in
print("Completion event: \(completion)")
} receiveValue: { snapshot in
guard let post = snapshot.items.first else {
return
}
DispatchQueue.main.async {
self.post = post
}
}
}
func updateTitle(_ title: String) {
guard var post = post else {
return
}
post.title = title
Amplify.DataStore.save(post) { result in
switch result {
case .success(let updatedPost):
print("Updated post successfully: \(updatedPost)")
case .failure(let error):
print("Failed to update post: \(error)")
}
}
}
}
struct PostView: View {
@StateObject var vm = PostViewModel()
@State private var title = ""
let postId: String
init(postId: String) {
self.postId = postId
}
var body: some View {
VStack {
Text("Post's current title: \(vm.post?.title ?? "")")
TextField("Enter new title", text: $title)
Button("Click to update the title to '\(title)'") {
vm.updateTitle(title)
}
}.onAppear(perform: {
vm.observe(postId: postId)
})
}
}

Delete

To delete an item, simply pass in an instance.

Amplify.DataStore.delete(post) {
switch $0 {
case .success:
print("Post deleted!")
case .failure(let error):
print("Error deleting post - \(error.localizedDescription)")
}
}
let sink = Amplify.DataStore.delete(post).sink {
if case let .failure(error) = $0 {
print("Fetch session failed with error \(error)")
}
}
receiveValue: {
print("Post deleted!")
}

A delete can also be achieved by a type and its id.

Amplify.DataStore.delete(Post.self, withId: "123") {
switch $0 {
case .success:
print("Post deleted!")
case .failure(let error):
print("Error deleting post - \(error.localizedDescription)")
}
}
let sink = Amplify.DataStore.delete(Post.self, withId: "123").sink {
if case let .failure(error) = $0 {
print("Error deleting post - \(error.localizedDescription)")
}
}
receiveValue: {
print("Post deleted!")
}

Query Data

Queries are performed against the local store. When cloud synchronization is enabled, the local store is updated in the background by the DataStore Sync Engine.

For more advanced filtering, such as matching arbitrary field values on an object, you can supply a query predicate.

Amplify.DataStore.query(Post.self) {
switch $0 {
case .success(let result):
// result will be of type [Post]
print("Posts: \(result)")
case .failure(let error):
print("Error on query() for type Post - \(error.localizedDescription)")
}
}
let sink = Amplify.DataStore.query(Post.self).sink {
if case let .failure(error) = $0 {
print("Error on query() for type Post - \(error.localizedDescription)")
}
}
receiveValue: { result in
print("Posts: \(result)")
}

Query by id

Query has an alternative signature that allows to fetch a single item by its id:

Amplify.DataStore.query(Post.self, byId: "123") {
switch $0 {
case .success(let result):
// result will be a single object of type Post?
print("Posts: \(result)")
case .failure(let error):
print("Error on query() for type Post - \(error.localizedDescription)")
}
}
let sink = Amplify.DataStore.query(Post.self, byId: "123").sink {
if case let .failure(error) = $0 {
print("Error on query() for type Post - \(error.localizedDescription)")
}
}
receiveValue: { result in
print("Posts: \(result)")
}

Predicates

Predicates are filters that can be used to match items in the DataStore. When applied to a query(), they constrain the returned results. When applied to a save(), they act as a pre-requisite for updating the data. You can match against fields in your schema by using the following predicates:

Strings: eq | ne | le | lt | ge | gt | contains | notContains | beginsWith | between

Numbers: eq | ne | le | lt | ge | gt | between

Lists: contains | notContains

For example if you wanted a list of all Post Models that have a rating greater than 4:

let p = Post.keys
Amplify.DataStore.query(Post.self, where: p.rating > 4) {
switch $0 {
case .success(let result):
print("Posts: \(result)")
case .failure(let error):
print("Error listing posts - \(error.localizedDescription)")
}
}
let p = Post.keys
let sink = Amplify.DataStore.query(Post.self, where: p.rating > 4).sink {
if case let .failure(error) = $0 {
print("Error listing posts - \(error.localizedDescription)")
}
}
receiveValue: { result in
print("Posts: \(result)")
}

Multiple conditions can also be used, like the ones defined in GraphQL Transform condition statements. For example, fetch all posts that have a rating greater than 4 and are ACTIVE:

let p = Post.keys
Amplify.DataStore.query(Post.self,
where: p.rating > 4 && p.status == PostStatus.active) {
switch $0 {
case .success(let result):
// result of type [Post]
print("Published posts with rating greater than 4: \(result)")
case .failure(let error):
print("Error listing posts - \(error.localizedDescription)")
}
}
let p = Post.keys
let sink = Amplify.DataStore.query(
Post.self,
where: p.rating > 4 && p.status == PostStatus.active
).sink {
if case let .failure(error) = $0 {
print("Error listing posts - \(error.localizedDescription)")
}
}
receiveValue: { result in
print("Published posts with rating greater than 4: \(result)")
}

You can also write this in a compositional function manner by replacing the operators with their equivalent predicate statements such as .gt, .and, etc:

let p = Post.keys
Amplify.DataStore.query(Post.self,
where: p.rating.gt(4).and(p.status.eq(PostStatus.active))) {
// handle the callback like in the previous example
}
let p = Post.keys
let sink = Amplify.DataStore.query(
Post.self,
where: p.rating > 4 && p.status == PostStatus.active
).sink {
// handle the callback like in the previous example
}

Alternatively, the or logical operator can also be used:

let p = Post.keys
Amplify.DataStore.query(Post.self,
where: { p.rating == nil || p.status == PostStatus.active }) {
switch $0 {
case .success(let result):
// result of type [Post]
print("Posts in draft or without rating: \(result)")
case .failure(let error):
print("Error listing posts - \(error.localizedDescription)")
}
}
let p = Post.keys
let sink = Amplify.DataStore.query(
Post.self,
where: { p.rating == nil || p.status == PostStatus.active }
).sink {
if case let .failure(error) = $0 {
print("Error listing posts - \(error.localizedDescription)")
}
}
receiveValue: { result in
// result of type [Post]
print("Posts in draft or without rating: \(result)")
}

Sort

Query results can also be sorted by one or more fields.

For example, to sort all Post objects by rating in ascending order:

Amplify.DataStore.query(Post.self, sort: .ascending(Post.keys.rating)) {
switch $0 {
case .success(let result):
print("Posts: \(result)")
case .failure(let error):
print("Error listing posts - \(error.localizedDescription)")
}
}
let sink = Amplify.DataStore.query(Post.self, sort: .ascending(Post.keys.rating)).sink {
if case let .failure(error) = $0 {
print("Error listing posts - \(error.localizedDescription)")
}
}
receiveValue: { result in
print("Posts: \(result)")
}

To get all Post objects sorted first by rating in ascending order, and then by title in descending order:

Amplify.DataStore.query(Post.self, sort: .by(.ascending(Post.keys.rating),
.descending(Post.keys.title))) {
switch $0 {
case .success(let result):
print("Posts: \(result)")
case .failure(let error):
print("Error listing posts - \(error.localizedDescription)")
}
}
let sink = Amplify.DataStore.query(Post.self, sort: .by(.ascending(Post.keys.rating),
.descending(Post.keys.title))).sink {
if case let .failure(error) = $0 {
print("Error listing posts - \(error.localizedDescription)")
}
}
receiveValue: { result in
print("Posts: \(result)")
}

Pagination

Query results can also be paginated by passing in a page number (starting at 0) and an optional limit (defaults to 100). This will return a list of the first 100 items:

Amplify.DataStore.query(Post.self, paginate: .page(0, limit: 100)) {
// handle result
}
let sink = Amplify.DataStore.query(Post.self, paginate: .page(0, limit: 100)).sink {
// handle result
}

The paginate arguments takes an object of type QueryPaginationInput. That object can be created with the following factory functions:

Query, then observe changes

To both query and observe subsequent changes to a Model, consider using observeQuery.