Flutter

Parse Relational Query in Flutter

Introduction

You’ve already seen how to store relational data objects using theParse.Pointer and Parse.Relations data types. You’ve learned that on Parse it is possible to use any ParseObject as a value in other ParseObject, establishing a relation between them. Internally, the Parse framework will store the referred-to object in just one place to maintain consistency. That can give you extra power when building and running complex queries.

Also, you’ve already learned how to use a QueryBuilder with get can retrieve a single ParseObject from Back4App. There are many other ways to retrieve data with QueryBuilder.

In this guide, you will ding deep into the QueryBuilder class and see methods you can use to build Relational Queries. You will use a simple database class with some mocked data to perform the Queries using Flutter on Back4App.

Prerequisites

To complete this tutorial, you will need:

Goal

Query relational data stored on Back4App from a Flutter App.

Step 1 - Understanding the QueryBuilder class

Any Parse query operation uses the QueryBuilder object type, which will help you retrieve specific data from your database throughout your app.

To create a new QueryBuilder, you need to pass as a parameter the desired ParseObject subclass, which is the one that will contain your query results.

It is crucial to know that a QueryBuilder will only resolve after calling a retrieve method query, so a query can be set up and several modifiers can be chained before actually being called.

1
2
3
4
5
6
7
8
9
10
11
12
	// This will create your query
   QueryBuilder<ParseObject> queryBook =
        QueryBuilder<ParseObject>(ParseObject('Book'));
        
   	// The query will resolve only after calling this method
   final ParseResponse apiResponse = await queryBook.query();
	
    if (apiResponse.success && apiResponse.results != null) {
      ....
    } else {
	   ...
    }

You can read more about the QueryBuilder class here at the official documentation.

Using the JavaScript Console on Back4App

Inside your Back4App application’s dashboard, you will find a very useful API console in which you can run JavaScript code directly. In this guide you will use to store data objects in Back4App. On your App main dashboard go to Core->API Console->Javascript.

Flutter Back4App

Step 2 - Save Data on Back4app

To run the queries on this guide you’ll need first to populate your App with some data.

The classes are: Author, Book, Publisher and BookStore.
Which Book has a 1:N relation with Publisher and N:N with Author, and BookStore has an N:N relation with Book.

Here is the Parse.Object classes creation code, so go ahead and run it in your API console:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// Add objects and create tables
// Authors
const AuthorA = new Parse.Object('Author');
AuthorA.set('name', 'Aaron Writer');
await AuthorA.save();

const AuthorB = new Parse.Object('Author');
AuthorB.set('name', 'Beatrice Novelist');
await AuthorB.save();

const AuthorC = new Parse.Object('Author');
AuthorC.set('name', 'Casey Columnist');
await AuthorC.save();

const AuthorD = new Parse.Object('Author');
AuthorD.set('name', 'Gary Stur');
await AuthorD.save();

const AuthorE = new Parse.Object('Author');
AuthorE.set('name', 'Mary Sue');
await AuthorE.save();

// Publishers
const PublisherA = new Parse.Object('Publisher');
PublisherA.set('name', 'Acacia Publishings');
await PublisherA.save();

const PublisherB = new Parse.Object('Publisher');
PublisherB.set('name', 'Birch Distributions');
await PublisherB.save();

const PublisherC = new Parse.Object('Publisher');
PublisherC.set('name', 'Acacia Distributions');
await PublisherC.save();


// Books
const BookA = new Parse.Object('Book');
BookA.set('title', 'A Love Story');
BookA.set('publisher', PublisherA);
BookA.set('publishingDate', new Date('05/07/1998'));
const BookARelation = BookA.relation("authors");
BookARelation.add(AuthorA);
await BookA.save();

const BookB = new Parse.Object('Book');
BookB.set('title', 'Benevolent Elves');
BookB.set('publisher', PublisherB);
BookB.set('publishingDate', new Date('11/31/2008'));
const BookBRelation = BookB.relation("authors");
BookBRelation.add(AuthorB);
await BookB.save();

const BookC = new Parse.Object('Book');
BookC.set('title', 'Can You Believe It?');
BookC.set('publisher', PublisherB);
BookC.set('publishingDate', new Date('08/21/2018'));
const BookCRelation = BookC.relation("authors");
BookCRelation.add(AuthorA);
BookCRelation.add(AuthorC);
await BookC.save();

// BookStore
const BookStoreA = new Parse.Object('BookStore');
BookStoreA.set('name', 'Books of Love');
const BookStoreARelation = BookStoreA.relation("books");
BookStoreARelation.add(BookA);
await BookStoreA.save();

const BookStoreB = new Parse.Object('BookStore');
BookStoreB.set('name', 'Fantasy Books');
const BookStoreBRelation = BookStoreB.relation("books");
BookStoreBRelation.add(BookB);
await BookStoreB.save();

