Manipulating data
Create and update
To write data to the DataStore, pass an instance of a model to Amplify.DataStore.save()
:
1Amplify.DataStore.save(2 Post(title: "My first post",3 description: "Amplify.DataStore is awesome!",4 status: .draft)5) {6 switch $0 {7 case .success:8 print("Created a new post successfully")9 case .failure(let error):10 print("Error creating post - \(error.localizedDescription)")11 }12}
1let saveSink = Amplify.DataStore.save(2 Post(3 title: "My first post",4 description: "Amplify.DataStore is awesome!",5 status: .draft6 )7)8.sink {9 if case let .failure(error) = $0 {10 print("Error creating post - \(error.localizedDescription)")11 }12}13receiveValue: {14 print("Created a new post successfully: \($0)")15}
The save
method creates a new record, or in the event that one already exists in the local store, it updates the record.
1let existingPost: Post = /* get an existing post */2existingPost.title = "[updated] My first post"3
4Amplify.DataStore.save(existingPost) {5 switch $0 {6 case .success:7 print("Updated the existing post")8 case .failure(let error):9 print("Error updating post - \(error.localizedDescription)")10 }11}
1let existingPost: Post = /* get an existing post */2existingPost.title = "[updated] My first post"3
4let saveSink = Amplify.DataStore.save(existingPost).sink {5 if case let .failure(error) = $0 {6 print("Error updating post - \(error.localizedDescription)")7 }8}9receiveValue: {10 print("Updated the existing post: \($0)")11}
1import SwiftUI2import Amplify3import Combine4
5/*6 Example showing how to observe the model and keep the state updated before performing a save. This uses the7 `@Published` property for views to observe and re-render changes to the model.8 */9class PostViewModel: ObservableObject {10 @Published var post: Post?11 var subscription: AnyCancellable?12 13 init() {14 }15 16 func observe(postId: String) {17 self.subscription = Amplify.DataStore.observeQuery(for: Post.self,18 where: Post.keys.id == postId).sink { completion in19 print("Completion event: \(completion)")20 } receiveValue: { snapshot in21 guard let post = snapshot.items.first else {22 return23 }24 DispatchQueue.main.async {25 self.post = post26 }27 }28 }29 30 func updateTitle(_ title: String) {31 guard var post = post else {32 return33 }34 post.title = title35 Amplify.DataStore.save(post) { result in36 switch result {37 case .success(let updatedPost):38 print("Updated post successfully: \(updatedPost)")39 case .failure(let error):40 print("Failed to update post: \(error)")41 }42 }43 }44}45struct PostView: View {46 @StateObject var vm = PostViewModel()47 @State private var title = ""48 let postId: String49 50 init(postId: String) {51 self.postId = postId52 }53 54 var body: some View {55 VStack {56 Text("Post's current title: \(vm.post?.title ?? "")")57 TextField("Enter new title", text: $title)58 Button("Click to update the title to '\(title)'") {59 vm.updateTitle(title)60 }61 }.onAppear(perform: {62 vm.observe(postId: postId)63 })64 }65}
Delete
To delete an item, simply pass in an instance.
1Amplify.DataStore.delete(post) {2 switch $0 {3 case .success:4 print("Post deleted!")5 case .failure(let error):6 print("Error deleting post - \(error.localizedDescription)")7 }8}
1let sink = Amplify.DataStore.delete(post).sink {2 if case let .failure(error) = $0 {3 print("Fetch session failed with error \(error)")4 }5}6receiveValue: {7 print("Post deleted!")8}
A delete can also be achieved by a type
and its id
.
1Amplify.DataStore.delete(Post.self, withId: "123") {2 switch $0 {3 case .success:4 print("Post deleted!")5 case .failure(let error):6 print("Error deleting post - \(error.localizedDescription)")7 }8}
1let sink = Amplify.DataStore.delete(Post.self, withId: "123").sink {2 if case let .failure(error) = $0 {3 print("Error deleting post - \(error.localizedDescription)")4 }5}6receiveValue: {7 print("Post deleted!")8}
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.
1Amplify.DataStore.query(Post.self) {2 switch $0 {3 case .success(let result):4 // result will be of type [Post]5 print("Posts: \(result)")6 case .failure(let error):7 print("Error on query() for type Post - \(error.localizedDescription)")8 }9}
1let sink = Amplify.DataStore.query(Post.self).sink {2 if case let .failure(error) = $0 {3 print("Error on query() for type Post - \(error.localizedDescription)")4 }5}6receiveValue: { result in7 print("Posts: \(result)")8}
Query by id
Query has an alternative signature that allows to fetch a single item by its id
:
1Amplify.DataStore.query(Post.self, byId: "123") {2 switch $0 {3 case .success(let result):4 // result will be a single object of type Post?5 print("Posts: \(result)")6 case .failure(let error):7 print("Error on query() for type Post - \(error.localizedDescription)")8 }9}
1let sink = Amplify.DataStore.query(Post.self, byId: "123").sink {2 if case let .failure(error) = $0 {3 print("Error on query() for type Post - \(error.localizedDescription)")4 }5}6receiveValue: { result in7 print("Posts: \(result)")8}
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:
1let p = Post.keys2Amplify.DataStore.query(Post.self, where: p.rating > 4) {3 switch $0 {4 case .success(let result):5 print("Posts: \(result)")6 case .failure(let error):7 print("Error listing posts - \(error.localizedDescription)")8 }9}
1let p = Post.keys2let sink = Amplify.DataStore.query(Post.self, where: p.rating > 4).sink {3 if case let .failure(error) = $0 {4 print("Error listing posts - \(error.localizedDescription)")5 }6}7receiveValue: { result in8 print("Posts: \(result)")9}
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
:
1let p = Post.keys2Amplify.DataStore.query(Post.self,3 where: p.rating > 4 && p.status == PostStatus.active) {4 switch $0 {5 case .success(let result):6 // result of type [Post]7 print("Published posts with rating greater than 4: \(result)")8 case .failure(let error):9 print("Error listing posts - \(error.localizedDescription)")10 }11}
1let p = Post.keys2let sink = Amplify.DataStore.query(3 Post.self,4 where: p.rating > 4 && p.status == PostStatus.active5).sink {6 if case let .failure(error) = $0 {7 print("Error listing posts - \(error.localizedDescription)")8 }9}10receiveValue: { result in11 print("Published posts with rating greater than 4: \(result)")12}
You can also write this in a compositional function manner by replacing the operators with their equivalent predicate statements such as .gt
, .and
, etc:
1let p = Post.keys2Amplify.DataStore.query(Post.self,3 where: p.rating.gt(4).and(p.status.eq(PostStatus.active))) {4 // handle the callback like in the previous example5}
1let p = Post.keys2let sink = Amplify.DataStore.query(3 Post.self,4 where: p.rating > 4 && p.status == PostStatus.active5).sink {6 // handle the callback like in the previous example7}
Alternatively, the or
logical operator can also be used:
1let p = Post.keys2Amplify.DataStore.query(Post.self,3 where: { p.rating == nil || p.status == PostStatus.active }) {4 switch $0 {5 case .success(let result):6 // result of type [Post]7 print("Posts in draft or without rating: \(result)")8 case .failure(let error):9 print("Error listing posts - \(error.localizedDescription)")10 }11}
1let p = Post.keys2let sink = Amplify.DataStore.query(3 Post.self,4 where: { p.rating == nil || p.status == PostStatus.active }5).sink {6 if case let .failure(error) = $0 {7 print("Error listing posts - \(error.localizedDescription)")8 }9}10receiveValue: { result in11 // result of type [Post]12 print("Posts in draft or without rating: \(result)")13}
Sort
Query results can also be sorted by one or more fields.
For example, to sort all Post
objects by rating
in ascending order:
1Amplify.DataStore.query(Post.self, sort: .ascending(Post.keys.rating)) {2 switch $0 {3 case .success(let result):4 print("Posts: \(result)")5 case .failure(let error):6 print("Error listing posts - \(error.localizedDescription)")7 }8}
1let sink = Amplify.DataStore.query(Post.self, sort: .ascending(Post.keys.rating)).sink {2 if case let .failure(error) = $0 {3 print("Error listing posts - \(error.localizedDescription)")4 }5}6receiveValue: { result in7 print("Posts: \(result)")8}
To get all Post
objects sorted first by rating
in ascending order, and then by title
in descending order:
1Amplify.DataStore.query(Post.self, sort: .by(.ascending(Post.keys.rating),2 .descending(Post.keys.title))) {3 switch $0 {4 case .success(let result):5 print("Posts: \(result)")6 case .failure(let error):7 print("Error listing posts - \(error.localizedDescription)")8 }9}
1let sink = Amplify.DataStore.query(Post.self, sort: .by(.ascending(Post.keys.rating),2 .descending(Post.keys.title))).sink {3 if case let .failure(error) = $0 {4 print("Error listing posts - \(error.localizedDescription)")5 }6}7receiveValue: { result in8 print("Posts: \(result)")9}
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:
1Amplify.DataStore.query(Post.self, paginate: .page(0, limit: 100)) {2 // handle result3}
1let sink = Amplify.DataStore.query(Post.self, paginate: .page(0, limit: 100)).sink {2 // handle result3}
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)