Page updated Jan 16, 2024

Manipulating data

Create and update

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

1Post post = Post.builder()
2 .title("My First Post")
3 .status(PostStatus.ACTIVE)
4 .rating(10)
5 .build();
6
7Amplify.DataStore.save(post,
8 saved -> Log.i("MyAmplifyApp", "Saved a post."),
9 failure -> Log.e("MyAmplifyApp", "Save failed.", failure)
10);
1val post = Post.builder()
2 .title("My First Post")
3 .status(PostStatus.ACTIVE)
4 .rating(10)
5 .build()
6
7Amplify.DataStore.save(post,
8 { Log.i("MyAmplifyApp", "Saved a post") },
9 { Log.e("MyAmplifyApp", "Save failed", it) }
10)
1val post = Post.builder()
2 .title("My First Post")
3 .status(PostStatus.ACTIVE)
4 .rating(10)
5 .build()
6try {
7 Amplify.DataStore.save(post)
8 Log.i("MyAmplifyApp", "Saved a post.")
9} catch (error: DataStoreException) {
10 Log.e("MyAmplifyApp", "Save failed.", error)
11}
1Post post = Post.builder()
2 .title("My First Post")
3 .status(PostStatus.ACTIVE)
4 .rating(10)
5 .build();
6
7RxAmplify.DataStore.save(post)
8 .subscribe(
9 () -> Log.i("MyAmplifyApp", "Saved a post."),
10 failure -> Log.e("MyAmplifyApp", "Save failed.", failure)
11 );

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

1QueryOptions queryOptions = null;
2try {
3 queryOptions = Where.identifier(Post.class, "123");
4} catch (AmplifyException e) {
5 Log.e("MyAmplifyApp", "Failed to construct QueryOptions with provided values for Where.identifier", e);
6}
7
8if (queryOptions != null) {
9 Amplify.DataStore.query(Post.class, queryOptions,
10 matches -> {
11 if (matches.hasNext()) {
12 Post original = matches.next();
13 Post edited = original.copyOfBuilder()
14 .title("New Title")
15 .build();
16 Amplify.DataStore.save(edited,
17 updated -> Log.i("MyAmplifyApp", "Updated a post."),
18 failure -> Log.e("MyAmplifyApp", "Update failed.", failure)
19 );
20 }
21 },
22 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
23 );
24}
1Amplify.DataStore.query(Post::class.java, Where.identifier(Post::class.java, "123"),
2 { matches ->
3 if (matches.hasNext()) {
4 val original = matches.next()
5 val edited = original.copyOfBuilder()
6 .title("New Title")
7 .build()
8 Amplify.DataStore.save(edited,
9 { Log.i("MyAmplifyApp", "Updated a post") },
10 { Log.e("MyAmplifyApp", "Update failed", it) }
11 )
12 }
13 },
14 { Log.e("MyAmplifyApp", "Query failed", it) }
15)
1Amplify.DataStore.query(Post::class, Where.identifier(Post::class.java, "123"))
2 .catch { Log.e("MyAmplifyApp", "Query failed", it) }
3 .map { it.copyOfBuilder().title("New Title").build() }
4 .onEach { Amplify.DataStore.save(it) }
5 .catch { Log.e("MyAmplifyApp", "Update failed", it) }
6 .collect { Log.i("MyAmplifyApp", "Updated a post") }
1QueryOptions queryOptions = null;
2try {
3 queryOptions = Where.identifier(Post.class, "123");
4} catch (AmplifyException e) {
5 Log.e("MyAmplifyApp", "Failed to construct QueryOptions with provided values for Where.identifier", e);
6}
7
8if (queryOptions != null) {
9 RxAmplify.DataStore.query(Post.class, queryOptions)
10 .map(matchingPost -> matchingPost.copyOfBuilder()
11 .title("New Title")
12 .build()
13 )
14 .flatMapCompletable(RxAmplify.DataStore::save)
15 .subscribe(
16 () -> Log.i("MyAmplifyApp", "Query and update succeeded."),
17 failure -> Log.e("MyAmplifyApp", "Update failed.", failure)
18 );
19}

Models in DataStore are immutable. Instead of directly modifying the fields on a Model, you must use the .copyOfBuilder() method to create a new representation of the model.

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.

