Skip to content

Commit caf2fb9

Browse files
authored
feat(offline_first): add OfflineFirstDeletePolicy.localOnly (#633)
1 parent 5bf599e commit caf2fb9

5 files changed

Lines changed: 120 additions & 6 deletions

File tree

.vscode/settings.json

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,74 @@
11
{
2+
// Dart/Flutter specific settings
23
"dart.lineLength": 100,
4+
"dart.previewFlutterUiGuides": true,
5+
"dart.previewFlutterUiGuidesCustomTracking": true,
6+
"dart.debugSdkLibraries": false,
7+
// File handling
8+
"files.insertFinalNewline": true,
9+
"files.trimTrailingWhitespace": true,
10+
"files.exclude": {
11+
"**/.dart_tool/**": true,
12+
"**/build/**": true,
13+
"**/*.g.dart": true,
14+
"**/*.iml": true
15+
},
16+
// Explorer settings for better project navigation
17+
"explorer.fileNesting.enabled": true,
18+
"explorer.fileNesting.expand": false,
19+
"explorer.fileNesting.patterns": {
20+
"pubspec.yaml": "pubspec.lock,analysis_options.yaml,.metadata",
21+
"*.dart": "*.g.dart",
22+
"README.md": "CHANGELOG.md,LICENSE,CODEOWNERS"
23+
},
24+
// Editor formatting
325
"editor.formatOnSave": true,
426
"editor.codeActionsOnSave": {
527
"source.fixAll": "explicit",
628
"source.organizeImports": "explicit"
729
},
8-
"explorer.fileNesting.enabled": true,
9-
"explorer.fileNesting.expand": false,
10-
"files.insertFinalNewline": true
30+
"editor.rulers": [
31+
100
32+
],
33+
"editor.tabSize": 2,
34+
"editor.insertSpaces": true,
35+
"editor.detectIndentation": false,
36+
// Language specific settings
37+
"[dart]": {
38+
"editor.selectionHighlight": false,
39+
"editor.suggest.snippetsPreventQuickSuggestions": false,
40+
"editor.suggestSelection": "first",
41+
"editor.tabCompletion": "onlySnippets",
42+
"editor.wordBasedSuggestions": "off"
43+
},
44+
"[yaml]": {
45+
"editor.insertSpaces": true,
46+
"editor.tabSize": 2,
47+
"editor.autoIndent": "advanced"
48+
},
49+
"[json]": {
50+
"editor.defaultFormatter": "vscode.json-language-features"
51+
},
52+
"[markdown]": {
53+
"editor.wordWrap": "wordWrapColumn",
54+
"editor.wordWrapColumn": 80,
55+
"editor.quickSuggestions": {
56+
"comments": "off",
57+
"strings": "off",
58+
"other": "off"
59+
},
60+
"editor.suggest.showWords": false,
61+
"editor.formatOnSave": false
62+
},
63+
// Search settings
64+
"search.exclude": {
65+
"**/.dart_tool/**": true,
66+
"**/build/**": true,
67+
"**/node_modules/**": true,
68+
"**/.git/**": true
69+
},
70+
// Workbench settings
71+
"workbench.editorAssociations": {
72+
"*.md": "default"
73+
}
1174
}

docs/offline_first/policies.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ Delete local results before waiting for the remote provider to respond.
1212

1313
Delete local results after remote responds; local results are not deleted if remote responds with any exception.
1414

15+
### `localOnly`
16+
17+
Delete local results only; do not send any request to the remote provider.
18+
1519
## OfflineFirstGetPolicy
1620

1721
### `alwaysHydrate`
@@ -39,3 +43,7 @@ Save results to local before waiting for the remote provider to respond.
3943
### `requireRemote`
4044

4145
Save results to local after remote responds; local results are not saved if remote responds with any exception.
46+
47+
### `localOnly`
48+
49+
Save results to local only; do not send any request to the remote provider.

packages/brick_offline_first/lib/src/offline_first_policy.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ enum OfflineFirstDeletePolicy {
55

66
/// Delete local results after remote responds; local results are not deleted if remote responds with any exception
77
requireRemote,
8+
9+
/// Delete local results only; do not send any request to the remote provider
10+
localOnly,
811
}
912

1013
/// Data will **always** be returned from local providers and never directly
@@ -35,4 +38,7 @@ enum OfflineFirstUpsertPolicy {
3538
/// Save results to local after remote responds;
3639
/// local results are not saved if remote responds with any exception
3740
requireRemote,
41+
42+
/// Save results to local only; do not send any request to the remote provider
43+
localOnly,
3844
}

packages/brick_offline_first/lib/src/offline_first_repository.dart

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,19 @@ abstract class OfflineFirstRepository<TRepositoryModel extends OfflineFirstModel
114114

115115
final optimisticLocal = policy == OfflineFirstDeletePolicy.optimisticLocal;
116116
final requireRemote = policy == OfflineFirstDeletePolicy.requireRemote;
117+
final localOnly = policy == OfflineFirstDeletePolicy.localOnly;
117118

118119
var rowsDeleted = 0;
119120

120-
if (optimisticLocal) {
121+
if (optimisticLocal || localOnly) {
121122
rowsDeleted = await _deleteLocal<TModel>(instance, query: query);
122123
await notifySubscriptionsWithLocalData<TModel>();
123124
}
124125

126+
if (localOnly) {
127+
return rowsDeleted > 0;
128+
}
129+
125130
try {
126131
await remoteProvider.delete<TModel>(instance, query: query, repository: this);
127132
if (requireRemote) {
@@ -135,7 +140,6 @@ abstract class OfflineFirstRepository<TRepositoryModel extends OfflineFirstModel
135140
logger.warning('#delete socket failure: $e');
136141
if (requireRemote) rethrow;
137142
}
138-
139143
// ignore: unawaited_futures
140144
if (autoHydrate) hydrate<TModel>(query: query);
141145

@@ -416,12 +420,17 @@ abstract class OfflineFirstRepository<TRepositoryModel extends OfflineFirstModel
416420

417421
final optimisticLocal = policy == OfflineFirstUpsertPolicy.optimisticLocal;
418422
final requireRemote = policy == OfflineFirstUpsertPolicy.requireRemote;
423+
final localOnly = policy == OfflineFirstUpsertPolicy.localOnly;
419424

420-
if (optimisticLocal) {
425+
if (optimisticLocal || localOnly) {
421426
instance.primaryKey = await _upsertLocal<TModel>(instance, query: query);
422427
await notifySubscriptionsWithLocalData<TModel>();
423428
}
424429

430+
if (localOnly) {
431+
return instance;
432+
}
433+
425434
try {
426435
await remoteProvider.upsert<TModel>(instance, query: query, repository: this);
427436

packages/brick_offline_first/test/offline_first/offline_first_repository_test.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,18 @@ void main() {
5252
throwsA(const TypeMatcher<SocketException>()),
5353
);
5454
});
55+
56+
test('OfflineFirstDeletePolicy.localOnly', () async {
57+
final instance = Mounty(name: 'SqliteName');
58+
final upserted = await TestRepository().upsert<Mounty>(instance);
59+
expect(await TestRepository().sqliteProvider.get<Mounty>(), hasLength(1));
60+
(TestRepository().remoteProvider as TestProvider).methodsCalled.clear();
61+
62+
await TestRepository().delete<Mounty>(upserted, policy: OfflineFirstDeletePolicy.localOnly);
63+
64+
expect(await TestRepository().sqliteProvider.get<Mounty>(), isEmpty);
65+
expect((TestRepository().remoteProvider as TestProvider).methodsCalled, isEmpty);
66+
});
5567
});
5668

5769
group('#get', () {
@@ -185,6 +197,22 @@ void main() {
185197
throwsA(const TypeMatcher<SocketException>()),
186198
);
187199
});
200+
201+
test('OfflineFirstUpsertPolicy.localOnly', () async {
202+
final instance = Mounty(name: 'LocalOnlyMounty');
203+
(TestRepository().remoteProvider as TestProvider).methodsCalled.clear();
204+
205+
final result = await TestRepository()
206+
.upsert<Mounty>(instance, policy: OfflineFirstUpsertPolicy.localOnly);
207+
208+
expect(result.name, 'LocalOnlyMounty');
209+
expect(result.primaryKey, greaterThanOrEqualTo(1));
210+
211+
final sqliteResults = await TestRepository().sqliteProvider.get<Mounty>();
212+
expect(sqliteResults.where((m) => m.name == 'LocalOnlyMounty'), hasLength(1));
213+
214+
expect((TestRepository().remoteProvider as TestProvider).methodsCalled, isEmpty);
215+
});
188216
});
189217

190218
test('#reset', () async {

0 commit comments

Comments
 (0)