const BookStoreC = new Parse.Object('BookStore');
BookStoreC.set('name', 'General Books');
const BookStoreCRelation = BookStoreC.relation("books");
BookStoreCRelation.add(BookA);
BookStoreCRelation.add(BookC);
await BookStoreC.save();

console.log('Success');

After running this code, you should now have a Author, Publisher, Book and BookStore class in your database.

Your new class should look like this:

Fluuter Back4App

Let’s now take a look at examples from every QueryBuilder method, along with brief explanations on what they do.

Step 3 - Query the data

Now that you have a populated class, we can now perform some relational queries

Now that you have populated all the classes, we can now perform some relational queries in it.

Let’s begin by filtering Book results by the publisher, searching for the ones that belong to the Publisher “Acacia Publishings” (or “Publisher A”) with relational Pointer using the basic .whereEqualTo method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    // Get PublisherA object
    final QueryBuilder<ParseObject> publisherQueryA =
        QueryBuilder<ParseObject>(ParseObject('Publisher'))
          ..whereEqualTo('name', 'Acacia Publishings');

    final ParseResponse publisherResponse = await publisherQueryA.query();
    if (!publisherResponse.success) {
      return;
    }
    final publisherA = publisherResponse.results?.first as ParseObject;

    // Query Books with PublisherA
    final QueryBuilder<ParseObject> bookQuery =
        QueryBuilder<ParseObject>(ParseObject('Book'))
          ..whereEqualTo('publisher', publisherA);

    final ParseResponse bookResponse = await bookQuery.query();
    if (!bookResponse.success) {
      return;
    }

    for (var book in bookResponse.results as List<ParseObject>) {
      print(book.get('title'));
    }

Let’s now query which BookStore objects contain Book objects with publishing date greater than 01/01/2010, using an inner query with the whereGreaterThan method and then the whereMatchesQuery method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    // Create inner Book query
    final QueryBuilder<ParseObject> bookQuery =
        QueryBuilder<ParseObject>(ParseObject('Book'))
          ..whereGreaterThan('publishingDate', DateTime(2010, 01, 01));

    // Query BookStore using inner Book query
    final QueryBuilder<ParseObject> bookStoreQuery =
        QueryBuilder<ParseObject>(ParseObject('BookStore'))
          ..whereMatchesQuery('books', bookQuery);

    final ParseResponse bookStoreResponse = await bookStoreQuery.query();
    if (!bookStoreResponse.success) {
      return;
    }

    for (var b in bookStoreResponse.results as List<ParseObject>) {
      print(b.get('name'));
    }

Let’s now create another query, looking for Authors that are relation with the book Can You Believe It?, using whereRelatedTo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    // Get Book object
    final QueryBuilder<ParseObject> bookQuery =
        QueryBuilder<ParseObject>(ParseObject('Book'))
          ..whereEqualTo('title', 'Can You Believe It?');

    final ParseResponse bookResponse = await bookQuery.query();
    if (!bookResponse.success) {
      return;
    }

    final book = bookResponse.results?.first as ParseObject;

    // Get Author with relation with Book Can You Believe It?
    final QueryBuilder<ParseObject> authorsQuery =
        QueryBuilder<ParseObject>(ParseObject('Author'))
          ..whereRelatedTo('authors', 'Book', book.objectId!);

    final ParseResponse authorResponse = await authorsQuery.query();

    // Let's show the results
    if (authorResponse.success && authorResponse.results != null) {
      for (var a in authorResponse.results! as List<ParseObject>) {
        print(a.get('name'));
      }
    }

Step 4 - Query from Flutter

Let’s now use our example queries inside a Flutter App, with a simple interface having a list showing results and also 3 buttons for calling the queries.