1public class ObserveExample {
2
3 String tag = "ObserveQuery";
4 private Post post;
5
6 public void observeExample() {
7 Consumer<DataStoreQuerySnapshot<Post>> onQuerySnapshot = value ->{
8 System.out.println("value: " + value.getItems());
9 if (value.getItems().size() > 0) {
10 post = value.getItems().get(0);
11 }
12 };
13 Consumer<Cancelable> observationStarted = value ->{
14 Log.d(tag, "success on cancelable");
15 };
16 Consumer<DataStoreException> onObservationError = value ->{
17 Log.d(tag, "error on snapshot$value");
18 };
19 Action onObservationComplete = () ->{
20 Log.d(tag, "complete");
21 };
22
23 ObserveQueryOptions options = new ObserveQueryOptions();
24
25 Amplify.DataStore.<Post>observeQuery(
26 Post.class,
27 options,
28 observationStarted,
29 onQuerySnapshot,
30 onObservationError,
31 onObservationComplete
32 );
33 }
34
35 public void save() {
36 // called from UI event, for example:
37 Amplify.DataStore.save(
38 post.copyOfBuilder()
39 .title("new post")
40 .build(),
41 updated -> Log.i(tag, "updated post"),
42 failure -> Log.e(tag, "update failed", failure)
43 );
44 }
45}
1class ObserveExampleKt {
2
3 val tag = "ObserveQuery"
4 var post: Post? = null
5
6 fun observeExample() {
7 val onQuerySnapshot: Consumer<DataStoreQuerySnapshot<Post>> =
8 Consumer<DataStoreQuerySnapshot<Post>> { value: DataStoreQuerySnapshot<Post> ->
9 Log.d(tag, "Post updated")
10 post = value.items[0]
11 }
12 val observationStarted =
13 Consumer { _: Cancelable ->
14 Log.d(tag, "success on cancelable")
15 }
16 val onObservationError =
17 Consumer { value: DataStoreException ->
18 Log.d(tag, "error on snapshot $value")
19 }
20 val onObservationComplete = Action {
21 Log.d(tag, "complete")
22 }
23
24 val options = ObserveQueryOptions()
25 Amplify.DataStore.observeQuery(
26 Post::class.java,
27 options,
28 observationStarted,
29 onQuerySnapshot,
30 onObservationError,
31 onObservationComplete
32 )
33 }
34
35 fun save() {
36 // called from a UI event, for example:
37 post?.let {
38 Amplify.DataStore.save(it.copyOfBuilder()
39 .title("new title")
40 .build(),
41 { Log.i(tag, "Post saved") },
42 { Log.i(tag, "Error saving post") }
43 )
44 }
45 }
46}
1// For coroutines, remember to `import com.amplifyframework.kotlin.core.Amplify`
2// instead of `import com.amplifyframework.core.Amplify`
3// and add "implementation 'com.amplifyframework:core-kotlin:0.20.0'" to gradle dependencies
4
5class ObserveExampleCoroutines {
6
7 var post: Post? = null
8
9 fun observeExample(lifecycleScope: LifecycleCoroutineScope) {
10 lifecycleScope.async {
11 Amplify.DataStore.observe(
12 Post::class
13 ).onStart { Log.i("MyAmplifyApp", "Observation began") }
14 .catch { Log.e("MyAmplifyApp", "Observation failed", it) }
15 .onCompletion { Log.i("MyAmplifyApp", "Observation complete") }
16 .collect {
17 post = it.item()
18 Log.i("MyAmplifyApp", "Received post: $it")
19 }
20 }
21 }
22
23 fun save(lifecycleScope: LifecycleCoroutineScope) {
24 lifecycleScope.async {
25 post?.let {
26 Amplify.DataStore.save(it)
27 Log.i("MyAmplifyApp", "Post saved: $it")
28 }
29 }
30 }
31}
1// Remember to import import com.amplifyframework.rx.RxAmplify;
2// and add "implementation 'com.amplifyframework:rxbindings:1.36.1'" to gradle dependencies
3
4public class ObserveExampleRx {
5
6 private Post post;
7
8 public void observeExample() {
9 RxAmplify.DataStore.observe(Post.class)
10 .subscribe(
11 data -> {
12 post = data.item();
13 Log.d("MyAmplifyApp", "Received post: " + post);
14 },
15 failure -> Log.d("MyAmplifyApp", "Observe failed"),
16 () -> Log.d("", "Observe complete")
17 );
18 }
19
20 public void save() {
21 RxAmplify.DataStore.save(post.copyOfBuilder().title("New title").build())
22 .subscribe(
23 () -> Log.d("MyAmplifyApp", "Save succeeded"),
24 failure -> Log.d("MyAmplifyApp", "Save failed: " + failure)
25 );
26 }
27}

Delete

To delete an item, simply pass in an instance.

