Skip to content

Commit 8d37351

Browse files
authored
Merge pull request #58 from ckritzinger/improve_docs
More detailed README and small bugfix
2 parents bf8b3a4 + 83bf0ad commit 8d37351

File tree

3 files changed

+119
-11
lines changed

3 files changed

+119
-11
lines changed

packages/powersync/README.md

Lines changed: 103 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,48 +11,141 @@
1111
* No need for client-side database migrations - these are handled automatically.
1212
* Subscribe to queries for live updates.
1313

14+
## Examples
15+
16+
For complete app examples, see our [example app gallery](https://docs.powersync.com/resources/demo-apps-example-projects#flutter)
17+
18+
For examples of some common patterns, see our [example snippets](./example/README.md)
19+
1420
## Getting started
1521

22+
You'll need to create a PowerSync account and set up a PowerSync instance. You can do this at [https://www.powersync.com/](https://www.powersync.com/).
23+
24+
### Install the package
25+
26+
`flutter pub add powersync`
27+
28+
### Implement a backend connector and initialize the PowerSync database
29+
1630
```dart
1731
import 'package:powersync/powersync.dart';
1832
import 'package:path_provider/path_provider.dart';
1933
import 'package:path/path.dart';
2034
35+
// Define the schema for the local SQLite database.
36+
// You can automatically generate this schema based on your sync rules:
37+
// In the PowerSync dashboard, right-click on your PowerSync instance and then click "Generate client-side schema"
2138
const schema = Schema([
2239
Table('customers', [Column.text('name'), Column.text('email')])
2340
]);
2441
2542
late PowerSyncDatabase db;
2643
27-
// Setup connector to backend if you would like to sync data.
28-
class BackendConnector extends PowerSyncBackendConnector {
44+
// You must implement a backend connector to define how PowerSync communicates with your backend.
45+
class MyBackendConnector extends PowerSyncBackendConnector {
2946
PowerSyncDatabase db;
3047
31-
BackendConnector(this.db);
48+
MyBackendConnector(this.db);
3249
@override
3350
Future<PowerSyncCredentials?> fetchCredentials() async {
34-
// implement fetchCredentials
51+
// implement fetchCredentials to obtain a JWT from your authentication service
52+
// see https://docs.powersync.com/usage/installation/authentication-setup
3553
}
3654
@override
3755
Future<void> uploadData(PowerSyncDatabase database) async {
38-
// implement uploadData
56+
// Implement uploadData to send local changes to your backend service
57+
// You can omit this method if you only want to sync data from the server to the client
58+
// see https://docs.powersync.com/usage/installation/upload-data
3959
}
4060
}
4161
4262
openDatabase() async {
4363
final dir = await getApplicationSupportDirectory();
4464
final path = join(dir.path, 'powersync-dart.db');
65+
4566
// Setup the database.
4667
db = PowerSyncDatabase(schema: schema, path: path);
4768
await db.initialize();
4869
49-
// Run local statements.
50-
await db.execute(
70+
// Connect to backend
71+
db.connect(connector: MyBackendConnector(db));
72+
}
73+
```
74+
75+
### Subscribe to changes in data
76+
77+
```dart
78+
StreamBuilder(
79+
// you can watch any SQL query
80+
stream: return db.watch('SELECT * FROM customers order by id asc'),
81+
builder: (context, snapshot) {
82+
if (snapshot.hasData) {
83+
// TODO: implement your own UI here based on the result set
84+
return ...;
85+
} else {
86+
return const Center(child: CircularProgressIndicator());
87+
}
88+
},
89+
)
90+
```
91+
92+
### Insert, update, and delete data in the SQLite database as you would normally
93+
94+
```dart
95+
FloatingActionButton(
96+
onPressed: () async {
97+
await db.execute(
5198
'INSERT INTO customers(id, name, email) VALUES(uuid(), ?, ?)',
52-
['Fred', '[email protected]']);
99+
['Fred', '[email protected]'],
100+
);
101+
},
102+
tooltip: '+',
103+
child: const Icon(Icons.add),
104+
);
105+
```
53106

107+
### Send changes in local data to your backend service
54108

55-
// Connect to backend
56-
db.connect(connector: BackendConnector(db));
109+
```dart
110+
// Implement the uploadData method in your backend connector
111+
@override
112+
Future<void> uploadData(PowerSyncDatabase database) async {
113+
final batch = await database.getCrudBatch();
114+
if (batch == null) return;
115+
for (var op in batch.crud) {
116+
switch (op.op) {
117+
case UpdateType.put:
118+
// Send the data to your backend service
119+
// replace `_myApi` with your own API client or service
120+
await _myApi.put(op.table, op.opData!);
121+
break;
122+
default:
123+
// TODO: implement the other operations (patch, delete)
124+
break;
125+
}
126+
}
127+
await batch.complete();
57128
}
58129
```
130+
131+
### Logging
132+
133+
You can enable logging to see what's happening under the hood
134+
or to debug connection/authentication/sync issues.
135+
136+
```dart
137+
Logger.root.level = Level.INFO;
138+
Logger.root.onRecord.listen((record) {
139+
if (kDebugMode) {
140+
print('[${record.loggerName}] ${record.level.name}: ${record.time}: ${record.message}');
141+
142+
if (record.error != null) {
143+
print(record.error);
144+
}
145+
if (record.stackTrace != null) {
146+
print(record.stackTrace);
147+
}
148+
}
149+
});
150+
```
151+

packages/powersync/lib/src/connector.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ class PowerSyncCredentials {
113113
try {
114114
List<String> parts = token.split('.');
115115
if (parts.length == 3) {
116-
final rawData = base64Decode(parts[1]);
116+
// dart:convert doesn't like missing padding
117+
final rawData = base64Url.decode(base64.normalize(parts[1]));
117118
final text = Utf8Decoder().convert(rawData);
118119
Map<String, dynamic> payload = jsonDecode(text);
119120
if (payload.containsKey('exp') && payload['exp'] is int) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import 'package:powersync/powersync.dart';
2+
import 'package:test/test.dart';
3+
4+
void main() {
5+
group('PowerSyncCredentials', () {
6+
test('getExpiryDate', () async {
7+
// Specifically test a token with a "-" character and missing padding
8+
final token =
9+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ9Pn0-YWIiLCJpYXQiOjE3MDc3Mzk0MDAsImV4cCI6MTcwNzczOTUwMH0=.IVoAtpJ7jfwLbqlyJGYHPCvljLis_fHj2Qvdqlj8AQU';
10+
expect(PowerSyncCredentials.getExpiryDate(token)?.toUtc(),
11+
equals(DateTime.parse('2024-02-12T12:05:00Z')));
12+
});
13+
});
14+
}

0 commit comments

Comments
 (0)