Open your Flutter project, go to the main.dart file, clean up all the code, and replace it with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:parse_server_sdk_flutter/parse_server_sdk.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final keyApplicationId = 'YOUR_APP_ID_HERE';
  final keyClientKey = 'YOUR_CLIENT_KEY_HERE';
  final keyParseServerUrl = 'https://parseapi.back4app.com';

  await Parse().initialize(keyApplicationId, keyParseServerUrl,
      clientKey: keyClientKey, debug: true);

  runApp(MaterialApp(
    title: 'Flutter - GeoPoint',
    debugShowCheckedModeBanner: false,
    home: HomePage(),
  ));
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<ParseObject> results = <ParseObject>[];
  double selectedDistance = 3000;

  void doQueryPointer() async {
    // Get PublisherA object
    final QueryBuilder<ParseObject> publisherQueryA =
        QueryBuilder<ParseObject>(ParseObject('Publisher'))
          ..whereEqualTo('name', 'Acacia Publishings');

    final ParseResponse publisherResponse = await publisherQueryA.query();
    if (!publisherResponse.success) {
      return;
    }
    final publisherA = publisherResponse.results?.first as ParseObject;

    // Query Books with PublisherA
    final QueryBuilder<ParseObject> bookQuery =
        QueryBuilder<ParseObject>(ParseObject('Book'))
          ..whereEqualTo('publisher', publisherA);

    final ParseResponse bookResponse = await bookQuery.query();

    if (!bookResponse.success) {
      setState(() {
        results.clear();
      });
    } else {
      setState(() {
        results = bookResponse.results as List<ParseObject>;
      });
    }
  }

  void doQueryMatches() async {
    // Create inner Book query
    final QueryBuilder<ParseObject> bookQuery =
        QueryBuilder<ParseObject>(ParseObject('Book'))
          ..whereGreaterThan('publishingDate', DateTime(2010, 01, 01));

    // Query BookStore using inner Book query
    final QueryBuilder<ParseObject> bookStoreQuery =
        QueryBuilder<ParseObject>(ParseObject('BookStore'))
          ..whereMatchesQuery('books', bookQuery);

    final ParseResponse bookStoreResponse = await bookStoreQuery.query();
    if (!bookStoreResponse.success) {
      setState(() {
        results.clear();
      });
    } else {
      setState(() {
        results = bookStoreResponse.results as List<ParseObject>;
      });
    }
  }

  void doQueryRelatedTo() async {
    // Get Book object
    final QueryBuilder<ParseObject> bookQuery =
        QueryBuilder<ParseObject>(ParseObject('Book'))
          ..whereEqualTo('title', 'Can You Believe It?');

    final ParseResponse bookResponse = await bookQuery.query();
    if (!bookResponse.success) {
      return;
    }

    final book = bookResponse.results?.first as ParseObject;

    // Get Author with relation with Book Can You Believe It?
    final QueryBuilder<ParseObject> authorsQuery =
        QueryBuilder<ParseObject>(ParseObject('Author'))
          ..whereRelatedTo('authors', 'Book', book.objectId!);

    final ParseResponse authorResponse = await authorsQuery.query();

    if (!authorResponse.success) {
      setState(() {
        results.clear();
      });
    } else {
      setState(() {
        results = authorResponse.results as List<ParseObject>;
      });
    }
  }

  void doClearResults() async {
    setState(() {
      results.clear();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Container(
            height: 200,
            child: Image.network(
                'https://blog.back4app.com/wp-content/uploads/2017/11/logo-b4a-1-768x175-1.png'),
          ),
          Center(
            child: const Text('Flutter on Back4app - GeoPoint',
                style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
          ),
          SizedBox(
            height: 8,
          ),
          Container(
            height: 50,
            child: ElevatedButton(
                onPressed: doQueryPointer,
                child: Text('Query A'),
                style: ElevatedButton.styleFrom(primary: Colors.blue)),
          ),
          SizedBox(
            height: 8,
          ),
          Container(
            height: 50,
            child: ElevatedButton(
                onPressed: doQueryMatches,
                child: Text('Query B'),
                style: ElevatedButton.styleFrom(primary: Colors.blue)),
          ),
          SizedBox(
            height: 8,
          ),
          Container(
            height: 50,
            child: ElevatedButton(
                onPressed: doQueryRelatedTo,
                child: Text('Query C'),
                style: ElevatedButton.styleFrom(primary: Colors.blue)),
          ),
          SizedBox(
            height: 8,
          ),
          Container(
            height: 50,
            child: ElevatedButton(
                onPressed: doClearResults,
                child: Text('Clear results'),
                style: ElevatedButton.styleFrom(primary: Colors.blue)),
          ),
          SizedBox(
            height: 8,
          ),
          Text(
            'Result List: ${results.length}',
          ),
          Expanded(
            child: ListView.builder(
                itemCount: results.length,
                itemBuilder: (context, index) {
                  final o = results[index];
                  return Container(
                    padding: const EdgeInsets.all(4),
                    decoration:
                        BoxDecoration(border: Border.all(color: Colors.black)),
                    child: Text('${o.toString()}'),
                  );
                }),
          )
        ],
      ),
    ));
  }
}

Find your Application Id and Client Key credentials navigating to your app Dashboard at Back4App Website.

Update your code in main.dart with the values of your project’s ApplicationId and ClientKey in Back4app.

  • keyApplicationId = App Id
  • keyClientKey = Client Key

Run the project, and the app will load as shown in the image.

flutter-back4app-associations

Conclusion

At the end of this guide, you learned how relational queries work on Parse and how to perform them on Back4App from a Flutter App. In the next guide, you will learn how to work with Users in Parse.