Below, you query for an instance with an id of "123", and then delete it, if found:

1QueryOptions queryOptions = null;
2try {
3 queryOptions = Where.identifier(Post.class, "123");
4} catch (AmplifyException e) {
5 Log.e("MyAmplifyApp", "Failed to construct QueryOptions with provided values for Where.identifier", e);
6}
7
8if (queryOptions != null) {
9 Amplify.DataStore.query(Post.class, queryOptions,
10 matches -> {
11 if (matches.hasNext()) {
12 Post post = matches.next();
13 Amplify.DataStore.delete(post,
14 deleted -> Log.i("MyAmplifyApp", "Deleted a post."),
15 failure -> Log.e("MyAmplifyApp", "Delete failed.", failure)
16 );
17 }
18 },
19 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
20 );
21}
1Amplify.DataStore.query(Post::class.java, Where.identifier(Post::class.java, "123"),
2 { matches ->
3 if (matches.hasNext()) {
4 val post = matches.next()
5 Amplify.DataStore.delete(post,
6 { Log.i("MyAmplifyApp", "Deleted a post.") },
7 { Log.e("MyAmplifyApp", "Delete failed.", it) }
8 )
9 }
10 },
11 { Log.e("MyAmplifyApp", "Query failed.", it) }
12)
1Amplify.DataStore.query(Post::class, Where.identifier(Post::class.java, "123"))
2 .catch { Log.e("MyAmplifyApp", "Query failed", it) }
3 .onEach { Amplify.DataStore.delete(it) }
4 .catch { Log.e("MyAmplifyApp", "Delete failed", it) }
5 .collect { Log.i("MyAmplifyApp", "Deleted a post") }
1QueryOptions queryOptions = null;
2try {
3 queryOptions = Where.identifier(Post.class, "123");
4} catch (AmplifyException e) {
5 Log.e("MyAmplifyApp", "Failed to construct QueryOptions with provided values for Where.identifier", e);
6}
7
8if (queryOptions != null) {
9 RxAmplify.DataStore.query(Post.class, queryOptions)
10 .flatMapCompletable(RxAmplify.DataStore::delete)
11 .subscribe(
12 () -> Log.i("MyAmplifyApp", "Deleted a post."),
13 failure -> Log.e("MyAmplifyApp", "Delete failed.", failure)
14 );
15}

To conditionally delete an instance, pass an instance and a predicate into Amplify.DataStore.delete(). In the example below, posts are deleted only if their rating is greater than 4:

1Amplify.DataStore.query(Post.class,
2 matches -> {
3 if (matches.hasNext()) {
4 Post post = matches.next();
5 Amplify.DataStore.delete(post, Where.matches(Post.RATING.gt(4)).getQueryPredicate(),
6 deleted -> Log.i("MyAmplifyApp", "Deleted a post."),
7 failure -> Log.e("MyAmplifyApp", "Delete failed.", failure)
8 );
9 }
10 },
11 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
12);
1Amplify.DataStore.query(Post::class.java,
2 { matches ->
3 if (matches.hasNext()) {
4 val post = matches.next()
5 Amplify.DataStore.delete(post, Where.matches(Post.RATING.gt(4)).queryPredicate,
6 { Log.i("MyAmplifyApp", "Deleted a post.") },
7 { Log.e("MyAmplifyApp", "Delete failed.", it) }
8 )
9 }
10 },
11 { Log.e("MyAmplifyApp", "Query failed.", it) }
12)
1Amplify.DataStore.query(Post::class)
2 .catch { Log.e("MyAmplifyApp", "Query failed", it) }
3 .onEach { Amplify.DataStore.delete(it, Where.matches(Post.RATING.gt(4)).queryPredicate) }
4 .catch { Log.e("MyAmplifyApp", "Delete failed", it) }
5 .collect { Log.i("MyAmplifyApp", "Deleted a post") }
1RxAmplify.DataStore.query(
2 Post.class,
3 Where.matches(Post.RATING.gt(4)))
4 .subscribe(
5 post -> RxAmplify.DataStore.delete(post, Where.matches(Post.RATING.gt(4)).getQueryPredicate())
6 .subscribe(
7 () -> Log.i("MyAmplifyApp", "Deleted a post."),
8 failure -> Log.e("MyAmplifyApp", "Delete failed.", failure)
9 ),
10 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
11 );

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.class,
2 allPosts -> {
3 while (allPosts.hasNext()) {
4 Post post = allPosts.next();
5 Log.i("MyAmplifyApp", "Title: " + post.getTitle());
6 }
7 },
8 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
9);
1Amplify.DataStore.query(Post::class.java,
2 { allPosts ->
3 while (allPosts.hasNext()) {
4 val post = allPosts.next()
5 Log.i("MyAmplifyApp", "Title: ${post.title}")
6 }
7 },
8 { Log.e("MyAmplifyApp", "Query failed", it) }
9)
1Amplify.DataStore.query(Post::class)
2 .catch { Log.e("MyAmplifyApp", "Query failed", it) }
3 .collect { Log.i("MyAmplifyApp", "Title: ${it.title}") }
1RxAmplify.DataStore.query(Post.class).subscribe(
2 post -> Log.i("MyAmplifyApp", "Title: " + post.getTitle()),
3 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
4);

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:

