Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit bd4a602

Browse files
committedMar 13, 2023
Update Flutter SDK to add offline support
1 parent 811bd17 commit bd4a602

19 files changed

+2064
-174
lines changed
 

‎CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 9.0.0
2+
3+
* Add offline support
4+
15
## 8.3.0
26

37
* Fix: back navigation bringing back web browser after OAuth session creation

‎README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
[![pub package](https://img.shields.io/pub/v/appwrite?style=flat-square)](https://pub.dartlang.org/packages/appwrite)
44
![License](https://img.shields.io/github/license/appwrite/sdk-for-flutter.svg?style=flat-square)
5-
![Version](https://img.shields.io/badge/api%20version-1.2.1-blue.svg?style=flat-square)
5+
![Version](https://img.shields.io/badge/api%20version-1.3.0-blue.svg?style=flat-square)
66
[![Build Status](https://img.shields.io/travis/com/appwrite/sdk-generator?style=flat-square)](https://travis-ci.com/appwrite/sdk-generator)
77
[![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite)
88
[![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord)
99

10-
**This SDK is compatible with Appwrite server version 1.2.x. For older versions, please check [previous releases](https://github.com/appwrite/sdk-for-flutter/releases).**
10+
**This SDK is compatible with Appwrite server version 1.3.x. For older versions, please check [previous releases](https://github.com/appwrite/sdk-for-flutter/releases).**
1111

1212
Appwrite is an open-source backend as a service server that abstract and simplify complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. Use the Flutter SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)
1313

@@ -21,7 +21,7 @@ Add this to your package's `pubspec.yaml` file:
2121

2222
```yml
2323
dependencies:
24-
appwrite: ^8.3.0
24+
appwrite: ^9.0.0
2525
```
2626
2727
You can install packages from the command line:

‎example/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
name: appwrite_example
22
environment:
3-
sdk: '>=2.17.0 <3.0.0'
3+
sdk: ">=2.17.0 <3.0.0"

‎lib/appwrite.dart

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
library appwrite;
22

33
import 'dart:async';
4+
import 'dart:math';
45
import 'dart:typed_data';
56
import 'src/enums.dart';
67
import 'src/service.dart';
78
import 'src/input_file.dart';
89
import 'models.dart' as models;
910
import 'src/upload_progress.dart';
1011

11-
export 'src/response.dart';
1212
export 'src/client.dart';
1313
export 'src/exception.dart';
14+
export 'src/input_file.dart';
1415
export 'src/realtime.dart';
15-
export 'src/upload_progress.dart';
16-
export 'src/realtime_subscription.dart';
1716
export 'src/realtime_message.dart';
18-
export 'src/input_file.dart';
17+
export 'src/realtime_subscription.dart';
18+
export 'src/response.dart';
19+
export 'src/upload_progress.dart';
1920

20-
part 'query.dart';
21+
part 'id.dart';
2122
part 'permission.dart';
23+
part 'query.dart';
2224
part 'role.dart';
23-
part 'id.dart';
2425
part 'services/account.dart';
2526
part 'services/avatars.dart';
2627
part 'services/databases.dart';

‎lib/id.dart

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,40 @@
11
part of appwrite;
22

33
class ID {
4-
ID._();
5-
6-
static String unique() {
7-
return 'unique()';
8-
}
4+
ID._();
5+
6+
// Generate a unique ID based on timestamp
7+
// Recreated from https://www.php.net/manual/en/function.uniqid.php
8+
static String _uniqid() {
9+
final now = DateTime.now();
10+
final secondsSinceEpoch = (now.millisecondsSinceEpoch / 1000).floor();
11+
final msecs = now.microsecondsSinceEpoch - secondsSinceEpoch * 1000000;
12+
return secondsSinceEpoch.toRadixString(16) +
13+
msecs.toRadixString(16).padLeft(5, '0');
14+
}
15+
16+
// Generate a unique ID with padding to have a longer ID
17+
// Recreated from https://github.com/utopia-php/database/blob/main/src/Database/ID.php#L13
18+
static String _unique({int padding = 7}) {
19+
String id = _uniqid();
20+
21+
if (padding > 0) {
22+
StringBuffer sb = StringBuffer();
23+
for (var i = 0; i < padding; i++) {
24+
sb.write(Random().nextInt(16).toRadixString(16));
25+
}
926

10-
static String custom(String id) {
11-
return id;
27+
id += sb.toString();
1228
}
13-
}
29+
30+
return id;
31+
}
32+
33+
static String unique() {
34+
return _unique();
35+
}
36+
37+
static String custom(String id) {
38+
return id;
39+
}
40+
}

‎lib/services/account.dart

Lines changed: 420 additions & 28 deletions
Large diffs are not rendered by default.

‎lib/services/databases.dart

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,21 @@ class Databases extends Service {
2121
'content-type': 'application/json',
2222
};
2323

24-
final res = await client.call(HttpMethod.get, path: path, params: params, headers: headers);
24+
final cacheModel = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId);
25+
final cacheKey = '';
26+
final cacheResponseIdKey = '\$id';
27+
final cacheResponseContainerKey = 'documents';
28+
29+
final res = await client.call(
30+
HttpMethod.get,
31+
path: path,
32+
params: params,
33+
headers: headers,
34+
cacheModel: cacheModel,
35+
cacheKey: cacheKey,
36+
cacheResponseIdKey: cacheResponseIdKey,
37+
cacheResponseContainerKey: cacheResponseContainerKey,
38+
);
2539

2640
return models.DocumentList.fromMap(res.data);
2741

@@ -47,7 +61,21 @@ class Databases extends Service {
4761
'content-type': 'application/json',
4862
};
4963

50-
final res = await client.call(HttpMethod.post, path: path, params: params, headers: headers);
64+
final cacheModel = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId);
65+
final cacheKey = documentId;
66+
final cacheResponseIdKey = '\$id';
67+
final cacheResponseContainerKey = '';
68+
69+
final res = await client.call(
70+
HttpMethod.post,
71+
path: path,
72+
params: params,
73+
headers: headers,
74+
cacheModel: cacheModel,
75+
cacheKey: cacheKey,
76+
cacheResponseIdKey: cacheResponseIdKey,
77+
cacheResponseContainerKey: cacheResponseContainerKey,
78+
);
5179

5280
return models.Document.fromMap(res.data);
5381

@@ -68,7 +96,21 @@ class Databases extends Service {
6896
'content-type': 'application/json',
6997
};
7098

71-
final res = await client.call(HttpMethod.get, path: path, params: params, headers: headers);
99+
final cacheModel = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId).replaceAll('{documentId}', documentId);
100+
final cacheKey = documentId;
101+
final cacheResponseIdKey = '\$id';
102+
final cacheResponseContainerKey = '';
103+
104+
final res = await client.call(
105+
HttpMethod.get,
106+
path: path,
107+
params: params,
108+
headers: headers,
109+
cacheModel: cacheModel,
110+
cacheKey: cacheKey,
111+
cacheResponseIdKey: cacheResponseIdKey,
112+
cacheResponseContainerKey: cacheResponseContainerKey,
113+
);
72114

73115
return models.Document.fromMap(res.data);
74116

@@ -91,7 +133,21 @@ class Databases extends Service {
91133
'content-type': 'application/json',
92134
};
93135

94-
final res = await client.call(HttpMethod.patch, path: path, params: params, headers: headers);
136+
final cacheModel = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId).replaceAll('{documentId}', documentId);
137+
final cacheKey = documentId;
138+
final cacheResponseIdKey = '\$id';
139+
final cacheResponseContainerKey = '';
140+
141+
final res = await client.call(
142+
HttpMethod.patch,
143+
path: path,
144+
params: params,
145+
headers: headers,
146+
cacheModel: cacheModel,
147+
cacheKey: cacheKey,
148+
cacheResponseIdKey: cacheResponseIdKey,
149+
cacheResponseContainerKey: cacheResponseContainerKey,
150+
);
95151

96152
return models.Document.fromMap(res.data);
97153

@@ -111,7 +167,21 @@ class Databases extends Service {
111167
'content-type': 'application/json',
112168
};
113169

114-
final res = await client.call(HttpMethod.delete, path: path, params: params, headers: headers);
170+
final cacheModel = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId).replaceAll('{documentId}', documentId);
171+
final cacheKey = documentId;
172+
final cacheResponseIdKey = '\$id';
173+
final cacheResponseContainerKey = '';
174+
175+
final res = await client.call(
176+
HttpMethod.delete,
177+
path: path,
178+
params: params,
179+
headers: headers,
180+
cacheModel: cacheModel,
181+
cacheKey: cacheKey,
182+
cacheResponseIdKey: cacheResponseIdKey,
183+
cacheResponseContainerKey: cacheResponseContainerKey,
184+
);
115185

116186
return res.data;
117187

‎lib/services/functions.dart

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,21 @@ class Functions extends Service {
2222
'content-type': 'application/json',
2323
};
2424

25-
final res = await client.call(HttpMethod.get, path: path, params: params, headers: headers);
25+
final cacheModel = '';
26+
final cacheKey = '';
27+
final cacheResponseIdKey = '\$id';
28+
final cacheResponseContainerKey = 'executions';
29+
30+
final res = await client.call(
31+
HttpMethod.get,
32+
path: path,
33+
params: params,
34+
headers: headers,
35+
cacheModel: cacheModel,
36+
cacheKey: cacheKey,
37+
cacheResponseIdKey: cacheResponseIdKey,
38+
cacheResponseContainerKey: cacheResponseContainerKey,
39+
);
2640

2741
return models.ExecutionList.fromMap(res.data);
2842

@@ -47,7 +61,21 @@ class Functions extends Service {
4761
'content-type': 'application/json',
4862
};
4963

50-
final res = await client.call(HttpMethod.post, path: path, params: params, headers: headers);
64+
final cacheModel = '';
65+
final cacheKey = '';
66+
final cacheResponseIdKey = '\$id';
67+
final cacheResponseContainerKey = '';
68+
69+
final res = await client.call(
70+
HttpMethod.post,
71+
path: path,
72+
params: params,
73+
headers: headers,
74+
cacheModel: cacheModel,
75+
cacheKey: cacheKey,
76+
cacheResponseIdKey: cacheResponseIdKey,
77+
cacheResponseContainerKey: cacheResponseContainerKey,
78+
);
5179

5280
return models.Execution.fromMap(res.data);
5381

@@ -67,7 +95,21 @@ class Functions extends Service {
6795
'content-type': 'application/json',
6896
};
6997

70-
final res = await client.call(HttpMethod.get, path: path, params: params, headers: headers);
98+
final cacheModel = '';
99+
final cacheKey = '';
100+
final cacheResponseIdKey = '\$id';
101+
final cacheResponseContainerKey = '';
102+
103+
final res = await client.call(
104+
HttpMethod.get,
105+
path: path,
106+
params: params,
107+
headers: headers,
108+
cacheModel: cacheModel,
109+
cacheKey: cacheKey,
110+
cacheResponseIdKey: cacheResponseIdKey,
111+
cacheResponseContainerKey: cacheResponseContainerKey,
112+
);
71113

72114
return models.Execution.fromMap(res.data);
73115

‎lib/services/graphql.dart

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,21 @@ class Graphql extends Service {
2020
'x-sdk-graphql': 'true', 'content-type': 'application/json',
2121
};
2222

23-
final res = await client.call(HttpMethod.post, path: path, params: params, headers: headers);
23+
final cacheModel = '';
24+
final cacheKey = '';
25+
final cacheResponseIdKey = '\$id';
26+
final cacheResponseContainerKey = '';
27+
28+
final res = await client.call(
29+
HttpMethod.post,
30+
path: path,
31+
params: params,
32+
headers: headers,
33+
cacheModel: cacheModel,
34+
cacheKey: cacheKey,
35+
cacheResponseIdKey: cacheResponseIdKey,
36+
cacheResponseContainerKey: cacheResponseContainerKey,
37+
);
2438

2539
return res.data;
2640

@@ -41,7 +55,21 @@ class Graphql extends Service {
4155
'x-sdk-graphql': 'true', 'content-type': 'application/json',
4256
};
4357

44-
final res = await client.call(HttpMethod.post, path: path, params: params, headers: headers);
58+
final cacheModel = '';
59+
final cacheKey = '';
60+
final cacheResponseIdKey = '\$id';
61+
final cacheResponseContainerKey = '';
62+
63+
final res = await client.call(
64+
HttpMethod.post,
65+
path: path,
66+
params: params,
67+
headers: headers,
68+
cacheModel: cacheModel,
69+
cacheKey: cacheKey,
70+
cacheResponseIdKey: cacheResponseIdKey,
71+
cacheResponseContainerKey: cacheResponseContainerKey,
72+
);
4573

4674
return res.data;
4775

‎lib/services/locale.dart

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,21 @@ class Locale extends Service {
2424
'content-type': 'application/json',
2525
};
2626

27-
final res = await client.call(HttpMethod.get, path: path, params: params, headers: headers);
27+
final cacheModel = '/locale';
28+
final cacheKey = 'current';
29+
final cacheResponseIdKey = '\$id';
30+
final cacheResponseContainerKey = '';
31+
32+
final res = await client.call(
33+
HttpMethod.get,
34+
path: path,
35+
params: params,
36+
headers: headers,
37+
cacheModel: cacheModel,
38+
cacheKey: cacheKey,
39+
cacheResponseIdKey: cacheResponseIdKey,
40+
cacheResponseContainerKey: cacheResponseContainerKey,
41+
);
2842

2943
return models.Locale.fromMap(res.data);
3044

@@ -45,7 +59,21 @@ class Locale extends Service {
4559
'content-type': 'application/json',
4660
};
4761

48-
final res = await client.call(HttpMethod.get, path: path, params: params, headers: headers);
62+
final cacheModel = '/locale/continents';
63+
final cacheKey = '';
64+
final cacheResponseIdKey = 'code';
65+
final cacheResponseContainerKey = 'continents';
66+
67+
final res = await client.call(
68+
HttpMethod.get,
69+
path: path,
70+
params: params,
71+
headers: headers,
72+
cacheModel: cacheModel,
73+
cacheKey: cacheKey,
74+
cacheResponseIdKey: cacheResponseIdKey,
75+
cacheResponseContainerKey: cacheResponseContainerKey,
76+
);
4977

5078
return models.ContinentList.fromMap(res.data);
5179

@@ -66,7 +94,21 @@ class Locale extends Service {
6694
'content-type': 'application/json',
6795
};
6896

69-
final res = await client.call(HttpMethod.get, path: path, params: params, headers: headers);
97+
final cacheModel = '/locale/countries';
98+
final cacheKey = '';
99+
final cacheResponseIdKey = 'code';
100+
final cacheResponseContainerKey = 'countries';
101+
102+
final res = await client.call(
103+
HttpMethod.get,
104+
path: path,
105+
params: params,
106+
headers: headers,
107+
cacheModel: cacheModel,
108+
cacheKey: cacheKey,
109+
cacheResponseIdKey: cacheResponseIdKey,
110+
cacheResponseContainerKey: cacheResponseContainerKey,
111+
);
70112

71113
return models.CountryList.fromMap(res.data);
72114

@@ -87,7 +129,21 @@ class Locale extends Service {
87129
'content-type': 'application/json',
88130
};
89131

90-
final res = await client.call(HttpMethod.get, path: path, params: params, headers: headers);
132+
final cacheModel = '/locale/countries/eu';
133+
final cacheKey = '';
134+
final cacheResponseIdKey = 'code';
135+
final cacheResponseContainerKey = 'countries';
136+
137+
final res = await client.call(
138+
HttpMethod.get,
139+
path: path,
140+
params: params,
141+
headers: headers,
142+
cacheModel: cacheModel,
143+
cacheKey: cacheKey,
144+
cacheResponseIdKey: cacheResponseIdKey,
145+
cacheResponseContainerKey: cacheResponseContainerKey,
146+
);
91147

92148
return models.CountryList.fromMap(res.data);
93149

@@ -108,7 +164,21 @@ class Locale extends Service {
108164
'content-type': 'application/json',
109165
};
110166

111-
final res = await client.call(HttpMethod.get, path: path, params: params, headers: headers);
167+
final cacheModel = '/locale/countries/phones';
168+
final cacheKey = '';
169+
final cacheResponseIdKey = 'countryCode';
170+
final cacheResponseContainerKey = 'phones';
171+
172+
final res = await client.call(
173+
HttpMethod.get,
174+
path: path,
175+
params: params,
176+
headers: headers,
177+
cacheModel: cacheModel,
178+
cacheKey: cacheKey,
179+
cacheResponseIdKey: cacheResponseIdKey,
180+
cacheResponseContainerKey: cacheResponseContainerKey,
181+
);
112182

113183
return models.PhoneList.fromMap(res.data);
114184

@@ -130,7 +200,21 @@ class Locale extends Service {
130200
'content-type': 'application/json',
131201
};
132202

133-
final res = await client.call(HttpMethod.get, path: path, params: params, headers: headers);
203+
final cacheModel = '/locale/currencies';
204+
final cacheKey = '';
205+
final cacheResponseIdKey = 'code';
206+
final cacheResponseContainerKey = 'currencies';
207+
208+
final res = await client.call(
209+
HttpMethod.get,
210+
path: path,
211+
params: params,
212+
headers: headers,
213+
cacheModel: cacheModel,
214+
cacheKey: cacheKey,
215+
cacheResponseIdKey: cacheResponseIdKey,
216+
cacheResponseContainerKey: cacheResponseContainerKey,
217+
);
134218

135219
return models.CurrencyList.fromMap(res.data);
136220

@@ -151,7 +235,21 @@ class Locale extends Service {
151235
'content-type': 'application/json',
152236
};
153237

154-
final res = await client.call(HttpMethod.get, path: path, params: params, headers: headers);
238+
final cacheModel = '/locale/languages';
239+
final cacheKey = '';
240+
final cacheResponseIdKey = 'code';
241+
final cacheResponseContainerKey = 'languages';
242+
243+
final res = await client.call(
244+
HttpMethod.get,
245+
path: path,
246+
params: params,
247+
headers: headers,
248+
cacheModel: cacheModel,
249+
cacheKey: cacheKey,
250+
cacheResponseIdKey: cacheResponseIdKey,
251+
cacheResponseContainerKey: cacheResponseContainerKey,
252+
);
155253

156254
return models.LanguageList.fromMap(res.data);
157255

‎lib/services/storage.dart

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,21 @@ class Storage extends Service {
2121
'content-type': 'application/json',
2222
};
2323

24-
final res = await client.call(HttpMethod.get, path: path, params: params, headers: headers);
24+
final cacheModel = '';
25+
final cacheKey = '';
26+
final cacheResponseIdKey = '\$id';
27+
final cacheResponseContainerKey = 'files';
28+
29+
final res = await client.call(
30+
HttpMethod.get,
31+
path: path,
32+
params: params,
33+
headers: headers,
34+
cacheModel: cacheModel,
35+
cacheKey: cacheKey,
36+
cacheResponseIdKey: cacheResponseIdKey,
37+
cacheResponseContainerKey: cacheResponseContainerKey,
38+
);
2539

2640
return models.FileList.fromMap(res.data);
2741

@@ -94,7 +108,21 @@ class Storage extends Service {
94108
'content-type': 'application/json',
95109
};
96110

97-
final res = await client.call(HttpMethod.get, path: path, params: params, headers: headers);
111+
final cacheModel = '';
112+
final cacheKey = '';
113+
final cacheResponseIdKey = '\$id';
114+
final cacheResponseContainerKey = '';
115+
116+
final res = await client.call(
117+
HttpMethod.get,
118+
path: path,
119+
params: params,
120+
headers: headers,
121+
cacheModel: cacheModel,
122+
cacheKey: cacheKey,
123+
cacheResponseIdKey: cacheResponseIdKey,
124+
cacheResponseContainerKey: cacheResponseContainerKey,
125+
);
98126

99127
return models.File.fromMap(res.data);
100128

@@ -116,7 +144,21 @@ class Storage extends Service {
116144
'content-type': 'application/json',
117145
};
118146

119-
final res = await client.call(HttpMethod.put, path: path, params: params, headers: headers);
147+
final cacheModel = '';
148+
final cacheKey = '';
149+
final cacheResponseIdKey = '\$id';
150+
final cacheResponseContainerKey = '';
151+
152+
final res = await client.call(
153+
HttpMethod.put,
154+
path: path,
155+
params: params,
156+
headers: headers,
157+
cacheModel: cacheModel,
158+
cacheKey: cacheKey,
159+
cacheResponseIdKey: cacheResponseIdKey,
160+
cacheResponseContainerKey: cacheResponseContainerKey,
161+
);
120162

121163
return models.File.fromMap(res.data);
122164

@@ -137,7 +179,21 @@ class Storage extends Service {
137179
'content-type': 'application/json',
138180
};
139181

140-
final res = await client.call(HttpMethod.delete, path: path, params: params, headers: headers);
182+
final cacheModel = '';
183+
final cacheKey = '';
184+
final cacheResponseIdKey = '\$id';
185+
final cacheResponseContainerKey = '';
186+
187+
final res = await client.call(
188+
HttpMethod.delete,
189+
path: path,
190+
params: params,
191+
headers: headers,
192+
cacheModel: cacheModel,
193+
cacheKey: cacheKey,
194+
cacheResponseIdKey: cacheResponseIdKey,
195+
cacheResponseContainerKey: cacheResponseContainerKey,
196+
);
141197

142198
return res.data;
143199

‎lib/services/teams.dart

Lines changed: 165 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,21 @@ class Teams extends Service {
2222
'content-type': 'application/json',
2323
};
2424

25-
final res = await client.call(HttpMethod.get, path: path, params: params, headers: headers);
25+
final cacheModel = '/teams';
26+
final cacheKey = '';
27+
final cacheResponseIdKey = '\$id';
28+
final cacheResponseContainerKey = 'teams';
29+
30+
final res = await client.call(
31+
HttpMethod.get,
32+
path: path,
33+
params: params,
34+
headers: headers,
35+
cacheModel: cacheModel,
36+
cacheKey: cacheKey,
37+
cacheResponseIdKey: cacheResponseIdKey,
38+
cacheResponseContainerKey: cacheResponseContainerKey,
39+
);
2640

2741
return models.TeamList.fromMap(res.data);
2842

@@ -47,7 +61,21 @@ class Teams extends Service {
4761
'content-type': 'application/json',
4862
};
4963

50-
final res = await client.call(HttpMethod.post, path: path, params: params, headers: headers);
64+
final cacheModel = '';
65+
final cacheKey = '';
66+
final cacheResponseIdKey = '\$id';
67+
final cacheResponseContainerKey = '';
68+
69+
final res = await client.call(
70+
HttpMethod.post,
71+
path: path,
72+
params: params,
73+
headers: headers,
74+
cacheModel: cacheModel,
75+
cacheKey: cacheKey,
76+
cacheResponseIdKey: cacheResponseIdKey,
77+
cacheResponseContainerKey: cacheResponseContainerKey,
78+
);
5179

5280
return models.Team.fromMap(res.data);
5381

@@ -67,7 +95,21 @@ class Teams extends Service {
6795
'content-type': 'application/json',
6896
};
6997

70-
final res = await client.call(HttpMethod.get, path: path, params: params, headers: headers);
98+
final cacheModel = '/teams'.replaceAll('{teamId}', teamId);
99+
final cacheKey = teamId;
100+
final cacheResponseIdKey = '\$id';
101+
final cacheResponseContainerKey = '';
102+
103+
final res = await client.call(
104+
HttpMethod.get,
105+
path: path,
106+
params: params,
107+
headers: headers,
108+
cacheModel: cacheModel,
109+
cacheKey: cacheKey,
110+
cacheResponseIdKey: cacheResponseIdKey,
111+
cacheResponseContainerKey: cacheResponseContainerKey,
112+
);
71113

72114
return models.Team.fromMap(res.data);
73115

@@ -89,7 +131,21 @@ class Teams extends Service {
89131
'content-type': 'application/json',
90132
};
91133

92-
final res = await client.call(HttpMethod.put, path: path, params: params, headers: headers);
134+
final cacheModel = '/teams'.replaceAll('{teamId}', teamId);
135+
final cacheKey = teamId;
136+
final cacheResponseIdKey = '\$id';
137+
final cacheResponseContainerKey = '';
138+
139+
final res = await client.call(
140+
HttpMethod.put,
141+
path: path,
142+
params: params,
143+
headers: headers,
144+
cacheModel: cacheModel,
145+
cacheKey: cacheKey,
146+
cacheResponseIdKey: cacheResponseIdKey,
147+
cacheResponseContainerKey: cacheResponseContainerKey,
148+
);
93149

94150
return models.Team.fromMap(res.data);
95151

@@ -110,7 +166,21 @@ class Teams extends Service {
110166
'content-type': 'application/json',
111167
};
112168

113-
final res = await client.call(HttpMethod.delete, path: path, params: params, headers: headers);
169+
final cacheModel = '';
170+
final cacheKey = '';
171+
final cacheResponseIdKey = '\$id';
172+
final cacheResponseContainerKey = '';
173+
174+
final res = await client.call(
175+
HttpMethod.delete,
176+
path: path,
177+
params: params,
178+
headers: headers,
179+
cacheModel: cacheModel,
180+
cacheKey: cacheKey,
181+
cacheResponseIdKey: cacheResponseIdKey,
182+
cacheResponseContainerKey: cacheResponseContainerKey,
183+
);
114184

115185
return res.data;
116186

@@ -133,7 +203,21 @@ class Teams extends Service {
133203
'content-type': 'application/json',
134204
};
135205

136-
final res = await client.call(HttpMethod.get, path: path, params: params, headers: headers);
206+
final cacheModel = '/teams/{teamId}/memberships'.replaceAll('{teamId}', teamId);
207+
final cacheKey = '';
208+
final cacheResponseIdKey = '\$id';
209+
final cacheResponseContainerKey = 'memberships';
210+
211+
final res = await client.call(
212+
HttpMethod.get,
213+
path: path,
214+
params: params,
215+
headers: headers,
216+
cacheModel: cacheModel,
217+
cacheKey: cacheKey,
218+
cacheResponseIdKey: cacheResponseIdKey,
219+
cacheResponseContainerKey: cacheResponseContainerKey,
220+
);
137221

138222
return models.MembershipList.fromMap(res.data);
139223

@@ -171,7 +255,21 @@ class Teams extends Service {
171255
'content-type': 'application/json',
172256
};
173257

174-
final res = await client.call(HttpMethod.post, path: path, params: params, headers: headers);
258+
final cacheModel = '';
259+
final cacheKey = '';
260+
final cacheResponseIdKey = '\$id';
261+
final cacheResponseContainerKey = '';
262+
263+
final res = await client.call(
264+
HttpMethod.post,
265+
path: path,
266+
params: params,
267+
headers: headers,
268+
cacheModel: cacheModel,
269+
cacheKey: cacheKey,
270+
cacheResponseIdKey: cacheResponseIdKey,
271+
cacheResponseContainerKey: cacheResponseContainerKey,
272+
);
175273

176274
return models.Membership.fromMap(res.data);
177275

@@ -192,7 +290,21 @@ class Teams extends Service {
192290
'content-type': 'application/json',
193291
};
194292

195-
final res = await client.call(HttpMethod.get, path: path, params: params, headers: headers);
293+
final cacheModel = '/teams/{teamId}/memberships'.replaceAll('{teamId}', teamId).replaceAll('{membershipId}', membershipId);
294+
final cacheKey = membershipId;
295+
final cacheResponseIdKey = '\$id';
296+
final cacheResponseContainerKey = '';
297+
298+
final res = await client.call(
299+
HttpMethod.get,
300+
path: path,
301+
params: params,
302+
headers: headers,
303+
cacheModel: cacheModel,
304+
cacheKey: cacheKey,
305+
cacheResponseIdKey: cacheResponseIdKey,
306+
cacheResponseContainerKey: cacheResponseContainerKey,
307+
);
196308

197309
return models.Membership.fromMap(res.data);
198310

@@ -215,7 +327,21 @@ class Teams extends Service {
215327
'content-type': 'application/json',
216328
};
217329

218-
final res = await client.call(HttpMethod.patch, path: path, params: params, headers: headers);
330+
final cacheModel = '';
331+
final cacheKey = '';
332+
final cacheResponseIdKey = '\$id';
333+
final cacheResponseContainerKey = '';
334+
335+
final res = await client.call(
336+
HttpMethod.patch,
337+
path: path,
338+
params: params,
339+
headers: headers,
340+
cacheModel: cacheModel,
341+
cacheKey: cacheKey,
342+
cacheResponseIdKey: cacheResponseIdKey,
343+
cacheResponseContainerKey: cacheResponseContainerKey,
344+
);
219345

220346
return models.Membership.fromMap(res.data);
221347

@@ -237,7 +363,21 @@ class Teams extends Service {
237363
'content-type': 'application/json',
238364
};
239365

240-
final res = await client.call(HttpMethod.delete, path: path, params: params, headers: headers);
366+
final cacheModel = '';
367+
final cacheKey = '';
368+
final cacheResponseIdKey = '\$id';
369+
final cacheResponseContainerKey = '';
370+
371+
final res = await client.call(
372+
HttpMethod.delete,
373+
path: path,
374+
params: params,
375+
headers: headers,
376+
cacheModel: cacheModel,
377+
cacheKey: cacheKey,
378+
cacheResponseIdKey: cacheResponseIdKey,
379+
cacheResponseContainerKey: cacheResponseContainerKey,
380+
);
241381

242382
return res.data;
243383

@@ -265,7 +405,21 @@ class Teams extends Service {
265405
'content-type': 'application/json',
266406
};
267407

268-
final res = await client.call(HttpMethod.patch, path: path, params: params, headers: headers);
408+
final cacheModel = '';
409+
final cacheKey = '';
410+
final cacheResponseIdKey = '\$id';
411+
final cacheResponseContainerKey = '';
412+
413+
final res = await client.call(
414+
HttpMethod.patch,
415+
path: path,
416+
params: params,
417+
headers: headers,
418+
cacheModel: cacheModel,
419+
cacheKey: cacheKey,
420+
cacheResponseIdKey: cacheResponseIdKey,
421+
cacheResponseContainerKey: cacheResponseContainerKey,
422+
);
269423

270424
return models.Membership.fromMap(res.data);
271425

‎lib/src/client.dart

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
1-
import 'enums.dart';
21
import 'client_stub.dart'
32
if (dart.library.html) 'client_browser.dart'
43
if (dart.library.io) 'client_io.dart';
4+
import 'enums.dart';
55
import 'response.dart';
66
import 'upload_progress.dart';
77

88
abstract class Client {
9-
static const int CHUNK_SIZE = 5*1024*1024;
9+
static const int CHUNK_SIZE = 5 * 1024 * 1024;
1010
late Map<String, String> config;
1111
late String _endPoint;
1212
late String? _endPointRealtime;
1313

1414
String get endPoint => _endPoint;
1515
String? get endPointRealtime => _endPointRealtime;
1616

17-
factory Client(
18-
{String endPoint = 'https://HOSTNAME/v1',
19-
bool selfSigned = false}) =>
17+
factory Client({
18+
String endPoint = 'https://HOSTNAME/v1',
19+
bool selfSigned = false,
20+
}) =>
2021
createClient(endPoint: endPoint, selfSigned: selfSigned);
2122

2223
Future webAuth(Uri url, {String? callbackUrlScheme});
@@ -36,18 +37,35 @@ abstract class Client {
3637

3738
Client setEndPointRealtime(String endPoint);
3839

39-
/// Your project ID
40+
/// Your project ID
4041
Client setProject(value);
41-
/// Your secret JSON Web Token
42+
/// Your secret JSON Web Token
4243
Client setJWT(value);
4344
Client setLocale(value);
4445

4546
Client addHeader(String key, String value);
4647

47-
Future<Response> call(HttpMethod method, {
48+
Future<Response> call(
49+
HttpMethod method, {
4850
String path = '',
4951
Map<String, String> headers = const {},
5052
Map<String, dynamic> params = const {},
5153
ResponseType? responseType,
54+
String cacheModel = '',
55+
String cacheKey = '',
56+
String cacheResponseIdKey = '',
57+
String cacheResponseContainerKey = '',
58+
Map<String, Object?>? previous,
59+
});
60+
61+
Future<Client> setOfflinePersistency({
62+
bool status = true,
63+
void Function(Object)? onWriteQueueError,
5264
});
65+
66+
bool getOfflinePersistency();
67+
68+
Client setOfflineCacheSize(int kbytes);
69+
70+
int getOfflineCacheSize();
5371
}

‎lib/src/client_base.dart

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import 'response.dart';
21
import 'client.dart';
32
import 'enums.dart';
3+
import 'response.dart';
44

5-
abstract class ClientBase implements Client {
6-
/// Your project ID
5+
abstract class ClientBase implements Client {
6+
/// Your project ID
77
@override
88
ClientBase setProject(value);
9-
/// Your secret JSON Web Token
9+
10+
/// Your secret JSON Web Token
1011
@override
1112
ClientBase setJWT(value);
13+
1214
@override
1315
ClientBase setLocale(value);
1416

@@ -31,5 +33,25 @@ abstract class ClientBase implements Client {
3133
Map<String, String> headers = const {},
3234
Map<String, dynamic> params = const {},
3335
ResponseType? responseType,
36+
String cacheModel = '',
37+
String cacheKey = '',
38+
String cacheResponseIdKey = '',
39+
String cacheResponseContainerKey = '',
40+
Map<String, Object?>? previous,
41+
});
42+
43+
@override
44+
Future<ClientBase> setOfflinePersistency({
45+
bool status = true,
46+
void Function(Object)? onWriteQueueError,
3447
});
48+
49+
@override
50+
bool getOfflinePersistency();
51+
52+
@override
53+
ClientBase setOfflineCacheSize(int kbytes);
54+
55+
@override
56+
int getOfflineCacheSize();
3557
}

‎lib/src/client_browser.dart

Lines changed: 170 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,37 @@
1+
import 'dart:io';
12
import 'dart:math';
3+
24
import 'package:flutter/foundation.dart';
35
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
4-
import 'package:http/http.dart' as http;
56
import 'package:http/browser_client.dart';
7+
import 'package:http/http.dart' as http;
68
import 'package:universal_html/html.dart' as html;
9+
10+
import 'client_base.dart';
711
import 'client_mixin.dart';
12+
import 'client_offline_mixin.dart';
813
import 'enums.dart';
914
import 'exception.dart';
10-
import 'client_base.dart';
1115
import 'input_file.dart';
12-
import 'upload_progress.dart';
1316
import 'response.dart';
17+
import 'upload_progress.dart';
1418

1519
ClientBase createClient({
1620
required String endPoint,
1721
required bool selfSigned,
1822
}) =>
1923
ClientBrowser(endPoint: endPoint, selfSigned: selfSigned);
2024

21-
class ClientBrowser extends ClientBase with ClientMixin {
22-
static const int CHUNK_SIZE = 5*1024*1024;
25+
class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin {
26+
static const int CHUNK_SIZE = 5 * 1024 * 1024;
2327
String _endPoint;
2428
Map<String, String>? _headers;
2529
@override
2630
late Map<String, String> config;
2731
late BrowserClient _httpClient;
2832
String? _endPointRealtime;
33+
bool _offlinePersistency = false;
34+
int _maxCacheSize = 40000; // 40MB
2935

3036
@override
3137
String? get endPointRealtime => _endPointRealtime;
@@ -43,8 +49,8 @@ class ClientBrowser extends ClientBase with ClientMixin {
4349
'x-sdk-name': 'Flutter',
4450
'x-sdk-platform': 'client',
4551
'x-sdk-language': 'flutter',
46-
'x-sdk-version': '8.3.0',
47-
'X-Appwrite-Response-Format' : '1.0.0',
52+
'x-sdk-version': '9.0.0',
53+
'X-Appwrite-Response-Format': '1.0.0',
4854
};
4955

5056
config = {};
@@ -57,26 +63,28 @@ class ClientBrowser extends ClientBase with ClientMixin {
5763
@override
5864
String get endPoint => _endPoint;
5965

60-
/// Your project ID
61-
@override
62-
ClientBrowser setProject(value) {
63-
config['project'] = value;
64-
addHeader('X-Appwrite-Project', value);
65-
return this;
66-
}
67-
/// Your secret JSON Web Token
68-
@override
69-
ClientBrowser setJWT(value) {
70-
config['jWT'] = value;
71-
addHeader('X-Appwrite-JWT', value);
72-
return this;
73-
}
74-
@override
75-
ClientBrowser setLocale(value) {
76-
config['locale'] = value;
77-
addHeader('X-Appwrite-Locale', value);
78-
return this;
79-
}
66+
/// Your project ID
67+
@override
68+
ClientBrowser setProject(value) {
69+
config['project'] = value;
70+
addHeader('X-Appwrite-Project', value);
71+
return this;
72+
}
73+
74+
/// Your secret JSON Web Token
75+
@override
76+
ClientBrowser setJWT(value) {
77+
config['jWT'] = value;
78+
addHeader('X-Appwrite-JWT', value);
79+
return this;
80+
}
81+
82+
@override
83+
ClientBrowser setLocale(value) {
84+
config['locale'] = value;
85+
addHeader('X-Appwrite-Locale', value);
86+
return this;
87+
}
8088

8189
@override
8290
ClientBrowser setSelfSigned({bool status = true}) {
@@ -98,6 +106,38 @@ class ClientBrowser extends ClientBase with ClientMixin {
98106
return this;
99107
}
100108

109+
bool getOfflinePersistency() {
110+
return _offlinePersistency;
111+
}
112+
113+
@override
114+
Future<ClientBrowser> setOfflinePersistency(
115+
{bool status = true, void Function(Object)? onWriteQueueError}) async {
116+
_offlinePersistency = status;
117+
118+
if (_offlinePersistency) {
119+
await initOffline(
120+
call: call,
121+
onWriteQueueError: onWriteQueueError,
122+
getOfflineCacheSize: getOfflineCacheSize,
123+
);
124+
}
125+
126+
return this;
127+
}
128+
129+
@override
130+
ClientBrowser setOfflineCacheSize(int kbytes) {
131+
_maxCacheSize = kbytes * 1000;
132+
133+
return this;
134+
}
135+
136+
@override
137+
int getOfflineCacheSize() {
138+
return _maxCacheSize;
139+
}
140+
101141
@override
102142
ClientBrowser addHeader(String key, String value) {
103143
_headers![key] = value;
@@ -131,7 +171,11 @@ class ClientBrowser extends ClientBase with ClientMixin {
131171

132172
late Response res;
133173
if (size <= CHUNK_SIZE) {
134-
params[paramName] = http.MultipartFile.fromBytes(paramName, file.bytes!, filename: file.filename);
174+
params[paramName] = http.MultipartFile.fromBytes(
175+
paramName,
176+
file.bytes!,
177+
filename: file.filename,
178+
);
135179
return call(
136180
HttpMethod.post,
137181
path: path,
@@ -158,8 +202,11 @@ class ClientBrowser extends ClientBase with ClientMixin {
158202
var chunk;
159203
final end = min(offset + CHUNK_SIZE, size);
160204
chunk = file.bytes!.getRange(offset, end).toList();
161-
params[paramName] =
162-
http.MultipartFile.fromBytes(paramName, chunk, filename: file.filename);
205+
params[paramName] = http.MultipartFile.fromBytes(
206+
paramName,
207+
chunk,
208+
filename: file.filename,
209+
);
163210
headers['content-range'] =
164211
'bytes $offset-${min<int>(((offset + CHUNK_SIZE) - 1), size)}/$size';
165212
res = await call(HttpMethod.post,
@@ -187,6 +234,98 @@ class ClientBrowser extends ClientBase with ClientMixin {
187234
Map<String, String> headers = const {},
188235
Map<String, dynamic> params = const {},
189236
ResponseType? responseType,
237+
String cacheModel = '',
238+
String cacheKey = '',
239+
String cacheResponseIdKey = '',
240+
String cacheResponseContainerKey = '',
241+
Map<String, Object?>? previous,
242+
}) async {
243+
while (true) {
244+
final uri = Uri.parse(endPoint + path);
245+
246+
http.BaseRequest request = prepareRequest(
247+
method,
248+
uri: uri,
249+
headers: {..._headers!, ...headers},
250+
params: params,
251+
);
252+
253+
if (getOfflinePersistency() && !isOnline.value) {
254+
await checkOnlineStatus();
255+
}
256+
257+
if (cacheModel.isNotEmpty &&
258+
getOfflinePersistency() &&
259+
!isOnline.value &&
260+
responseType != ResponseType.bytes) {
261+
return handleOfflineRequest(
262+
uri: uri,
263+
method: method,
264+
call: call,
265+
path: path,
266+
headers: headers,
267+
params: params,
268+
responseType: responseType,
269+
cacheModel: cacheModel,
270+
cacheKey: cacheKey,
271+
cacheResponseIdKey: cacheResponseIdKey,
272+
cacheResponseContainerKey: cacheResponseContainerKey,
273+
);
274+
}
275+
276+
try {
277+
final response = await send(
278+
method,
279+
path: path,
280+
headers: headers,
281+
params: params,
282+
responseType: responseType,
283+
cacheModel: cacheModel,
284+
cacheKey: cacheKey,
285+
cacheResponseIdKey: cacheResponseIdKey,
286+
cacheResponseContainerKey: cacheResponseContainerKey,
287+
);
288+
289+
if (getOfflinePersistency()) {
290+
cacheResponse(
291+
cacheModel: cacheModel,
292+
cacheKey: cacheKey,
293+
cacheResponseIdKey: cacheResponseIdKey,
294+
request: request,
295+
response: response,
296+
);
297+
}
298+
299+
return response;
300+
} on AppwriteException catch (e) {
301+
if ((e.message != "Network is unreachable" &&
302+
!(e.message?.contains("Failed host lookup") ?? false)) ||
303+
!getOfflinePersistency()) {
304+
rethrow;
305+
}
306+
isOnline.value = false;
307+
} on SocketException catch (_) {
308+
if (!getOfflinePersistency()) {
309+
rethrow;
310+
}
311+
isOnline.value = false;
312+
} catch (e) {
313+
throw AppwriteException(e.toString());
314+
}
315+
}
316+
}
317+
318+
Future<Response> send(
319+
HttpMethod method, {
320+
String path = '',
321+
Map<String, String> headers = const {},
322+
Map<String, dynamic> params = const {},
323+
ResponseType? responseType,
324+
String cacheModel = '',
325+
String cacheKey = '',
326+
String cacheResponseIdKey = '',
327+
String cacheResponseContainerKey = '',
328+
Map<String, Object?>? previous,
190329
}) async {
191330
await init();
192331

@@ -219,7 +358,7 @@ class ClientBrowser extends ClientBase with ClientMixin {
219358

220359
@override
221360
Future webAuth(Uri url, {String? callbackUrlScheme}) {
222-
return FlutterWebAuth2.authenticate(
361+
return FlutterWebAuth2.authenticate(
223362
url: url.toString(),
224363
callbackUrlScheme: "appwrite-callback-" + config['project']!,
225364
);

‎lib/src/client_io.dart

Lines changed: 202 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
1+
import 'dart:async';
12
import 'dart:io';
23
import 'dart:math';
4+
35
import 'package:cookie_jar/cookie_jar.dart';
46
import 'package:device_info_plus/device_info_plus.dart';
7+
import 'package:flutter/foundation.dart';
8+
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
59
import 'package:http/http.dart' as http;
610
import 'package:http/io_client.dart';
711
import 'package:package_info_plus/package_info_plus.dart';
812
import 'package:path_provider/path_provider.dart';
9-
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
10-
import 'client_mixin.dart';
13+
1114
import 'client_base.dart';
15+
import 'client_mixin.dart';
16+
import 'client_offline_mixin.dart';
1217
import 'cookie_manager.dart';
1318
import 'enums.dart';
1419
import 'exception.dart';
20+
import 'input_file.dart';
1521
import 'interceptor.dart';
1622
import 'response.dart';
17-
import 'package:flutter/foundation.dart';
18-
import 'input_file.dart';
1923
import 'upload_progress.dart';
2024

2125
ClientBase createClient({
@@ -27,8 +31,8 @@ ClientBase createClient({
2731
selfSigned: selfSigned,
2832
);
2933

30-
class ClientIO extends ClientBase with ClientMixin {
31-
static const int CHUNK_SIZE = 5*1024*1024;
34+
class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin {
35+
static const int CHUNK_SIZE = 5 * 1024 * 1024;
3236
String _endPoint;
3337
Map<String, String>? _headers;
3438
@override
@@ -47,6 +51,8 @@ class ClientIO extends ClientBase with ClientMixin {
4751
CookieJar get cookieJar => _cookieJar;
4852
@override
4953
String? get endPointRealtime => _endPointRealtime;
54+
bool _offlinePersistency = false;
55+
int _maxCacheSize = 40000; // 40MB
5056

5157
ClientIO({
5258
String endPoint = 'https://HOSTNAME/v1',
@@ -64,8 +70,8 @@ class ClientIO extends ClientBase with ClientMixin {
6470
'x-sdk-name': 'Flutter',
6571
'x-sdk-platform': 'client',
6672
'x-sdk-language': 'flutter',
67-
'x-sdk-version': '8.3.0',
68-
'X-Appwrite-Response-Format' : '1.0.0',
73+
'x-sdk-version': '9.0.0',
74+
'X-Appwrite-Response-Format': '1.0.0',
6975
};
7076

7177
config = {};
@@ -86,26 +92,28 @@ class ClientIO extends ClientBase with ClientMixin {
8692
return dir;
8793
}
8894

89-
/// Your project ID
90-
@override
91-
ClientIO setProject(value) {
92-
config['project'] = value;
93-
addHeader('X-Appwrite-Project', value);
94-
return this;
95-
}
96-
/// Your secret JSON Web Token
97-
@override
98-
ClientIO setJWT(value) {
99-
config['jWT'] = value;
100-
addHeader('X-Appwrite-JWT', value);
101-
return this;
102-
}
103-
@override
104-
ClientIO setLocale(value) {
105-
config['locale'] = value;
106-
addHeader('X-Appwrite-Locale', value);
107-
return this;
108-
}
95+
/// Your project ID
96+
@override
97+
ClientIO setProject(value) {
98+
config['project'] = value;
99+
addHeader('X-Appwrite-Project', value);
100+
return this;
101+
}
102+
103+
/// Your secret JSON Web Token
104+
@override
105+
ClientIO setJWT(value) {
106+
config['jWT'] = value;
107+
addHeader('X-Appwrite-JWT', value);
108+
return this;
109+
}
110+
111+
@override
112+
ClientIO setLocale(value) {
113+
config['locale'] = value;
114+
addHeader('X-Appwrite-Locale', value);
115+
return this;
116+
}
109117

110118
@override
111119
ClientIO setSelfSigned({bool status = true}) {
@@ -130,6 +138,38 @@ class ClientIO extends ClientBase with ClientMixin {
130138
return this;
131139
}
132140

141+
bool getOfflinePersistency() {
142+
return _offlinePersistency;
143+
}
144+
145+
@override
146+
Future<ClientIO> setOfflinePersistency(
147+
{bool status = true, void Function(Object)? onWriteQueueError}) async {
148+
_offlinePersistency = status;
149+
150+
if (_offlinePersistency) {
151+
await initOffline(
152+
call: call,
153+
onWriteQueueError: onWriteQueueError,
154+
getOfflineCacheSize: getOfflineCacheSize,
155+
);
156+
}
157+
158+
return this;
159+
}
160+
161+
@override
162+
ClientIO setOfflineCacheSize(int kbytes) {
163+
_maxCacheSize = kbytes * 1000;
164+
165+
return this;
166+
}
167+
168+
@override
169+
int getOfflineCacheSize() {
170+
return _maxCacheSize;
171+
}
172+
133173
@override
134174
ClientIO addHeader(String key, String value) {
135175
_headers![key] = value;
@@ -138,7 +178,7 @@ class ClientIO extends ClientBase with ClientMixin {
138178
}
139179

140180
Future init() async {
141-
if(_initProgress) return;
181+
if (_initProgress) return;
142182
_initProgress = true;
143183
final Directory cookieDir = await _getCookiePath();
144184
_cookieJar = PersistCookieJar(storage: FileStorage(cookieDir.path));
@@ -147,8 +187,10 @@ class ClientIO extends ClientBase with ClientMixin {
147187
var device = '';
148188
try {
149189
PackageInfo packageInfo = await PackageInfo.fromPlatform();
150-
addHeader('Origin',
151-
'appwrite-${Platform.operatingSystem}://${packageInfo.packageName}');
190+
addHeader(
191+
'Origin',
192+
'appwrite-${Platform.operatingSystem}://${packageInfo.packageName}',
193+
);
152194

153195
//creating custom user agent
154196
DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
@@ -175,12 +217,13 @@ class ClientIO extends ClientBase with ClientMixin {
175217
device = '(Macintosh; ${macinfo.model})';
176218
}
177219
addHeader(
178-
'user-agent', '${packageInfo.packageName}/${packageInfo.version} $device');
220+
'user-agent',
221+
'${packageInfo.packageName}/${packageInfo.version} $device',
222+
);
179223
} catch (e) {
180224
debugPrint('Error getting device info: $e');
181225
device = Platform.operatingSystem;
182-
addHeader(
183-
'user-agent', '$device');
226+
addHeader('user-agent', '$device');
184227
}
185228

186229
_initialized = true;
@@ -246,11 +289,16 @@ class ClientIO extends ClientBase with ClientMixin {
246289
if (size <= CHUNK_SIZE) {
247290
if (file.path != null) {
248291
params[paramName] = await http.MultipartFile.fromPath(
249-
paramName, file.path!,
250-
filename: file.filename);
292+
paramName,
293+
file.path!,
294+
filename: file.filename,
295+
);
251296
} else {
252-
params[paramName] = http.MultipartFile.fromBytes(paramName, file.bytes!,
253-
filename: file.filename);
297+
params[paramName] = http.MultipartFile.fromBytes(
298+
paramName,
299+
file.bytes!,
300+
filename: file.filename,
301+
);
254302
}
255303
return call(
256304
HttpMethod.post,
@@ -281,20 +329,27 @@ class ClientIO extends ClientBase with ClientMixin {
281329
}
282330

283331
while (offset < size) {
284-
var chunk;
332+
List<int> chunk = [];
285333
if (file.bytes != null) {
286-
final end = min(offset + CHUNK_SIZE-1, size-1);
334+
final end = min(offset + CHUNK_SIZE - 1, size - 1);
287335
chunk = file.bytes!.getRange(offset, end).toList();
288336
} else {
289337
raf!.setPositionSync(offset);
290338
chunk = raf.readSync(CHUNK_SIZE);
291339
}
292-
params[paramName] =
293-
http.MultipartFile.fromBytes(paramName, chunk, filename: file.filename);
340+
params[paramName] = http.MultipartFile.fromBytes(
341+
paramName,
342+
chunk,
343+
filename: file.filename,
344+
);
294345
headers['content-range'] =
295346
'bytes $offset-${min<int>(((offset + CHUNK_SIZE) - 1), size)}/$size';
296-
res = await call(HttpMethod.post,
297-
path: path, headers: headers, params: params);
347+
res = await call(
348+
HttpMethod.post,
349+
path: path,
350+
headers: headers,
351+
params: params,
352+
);
298353
offset += CHUNK_SIZE;
299354
if (offset < size) {
300355
headers['x-appwrite-id'] = res.data['\$id'];
@@ -316,7 +371,9 @@ class ClientIO extends ClientBase with ClientMixin {
316371
Future webAuth(Uri url, {String? callbackUrlScheme}) {
317372
return FlutterWebAuth2.authenticate(
318373
url: url.toString(),
319-
callbackUrlScheme: callbackUrlScheme != null && Platform.isWindows ? callbackUrlScheme : "appwrite-callback-" + config['project']!,
374+
callbackUrlScheme: callbackUrlScheme != null && Platform.isWindows
375+
? callbackUrlScheme
376+
: "appwrite-callback-" + config['project']!,
320377
preferEphemeral: true,
321378
).then((value) async {
322379
Uri url = Uri.parse(value);
@@ -336,13 +393,17 @@ class ClientIO extends ClientBase with ClientMixin {
336393
});
337394
}
338395

339-
@override
340-
Future<Response> call(
396+
Future<Response> send(
341397
HttpMethod method, {
342398
String path = '',
343399
Map<String, String> headers = const {},
344400
Map<String, dynamic> params = const {},
345401
ResponseType? responseType,
402+
String cacheModel = '',
403+
String cacheKey = '',
404+
String cacheResponseIdKey = '',
405+
String cacheResponseContainerKey = '',
406+
Map<String, Object?>? previous,
346407
}) async {
347408
while (!_initialized && _initProgress) {
348409
await Future.delayed(Duration(milliseconds: 10));
@@ -351,29 +412,119 @@ class ClientIO extends ClientBase with ClientMixin {
351412
await init();
352413
}
353414

354-
late http.Response res;
415+
final uri = Uri.parse(_endPoint + path);
355416
http.BaseRequest request = prepareRequest(
356417
method,
357-
uri: Uri.parse(_endPoint + path),
418+
uri: uri,
358419
headers: {..._headers!, ...headers},
359420
params: params,
360421
);
361422

362423
try {
363424
request = await _interceptRequest(request);
364425
final streamedResponse = await _httpClient.send(request);
365-
res = await toResponse(streamedResponse);
426+
http.Response res = await toResponse(streamedResponse);
366427
res = await _interceptResponse(res);
367428

368-
return prepareResponse(
429+
final response = prepareResponse(
369430
res,
370431
responseType: responseType,
371432
);
433+
434+
return response;
372435
} catch (e) {
373436
if (e is AppwriteException) {
374437
rethrow;
375438
}
376439
throw AppwriteException(e.toString());
377440
}
378441
}
442+
443+
@override
444+
Future<Response> call(
445+
HttpMethod method, {
446+
String path = '',
447+
Map<String, String> headers = const {},
448+
Map<String, dynamic> params = const {},
449+
ResponseType? responseType,
450+
String cacheModel = '',
451+
String cacheKey = '',
452+
String cacheResponseIdKey = '',
453+
String cacheResponseContainerKey = '',
454+
Map<String, Object?>? previous,
455+
}) async {
456+
while (true) {
457+
final uri = Uri.parse(endPoint + path);
458+
459+
http.BaseRequest request = prepareRequest(
460+
method,
461+
uri: uri,
462+
headers: {..._headers!, ...headers},
463+
params: params,
464+
);
465+
466+
if (getOfflinePersistency() && !isOnline.value) {
467+
await checkOnlineStatus();
468+
}
469+
470+
if (cacheModel.isNotEmpty &&
471+
getOfflinePersistency() &&
472+
!isOnline.value &&
473+
responseType != ResponseType.bytes) {
474+
return handleOfflineRequest(
475+
uri: uri,
476+
method: method,
477+
call: call,
478+
path: path,
479+
headers: headers,
480+
params: params,
481+
responseType: responseType,
482+
cacheModel: cacheModel,
483+
cacheKey: cacheKey,
484+
cacheResponseIdKey: cacheResponseIdKey,
485+
cacheResponseContainerKey: cacheResponseContainerKey,
486+
);
487+
}
488+
489+
try {
490+
final response = await send(
491+
method,
492+
path: path,
493+
headers: headers,
494+
params: params,
495+
responseType: responseType,
496+
cacheModel: cacheModel,
497+
cacheKey: cacheKey,
498+
cacheResponseIdKey: cacheResponseIdKey,
499+
cacheResponseContainerKey: cacheResponseContainerKey,
500+
);
501+
502+
if (getOfflinePersistency()) {
503+
cacheResponse(
504+
cacheModel: cacheModel,
505+
cacheKey: cacheKey,
506+
cacheResponseIdKey: cacheResponseIdKey,
507+
request: request,
508+
response: response,
509+
);
510+
}
511+
512+
return response;
513+
} on AppwriteException catch (e) {
514+
if ((e.message != "Network is unreachable" &&
515+
!(e.message?.contains("Failed host lookup") ?? false)) ||
516+
!getOfflinePersistency()) {
517+
rethrow;
518+
}
519+
isOnline.value = false;
520+
} on SocketException catch (_) {
521+
if (!getOfflinePersistency()) {
522+
rethrow;
523+
}
524+
isOnline.value = false;
525+
} catch (e) {
526+
throw AppwriteException(e.toString());
527+
}
528+
}
529+
}
379530
}

‎lib/src/client_offline_mixin.dart

Lines changed: 660 additions & 0 deletions
Large diffs are not rendered by default.

‎lib/src/offline_db.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import 'package:flutter/foundation.dart';
2+
import 'package:sembast/sembast.dart';
3+
import 'package:sembast_sqflite/sembast_sqflite.dart';
4+
import 'package:sembast_web/sembast_web.dart';
5+
import 'package:sqflite/sqflite.dart' as sqflite;
6+
7+
class OfflineDatabase {
8+
static final OfflineDatabase instance = OfflineDatabase._internal();
9+
Database? _db;
10+
11+
OfflineDatabase._internal();
12+
13+
Future<Database> db() async {
14+
if (_db == null) {
15+
final factory = kIsWeb
16+
? databaseFactoryWeb
17+
: getDatabaseFactorySqflite(sqflite.databaseFactory);
18+
_db = await factory.openDatabase('appwrite.db');
19+
}
20+
return _db!;
21+
}
22+
}

‎pubspec.yaml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
name: appwrite
2-
version: 8.3.0
2+
version: 9.0.0
33
description: Appwrite is an open-source self-hosted backend server that abstract and simplify complex and repetitive development tasks behind a very simple REST API
44
homepage: https://appwrite.io
55
repository: https://github.com/appwrite/sdk-for-flutter
66
issue_tracker: https://github.com/appwrite/sdk-generator/issues
77
documentation: https://appwrite.io/support
88
environment:
9-
sdk: '>=2.17.0 <3.0.0'
9+
sdk: ">=2.17.0 <3.0.0"
1010

1111
dependencies:
1212
flutter:
@@ -19,6 +19,12 @@ dependencies:
1919
path_provider: ^2.0.13
2020
web_socket_channel: ^2.3.0
2121
universal_html: ^2.0.9
22+
connectivity_plus: ^2.3.9
23+
path: ^1.8.2
24+
sembast: ^3.4.0+6
25+
sembast_sqflite: ^2.1.0+1
26+
sembast_web: ^2.1.0+4
27+
sqflite: ^2.2.2
2228

2329
dev_dependencies:
2430
path_provider_platform_interface: ^2.0.5

0 commit comments

Comments
 (0)
Please sign in to comment.