Manipulating data
Create and update
To write data to the DataStore, pass an instance of a model to Amplify.DataStore.save()
:
do { try await Amplify.DataStore.save( Post(title: "My first post", status: .active, content: "Amplify.DataStore is awesome!") ) print("Created a new post successfully")} catch let error as DataStoreError { print("Error creating post - \(error)")} catch { print("Unexpected error \(error)")}
let saveSink = Amplify.Publisher.create { try await Amplify.DataStore.save( Post(title: "My first post", status: .active, content: "Amplify.DataStore is awesome!") )}.sink { if case let .failure(error) = $0 { print("Error updating post - \(error.localizedDescription)") } } receiveValue: { print("Updated the existing post: \($0)") }
The save
method creates a new record, or in the event that one already exists in the local store, it updates the record.
var existingPost: Post = /* get an existing post */existingPost.title = "[updated] My first post"
do { try await Amplify.DataStore.save(existingPost) print("Updated the existing post")} catch let error as DataStoreError { print("Error updating post - \(error)")} catch { print("Unexpected error \(error)")}
var existingPost: Post = /* get an existing post */existingPost.title = "[updated] My first post"let postForUpdate = existingPostlet saveSink = Amplify.Publisher.create { try await Amplify.DataStore.save(existingPost)}.sink { if case let .failure(error) = $0 { print("Error updating post - \(error)") }}receiveValue: { print("Updated the existing post: \($0)")}
import SwiftUIimport Amplifyimport 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.Publisher.create( 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) async { guard var post = post else { return } post.title = title do { let updatedPost = try await Amplify.DataStore.save(post) print("Updated post successfully: \(updatedPost)") } catch let error as DataStoreError { print("Failed to update post: \(error)") } catch { print("Unexpected error \(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)'") { Task { await vm.updateTitle(title) } } }.onAppear(perform: { Task { await vm.observe(postId: postId) } }) }}
Delete
To delete an item, simply pass in an instance.
do { try await Amplify.DataStore.delete(post) print("Post deleted!")} catch let error as DataStoreError { print("Error deleting post - \(error)")} catch { print("Unexpected error \(error)")}
let sink = Amplify.Publisher.create { try await 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
.
do { try await Amplify.DataStore.delete(Post.self, withId: "123") print("Post deleted!")} catch let error as DataStoreError { print("Error deleting post - \(error)")} catch { print("Unexpected error \(error)")}
let sink = Amplify.Publisher.create { try await Amplify.DataStore.delete(Post.self, withId: "123")}.sink { if case let .failure(error) = $0 { print("Error deleting post - \(error)") }}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.
do { let result = try await Amplify.DataStore.query(Post.self) // result will be of type [Post] print("Posts: \(result)")} catch let error as DataStoreError { print("Error on query() for type Post - \(error)")} catch { print("Unexpected error \(error)")}
let sink = Amplify.Publisher.create { try await Amplify.DataStore.query(Post.self)}.sink { if case let .failure(error) = $0 { print("Error on query() for type Post - \(error)") }}receiveValue: { result in print("Posts: \(result)")}
Query by identifier
Query has an alternative signature that allows to fetch a single item by its id
:
do { let result = try await Amplify.DataStore.query(Post.self, byId: "123") // result will be a single object of type Post? print("Post: \(result)")} catch { print("Error on query() for type Post - \(error)")}
let sink = Amplify.Publisher.create { try await Amplify.DataStore.query(Post.self, byId: "123")}.sink { if case let .failure(error) = $0 { print("Error on query() for type Post - \(error)") }}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.keysdo { let result = try await Amplify.DataStore.query(Post.self, where: p.rating > 4) print("Posts: \(result)")} catch let error as DataStoreError { print("Error listing posts - \(error)")} catch { print("Unexpected error \(error)")}
let p = Post.keyslet sink = Amplify.Publisher.create { try await Amplify.DataStore.query(Post.self, where: p.rating > 4)}.sink { if case let .failure(error) = $0 { print("Error listing posts - \(error)") }}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.keysdo { let result = try await Amplify.DataStore.query( Post.self, where: p.rating > 4 && p.status == PostStatus.active ) // result of type [Post] print("Published posts with rating greater than 4: \(result)")} catch let error as DataStoreError { print("Error listing posts - \(error)")} catch { print("Unexpected error \(error)")}
let p = Post.keyslet sink = Amplify.Publisher.create { try await 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)") }}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.keystry await Amplify.DataStore.query( Post.self, where: p.rating.gt(4).and(p.status.eq(PostStatus.active)))
let p = Post.keyslet sink = Amplify.Publisher.create { try await Amplify.DataStore.query( Post.self, where: p.rating > 4 && p.status == PostStatus.active )}.sink { // handle result}
Alternatively, the or
logical operator can also be used:
let p = Post.keysdo { let result = try await Amplify.DataStore.query( Post.self, where: p.rating == nil || p.status == PostStatus.active ) // result of type [Post] print("Posts in draft or without rating: \(result)")} catch let error as DataStoreError { print("Error listing posts - \(error)")} catch { print("Unexpected error \(error)")}
let p = Post.keyslet sink = Amplify.Publisher.create { try await 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)") }}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:
do { let result = try await Amplify.DataStore.query( Post.self, sort: .ascending(Post.keys.rating)) print("Posts: \(result)")} catch let error as DataStoreError { print("Error listing posts - \(error)")} catch { print("Unexpected error \(error)")}
let sink = Amplify.Publisher.create { try await Amplify.DataStore.query( Post.self, sort: .ascending(Post.keys.rating))}.sink { if case let .failure(error) = $0 { print("Error listing posts - \(error)") }}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:
do { let result = try await Amplify.DataStore.query( Post.self, sort: .by( .ascending(Post.keys.rating), .descending(Post.keys.title) ) ) print("Posts: \(result)")} catch let error as DataStoreError { print("Failed with error \(error)")} catch { print("Error listing posts - \(error)")}
let sink = Amplify.Publisher.create { try await 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)") }}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:
let posts = try await Amplify.DataStore.query( Post.self, paginate: .page(0, limit: 100))
let sink = Amplify.Publisher.create { try await 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:
.page(_ page: UInt, limit: UInt)
: the page number (starting at0
) and the page size, defined bylimit
(defaults to100
).firstPage
: an idiomatic shortcut to.page(0, limit: 100)
.firstResult
: an idiomatic shortcut to.page(0, limit: 1)