1Amplify.DataStore.query(Post.class, Where.matches(Post.RATING.gt(4)),
2 goodPosts -> {
3 while (goodPosts.hasNext()) {
4 Post post = goodPosts.next();
5 Log.i("MyAmplifyApp", "Post: " + post);
6 }
7 },
8 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
9);
1Amplify.DataStore.query(Post::class.java,
2 Where.matches(Post.RATING.gt(4)),
3 { goodPosts ->
4 while (goodPosts.hasNext()) {
5 val post = goodPosts.next()
6 Log.i("MyAmplifyApp", "Post: $post")
7 }
8 },
9 { Log.e("MyAmplifyApp", "Query failed", it) }
10)
1Amplify.DataStore
2 .query(Post::class, Where.matches(Post.RATING.gt(4)))
3 .catch { Log.e("MyAmplifyApp", "Query failed", it) }
4 .collect { Log.i("MyAmplifyApp", "Post: $it") }
1RxAmplify.DataStore.query(
2 Post.class,
3 Where.matches(Post.RATING.gt(4)))
4 .subscribe(
5 post -> Log.i("MyAmplifyApp", "Post: " + post),
6 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
7 );

Note: when constructing predicates, static QueryField instances such as Post.RATING do not own any information about the model to which the field belongs. In order to avoid any ambiguity between field names which are used across multiple models, it is recommended to construct a custom instance of QueryField in the form of QueryField.field("{model-name}.{field-name}") (i.e. field("post.rating")).

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:

1Amplify.DataStore.query(
2 Post.class,
3 Where.matches(Post.RATING.gt(4).and(Post.STATUS.eq(PostStatus.ACTIVE))),
4 goodActivePosts -> {
5 while (goodActivePosts.hasNext()) {
6 Post post = goodActivePosts.next();
7 Log.i("MyAmplifyApp", "Post: " + post);
8 }
9 },
10 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
11);
1Amplify.DataStore.query(
2 Post.class, Where.matches(Post.RATING.gt(4)
3 .and(Post.STATUS.eq(PostStatus.ACTIVE))
4 ),
5 { goodActivePosts ->
6 while (goodActivePosts.hasNext()) {
7 val post = goodActivePosts.next()
8 Log.i("MyAmplifyApp", "Post: $post ")
9 }
10 },
11 { Log.e("MyAmplifyApp", "Query failed", it) }
12)
1Amplify.DataStore
2 .query(Post::class,
3 Where.matches(Post.RATING.gt(4)
4 .and(Post.STATUS.eq(PostStatus.ACTIVE)))
5 )
6 .catch { Log.e("MyAmplifyApp", "Query failed", it) }
7 .collect { Log.i("MyAmplifyApp", "Post: $it") }
1RxAmplify.DataStore.query(
2 Post.class,
3 Where.matches(Post.RATING.gt(4).and(Post.STATUS.eq(PostStatus.ACTIVE))))
4 .subscribe(
5 post -> Log.i("MyAmplifyApp", "Post: " + post),
6 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
7 );

Alternatively, the or logical operator can also be used:

