Skip to content

Commit 0fd1955

Browse files
HeinrichvonSteinJoshua Brink
authored andcommitted
Improvements to Flutter code examples (#150)
1 parent 37fa8fe commit 0fd1955

File tree

1 file changed

+187
-48
lines changed

1 file changed

+187
-48
lines changed

client-sdk-references/flutter.mdx

Lines changed: 187 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,24 @@ Before implementing the PowerSync SDK in your project, make sure you have comple
4242
* [Configured your backend database](/installation/database-setup) and connected it to your PowerSync instance.
4343
* [Installed](/client-sdk-references/flutter#installation) the PowerSync Flutter SDK.
4444

45+
<Note>
46+
For this reference document, we assume that you have created a Flutter project and have the following directory structure:
47+
48+
```plaintext
49+
lib/
50+
├── models/
51+
├── schema.dart
52+
└── todolist.dart
53+
├── powersync/
54+
├── my_backend_connector.dart
55+
└── powersync.dart
56+
├── widgets/
57+
├── lists_widget.dart
58+
├── todos_widget.dart
59+
├── main.dart
60+
```
61+
</Note>
62+
4563
### 1\. Define the Schema
4664

4765
The first step is defining the schema for the local SQLite database. This will be provided as a `schema` parameter to the [PowerSyncDatabase](https://pub.dev/documentation/powersync/latest/powersync/PowerSyncDatabase/PowerSyncDatabase.html) constructor.
@@ -60,8 +78,7 @@ The types available are `text`, `integer` and `real`. These should map directly
6078

6179
**Example**:
6280

63-
```dart
64-
// lib/models/schema.dart
81+
```dart lib/models/schema.dart
6582
import 'package:powersync/powersync.dart';
6683
6784
const schema = Schema(([
@@ -99,10 +116,12 @@ To instantiate `PowerSyncDatabase`, inject the Schema you defined in the previou
99116

100117
**Example**:
101118

102-
```dart
103-
import 'package:powersync/powersync.dart';
104-
import 'package:path_provider/path_provider.dart';
119+
```dart lib/powersync/powersync.dart
105120
import 'package:path/path.dart';
121+
import 'package:path_provider/path_provider.dart';
122+
import 'package:powersync/powersync.dart';
123+
import '../main.dart';
124+
import '../models/schema.dart';
106125
107126
openDatabase() async {
108127
final dir = await getApplicationSupportDirectory();
@@ -117,9 +136,49 @@ openDatabase() async {
117136

118137
Once you've instantiated your PowerSync database, you will need to call the [connect()](https://pub.dev/documentation/powersync/latest/powersync/PowerSyncDatabase/connect.html) method to activate it. This method requires the backend connector that will be created in the next step.
119138

120-
```dart
121-
// Uses the backend connector that will be created in the next step
122-
db.connect(connector: MyBackendConnector(db));
139+
```dart lib/main.dart {35}
140+
import 'package:flutter/material.dart';
141+
import 'package:powersync/powersync.dart';
142+
143+
import 'powersync/powersync.dart';
144+
145+
late PowerSyncDatabase db;
146+
147+
Future<void> main() async {
148+
WidgetsFlutterBinding.ensureInitialized();
149+
150+
await openDatabase();
151+
runApp(const DemoApp());
152+
}
153+
154+
class DemoApp extends StatefulWidget {
155+
const DemoApp({super.key});
156+
157+
@override
158+
State<DemoApp> createState() => _DemoAppState();
159+
}
160+
161+
class _DemoAppState extends State<DemoApp> {
162+
@override
163+
Widget build(BuildContext context) {
164+
return MaterialApp(
165+
title: 'Demo',
166+
home: // TODO: Implement your own UI here.
167+
// You could listen for authentication state changes to connect or disconnect from PowerSync
168+
StreamBuilder(
169+
stream: // TODO: some stream,
170+
builder: (ctx, snapshot) {,
171+
// TODO: implement your own condition here
172+
if ( ... ) {
173+
// Uses the backend connector that will be created in the next step
174+
db.connect(connector: MyBackendConnector());
175+
// TODO: implement your own UI here
176+
}
177+
},
178+
)
179+
);
180+
}
181+
}
123182
```
124183

125184
### 3\. Integrate with your Backend
@@ -139,12 +198,8 @@ Accordingly, the connector must implement two methods:
139198

140199
**Example**:
141200

142-
```dart
201+
```dart lib/powersync/my_backend_connector.dart
143202
import 'package:powersync/powersync.dart';
144-
import 'package:path_provider/path_provider.dart';
145-
import 'package:path/path.dart';
146-
147-
late PowerSyncDatabase db;
148203
149204
class MyBackendConnector extends PowerSyncBackendConnector {
150205
PowerSyncDatabase db;
@@ -159,18 +214,41 @@ class MyBackendConnector extends PowerSyncBackendConnector {
159214
160215
// See example implementation here: https://pub.dev/documentation/powersync/latest/powersync/DevConnector/fetchCredentials.html
161216
162-
return {
163-
endpoint: '[Your PowerSync instance URL or self-hosted endpoint]',
164-
// Use a development token (see Authentication Setup https://docs.powersync.com/installation/authentication-setup/development-tokens) to get up and running quickly
165-
token: 'An authentication token'
166-
};
217+
return PowerSyncCredentials(
218+
endpoint: 'https://xxxxxx.powersync.journeyapps.com',
219+
// Use a development token (see Authentication Setup https://docs.powersync.com/installation/authentication-setup/development-tokens) to get up and running quickly
220+
token: 'An authentication token'
221+
);
167222
}
223+
224+
// Implement uploadData to send local changes to your backend service
225+
// You can omit this method if you only want to sync data from the server to the client
226+
// See example implementation here: https://docs.powersync.com/client-sdk-references/flutter#3-integrate-with-your-backend
168227
@override
169228
Future<void> uploadData(PowerSyncDatabase database) async {
170-
// Implement uploadData to send local changes to your backend service
171-
// You can omit this method if you only want to sync data from the server to the client
229+
// This function is called whenever there is data to upload, whether the
230+
// device is online or offline.
231+
// If this call throws an error, it is retried periodically.
232+
233+
final transaction = await database.getNextCrudTransaction();
234+
if (transaction == null) {
235+
return;
236+
}
172237
173-
// See example implementation here: https://docs.powersync.com/client-sdk-references/flutter#3-integrate-with-your-backend
238+
// The data that needs to be changed in the remote db
239+
for (var op in transaction.crud) {
240+
switch (op.op) {
241+
case UpdateType.put:
242+
// TODO: Instruct your backend API to CREATE a record
243+
case UpdateType.patch:
244+
// TODO: Instruct your backend API to PATCH a record
245+
case UpdateType.delete:
246+
//TODO: Instruct your backend API to DELETE a record
247+
}
248+
}
249+
250+
// Completes the transaction and moves onto the next one
251+
await transaction.complete();
174252
}
175253
}
176254
@@ -187,12 +265,43 @@ The most commonly used CRUD functions to interact with your SQLite data are:
187265
* [PowerSyncDatabase.watch](/client-sdk-references/flutter#watching-queries-powersync.watch) \- execute a read query every time source tables are modified.
188266
* [PowerSyncDatabase.execute](/client-sdk-references/flutter#mutations-powersync.execute) \- execute a write (INSERT/UPDATE/DELETE) query.
189267

268+
For the following examples, we will define a `TodoList` model class that represents a List of todos.
269+
270+
```dart lib/models/todolist.dart
271+
/// This is a simple model class representing a TodoList
272+
class TodoList {
273+
final int id;
274+
final String name;
275+
final DateTime createdAt;
276+
final DateTime updatedAt;
277+
278+
TodoList({
279+
required this.id,
280+
required this.name,
281+
required this.createdAt,
282+
required this.updatedAt,
283+
});
284+
285+
factory TodoList.fromRow(Map<String, dynamic> row) {
286+
return TodoList(
287+
id: row['id'],
288+
name: row['name'],
289+
createdAt: DateTime.parse(row['created_at']),
290+
updatedAt: DateTime.parse(row['updated_at']),
291+
);
292+
}
293+
}
294+
```
295+
190296
### Fetching a Single Item
191297

192298
The [get](https://pub.dev/documentation/powersync/latest/sqlite_async/SqliteQueries/get.html) method executes a read-only (SELECT) query and returns a single result. It throws an exception if no result is found. Use [getOptional](https://pub.dev/documentation/powersync/latest/sqlite_async/SqliteQueries/getOptional.html) to return a single optional result (returns `null` if no result is found).
193299

194-
```dart
195-
// Find a list item by ID
300+
The following is an example of selecting a list item by ID
301+
```dart lib/widgets/lists_widget.dart
302+
import '../main.dart';
303+
import '../models/todolist.dart';
304+
196305
Future<TodoList> find(id) async {
197306
final result = await db.get('SELECT * FROM lists WHERE id = ?', [id]);
198307
return TodoList.fromRow(result);
@@ -203,48 +312,78 @@ Future<TodoList> find(id) async {
203312

204313
The [getAll](https://pub.dev/documentation/powersync/latest/sqlite_async/SqliteQueries/getAll.html) method returns a set of rows from a table.
205314

206-
```dart
207-
/// Get all list IDs
315+
```dart lib/widgets/lists_widget.dart
316+
import 'package:powersync/sqlite3.dart';
317+
import '../main.dart';
318+
208319
Future<List<String>> getLists() async {
209320
ResultSet results = await db.getAll('SELECT id FROM lists WHERE id IS NOT NULL');
210321
List<String> ids = results.map((row) => row['id'] as String).toList();
211322
return ids;
212323
}
213324
```
325+
214326
### Watching Queries (PowerSync.watch)
215327

216328
The [watch](https://pub.dev/documentation/powersync/latest/sqlite_async/SqliteQueries/watch.html) method executes a read query whenever a change to a dependent table is made.
217329

218-
```dart
219-
StreamBuilder(
220-
// You can watch any SQL query
221-
stream: db.watch('SELECT * FROM customers ORDER BY id asc'),
222-
builder: (context, snapshot) {
223-
if (snapshot.hasData) {
224-
// TODO: implement your own UI here based on the result set
225-
return ...;
226-
} else {
227-
return const Center(child: CircularProgressIndicator());
228-
}
229-
},
230-
)
330+
```dart lib/widgets/todos_widget.dart {13-17}
331+
import 'package:flutter/material.dart';
332+
import '../main.dart';
333+
import '../models/todolist.dart';
334+
335+
// Example Todos widget
336+
class TodosWidget extends StatelessWidget {
337+
const TodosWidget({super.key});
338+
339+
@override
340+
Widget build(BuildContext context) {
341+
return StreamBuilder(
342+
// You can watch any SQL query
343+
stream: db
344+
.watch('SELECT * FROM lists ORDER BY created_at, id')
345+
.map((results) {
346+
return results.map(TodoList.fromRow).toList(growable: false);
347+
}),
348+
builder: (context, snapshot) {
349+
if (snapshot.hasData) {
350+
// TODO: implement your own UI here based on the result set
351+
return ...;
352+
} else {
353+
return const Center(child: CircularProgressIndicator());
354+
}
355+
},
356+
);
357+
}
358+
}
231359
```
232360

233361
### Mutations (PowerSync.execute)
234362

235363
The [execute](https://pub.dev/documentation/powersync/latest/powersync/PowerSyncDatabase/execute.html) method can be used for executing single SQLite write statements.
236364

237-
```dart
238-
FloatingActionButton(
239-
onPressed: () async {
240-
await db.execute(
241-
'INSERT INTO customers(id, name, email) VALUES(uuid(), ?, ?)',
242-
['Fred', 'fred@example.org'],
365+
```dart lib/widgets/todos_widget.dart {12-15}
366+
import 'package:flutter/material.dart';
367+
import '../main.dart';
368+
369+
// Example Todos widget
370+
class TodosWidget extends StatelessWidget {
371+
const TodosWidget({super.key});
372+
373+
@override
374+
Widget build(BuildContext context) {
375+
return FloatingActionButton(
376+
onPressed: () async {
377+
await db.execute(
378+
'INSERT INTO lists(id, created_at, name, owner_id) VALUES(uuid(), datetime(), ?, ?)',
379+
['name', '123'],
380+
);
381+
},
382+
tooltip: '+',
383+
child: const Icon(Icons.add),
243384
);
244-
},
245-
tooltip: '+',
246-
child: const Icon(Icons.add),
247-
);
385+
}
386+
}
248387
```
249388

250389
## Additional Usage Examples

0 commit comments

Comments
 (0)