-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathapp_persistor.dart
135 lines (119 loc) · 5.01 KB
/
app_persistor.dart
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
import "dart:async";
import "dart:io";
import 'package:async_redux/async_redux.dart';
import "package:async_redux/local_json_persist.dart";
import 'package:flutter/foundation.dart';
import 'package:mobile_app_flutter_redux/client/infra/app_state.dart';
import 'package:mobile_app_flutter_redux/models/portfolio.dart';
import 'package:mobile_app_flutter_redux/models/ui.dart';
import 'package:mobile_app_flutter_redux/models/utils/map_deserialization_extension.dart';
/// Saves/Loads the state to/from the local device disk.
class AppPersistor extends Persistor<AppState> {
//
static const dbName_Portfolio = "portfolio";
static const dbName_Ui = "ui";
/// Read the saved state from the persistence. Should return null if the state is not yet
/// persisted. This method should be called only once, the app starts, before the store is
/// created. The state it returns may become the store's initial state. If some error
/// occurs while loading the info, we have to deal with it, by fixing the problem. In the worse
/// case, if we think the state is corrupted and cannot be fixed, one alternative is deleting
/// all persisted files and returning null.
@override
Future<AppState?> readState() async {
AppState? state;
try {
print('Reading state from disk.');
state = await _readFromDisk();
}
//
catch (error, stackTrace) {
// We should log this error, not throw it.
print('\n'
'Error while reading from the local persistence:\n'
'Error: $error.'
'StackTrace: $stackTrace.\n');
}
// If we managed to read the saved state, return it.
// It will later become the store's initial state.
if (state != null)
return state;
//
// When an error happens, state is null.
else {
print('Creating an empty state.');
// So, we delete the old corrupted state from disk.
await deleteState();
// And them recreate the empty state and save it to disk.
AppState state = AppState.initialState();
await saveInitialState(state);
// The empty state will later become the store's initial state.
return state;
}
}
Future<AppState?> _readFromDisk() async {
//
/// We are reading in sequence, but the correct here is reading both in parallel,
/// by using `Future.wait([...])`
Portfolio portfolio = await _readPortfolio();
Ui ui = await _readUi();
var state = AppState.initialState().copy(
portfolio: portfolio,
ui: ui,
);
print('State read from disk: $state.');
return state;
}
/// This method throws an exception if the file does not contain a valid Json for the Portfolio.
/// If that happens, the caller assumes the file is corrupted, and will treat the error.
Future<Portfolio> _readPortfolio() async {
print('Reading $dbName_Portfolio.db...');
LocalJsonPersist localPersist = LocalJsonPersist(dbName_Portfolio);
Object? result = await localPersist.load();
var portfolio = Portfolio.fromJson(result as Json?);
print('Read the Portfolio from disk: $portfolio');
return portfolio;
}
/// This method throws an exception if the file does not contain a valid Json for the Portfolio.
/// If that happens, the caller assumes the file is corrupted, and will treat the error.
Future<Ui> _readUi() async {
print('Reading $dbName_Ui.db...');
LocalJsonPersist localPersist = LocalJsonPersist(dbName_Ui);
Object? result = await localPersist.load();
var ui = Ui.fromJson(result as Json?);
print('Read the Ui from disk: $ui');
return ui;
}
@override
Future<void> deleteState() async {
print('Deleting the state from disk.');
var rootDir = await findRootDirForLocalPersist();
if (rootDir.existsSync()) await rootDir.delete(recursive: true);
}
/// Return the directory `LocalPersist` saves the files and create subdirectories.
@visibleForTesting
Future<Directory> findRootDirForLocalPersist() async {
// Hack to get the dir, since this info is not shared.
var fileInRoot = await LocalJsonPersist("file-in-root").file();
return fileInRoot.parent;
}
/// Here I compare the last saved state with the current state.
/// If the state changed, I save it to a file. I could have saved it to a database instead.
@override
Future<void> persistDifference({
required AppState? lastPersistedState,
required AppState newState,
}) async {
/// Here I compare the last saved Portfolio with the current Portfolio in the state.
/// If the Portfolio changed, I save it to a file. I could have saved it to a database instead.
if (newState.portfolio != lastPersistedState?.portfolio) {
print('Persisting the Portfolio to disk: ${newState.portfolio}');
var localPersist = LocalJsonPersist(dbName_Portfolio);
await localPersist.save(newState.portfolio.toJson());
}
if (newState.ui != lastPersistedState?.ui) {
print('Persisting the Ui to disk: ${newState.ui}');
var localPersist = LocalJsonPersist(dbName_Ui);
await localPersist.save(newState.ui.toJson());
}
}
}