1Amplify.DataStore.query(
2 Post.class,
3 Where.matches(Post.RATING.gt(4).or(Post.STATUS.eq(PostStatus.ACTIVE))),
4 posts -> {
5 while (posts.hasNext()) {
6 Post post = posts.next();
7 Log.i("MyAmplifyApp", "Post: " + post);
8 }
9 },
10 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
11);
1Amplify.DataStore.query(
2 Post.class, Where.matches(
3 Post.RATING.gt(4)
4 .or(Post.STATUS.eq(PostStatus.ACTIVE))
5 ),
6 { posts ->
7 while (posts.hasNext()) {
8 val post = posts.next()
9 Log.i("MyAmplifyApp", "Post: $post")
10 }
11 },
12 { Log.e("MyAmplifyApp", "Query failed", it) }
13)
1Amplify.DataStore
2 .query(Post::class,
3 Where.matches(Post.RATING.gt(4)
4 .or(Post.STATUS.eq(PostStatus.ACTIVE)))
5 )
6 .catch { Log.e("MyAmplifyApp", "Query failed", it) }
7 .collect { Log.i("MyAmplifyApp", "Post: $it") }
1RxAmplify.DataStore.query(
2 Post.class,
3 Where.matches(Post.RATING.gt(4).or(Post.STATUS.eq(PostStatus.ACTIVE))))
4 .subscribe(
5 post -> Log.i("MyAmplifyApp", "Post: " + post),
6 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
7 );

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.class,
2 Where.sorted(Post.RATING.ascending()),
3 posts -> {
4 while (posts.hasNext()) {
5 Post post = posts.next();
6 Log.i("MyAmplifyApp", "Title: " + post.getTitle());
7 }
8 },
9 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
10);
1Amplify.DataStore.query(Post::class.java,
2 Where.sorted(Post.RATING.ascending()),
3 { posts ->
4 while (posts.hasNext()) {
5 val post = posts.next()
6 Log.i("MyAmplifyApp", "Title: ${post.title}")
7 }
8 },
9 { Log.e("MyAmplifyApp", "Query failed", it) }
10)
1Amplify.DataStore
2 .query(Post::class, Where.sorted(Post.RATING.ascending()))
3 .catch { Log.e("MyAmplifyApp", "Query failed", it) }
4 .collect { Log.i("MyAmplifyApp", "Title: ${it.title}") }
1RxAmplify.DataStore.query(Post.class,
2 Where.sorted(Post.RATING.ascending())
3 .subscribe(
4 post -> Log.i("MyAmplifyApp", "Post: " + post),
5 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
6 );

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

1Amplify.DataStore.query(Post.class,
2 Where.sorted(Post.RATING.ascending(), Post.TITLE.descending()),
3 posts -> {
4 while (posts.hasNext()) {
5 Post post = posts.next();
6 Log.i("MyAmplifyApp", "Title: " + post.getTitle());
7 }
8 },
9 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
10);
1Amplify.DataStore.query(Post::class.java,
2 Where.sorted(Post.RATING.ascending(), Post.TITLE.descending()),
3 { posts ->
4 while (posts.hasNext()) {
5 val post = posts.next()
6 Log.i("MyAmplifyApp", "Title: ${post.title}")
7 }
8 },
9 { Log.e("MyAmplifyApp", "Query failed", it) }
10)
1Amplify.DataStore
2 .query(Post::class,
3 Where.sorted(Post.RATING.ascending(), Post.TITLE.descending())
4 )
5 .catch { Log.e("MyAmplifyApp", "Query failed", it) }
6 .collect { Log.i("MyAmplifyApp", "Title: ${it.title}") }
1RxAmplify.DataStore.query(Post.class,
2 Where.sorted(Post.RATING.ascending(), Post.TITLE.descending()))
3 .subscribe(
4 post -> Log.i("MyAmplifyApp", "Post: " + post),
5 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
6 );

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.class,
2 Where.matchesAll().paginated(Page.startingAt(0).withLimit(100)),
3 matchingPosts -> {
4 while (matchingPosts.hasNext()) {
5 Post post = matchingPosts.next();
6 Log.i("MyAmplifyApp", "Title: " + post.getTitle());
7 }
8 },
9 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
10);
1Amplify.DataStore.query(Post::class.java,
2 Where.matchesAll().paginated(Page.startingAt(0).withLimit(100)),
3 { posts ->
4 while (posts.hasNext()) {
5 val post = posts.next()
6 Log.i("MyAmplifyApp", "Title: ${post.title}")
7 }
8 },
9 { Log.e("MyAmplifyApp", "Query failed", it) }
10)
1Amplify.DataStore
2 .query(Post::class,
3 Where.matchesAll()
4 .paginated(Page.startingAt(0).withLimit(100))
5 )
6 .catch { Log.e("MyAmplifyApp", "Query failed", it) }
7 .collect { Log.i("MyAmplifyApp", "Title: ${it.title}") }
1RxAmplify.DataStore.query(
2 Post.class,
3 Where.matchesAll().paginated(Page.startingAt(0).withLimit(100)))
4 .subscribe(
5 post -> Log.i("MyAmplifyApp", "Title: " + post.getTitle()),
6 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
7 );

Query, then observe changes

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