@@ -75,7 +75,10 @@ abstract class GlobalStore extends ChangeNotifier {
75
75
email: account.email, apiKey: account.apiKey);
76
76
}
77
77
78
+ Map <int , PerAccountStore > get debugPerAccountStores => _perAccountStores;
78
79
final Map <int , PerAccountStore > _perAccountStores = {};
80
+
81
+ Map <int , Future <PerAccountStore >> get debugPerAccountStoresLoading => _perAccountStoresLoading;
79
82
final Map <int , Future <PerAccountStore >> _perAccountStoresLoading = {};
80
83
81
84
/// The store's per-account data for the given account, if already loaded.
@@ -142,8 +145,16 @@ abstract class GlobalStore extends ChangeNotifier {
142
145
/// This method should be called only by the implementation of [perAccount] .
143
146
/// Other callers interested in per-account data should use [perAccount]
144
147
/// and/or [perAccountSync] .
145
- Future <PerAccountStore > loadPerAccount (int accountId) {
146
- return doLoadPerAccount (accountId);
148
+ Future <PerAccountStore > loadPerAccount (int accountId) async {
149
+ assert (_accounts.containsKey (accountId));
150
+ final store = await doLoadPerAccount (accountId);
151
+ if (! _accounts.containsKey (accountId)) {
152
+ // [removeAccount] was called during [doLoadPerAccount].
153
+ // TODO close connection inside `.dispose` instead (once tests can adapt)
154
+ store..dispose ()..connection.close ();
155
+ throw AccountNotFoundException ();
156
+ }
157
+ return store;
147
158
}
148
159
149
160
/// Load per-account data for the given account, unconditionally.
@@ -197,10 +208,27 @@ abstract class GlobalStore extends ChangeNotifier {
197
208
/// Update an account in the underlying data store.
198
209
Future <void > doUpdateAccount (int accountId, AccountsCompanion data);
199
210
211
+ /// Remove an account from the store.
212
+ Future <void > removeAccount (int accountId) async {
213
+ await doRemoveAccount (accountId);
214
+ assert (_accounts.containsKey (accountId));
215
+ _accounts.remove (accountId);
216
+ _perAccountStores.remove (accountId)
217
+ // TODO close connection inside `.dispose` instead (once tests can adapt)
218
+ ? ..dispose ()..connection.close ();
219
+ _perAccountStoresLoading.remove (accountId);
220
+ notifyListeners ();
221
+ }
222
+
223
+ /// Remove an account from the underlying data store.
224
+ Future <void > doRemoveAccount (int accountId);
225
+
200
226
@override
201
227
String toString () => '${objectRuntimeType (this , 'GlobalStore' )}#${shortHash (this )}' ;
202
228
}
203
229
230
+ class AccountNotFoundException implements Exception {}
231
+
204
232
/// Store for the user's data for a given Zulip account.
205
233
///
206
234
/// This should always have a consistent snapshot of the state on the server,
@@ -337,8 +365,17 @@ class PerAccountStore extends ChangeNotifier with ChannelStore, MessageStore {
337
365
// Data attached to the self-account on the realm.
338
366
339
367
final int accountId;
368
+
369
+ /// The [Account] this store belongs to.
370
+ ///
371
+ /// Assumes the account hasn't been logged out.
372
+ /// If the account may have been logged out, don't use this;
373
+ /// use [maybeAccount] instead.
340
374
Account get account => _globalStore.getAccount (accountId)! ;
341
375
376
+ /// Like [account] , but for contexts where the account might be logged out.
377
+ Account ? get maybeAccount => _globalStore.getAccount (accountId);
378
+
342
379
/// Always equal to `account.userId` .
343
380
final int selfUserId;
344
381
@@ -426,6 +463,9 @@ class PerAccountStore extends ChangeNotifier with ChannelStore, MessageStore {
426
463
}
427
464
428
465
Future <void > handleEvent (Event event) async {
466
+ // Do nothing if the account has been logged out.
467
+ if (maybeAccount == null ) return ;
468
+
429
469
switch (event) {
430
470
case HeartbeatEvent ():
431
471
assert (debugLog ("server event: heartbeat" ));
@@ -566,10 +606,13 @@ class PerAccountStore extends ChangeNotifier with ChannelStore, MessageStore {
566
606
}
567
607
}
568
608
569
- Future <void > sendMessage ({required MessageDestination destination, required String content}) {
609
+ Future <void > sendMessage ({required MessageDestination destination, required String content}) async {
610
+ // Do nothing if the account has been logged out.
611
+ if (maybeAccount == null ) return ;
612
+
570
613
// TODO implement outbox; see design at
571
614
// https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/.23M3881.20Sending.20outbox.20messages.20is.20fraught.20with.20issues/near/1405739
572
- return _apiSendMessage (connection,
615
+ await _apiSendMessage (connection,
573
616
destination: destination,
574
617
content: content,
575
618
readBySender: true ,
@@ -685,6 +728,14 @@ class LiveGlobalStore extends GlobalStore {
685
728
assert (rowsAffected == 1 );
686
729
}
687
730
731
+ @override
732
+ Future <void > doRemoveAccount (int accountId) async {
733
+ final rowsAffected = await (_db.delete (_db.accounts)
734
+ ..where ((a) => a.id.equals (accountId))
735
+ ).go ();
736
+ assert (rowsAffected == 1 );
737
+ }
738
+
688
739
@override
689
740
String toString () => '${objectRuntimeType (this , 'LiveGlobalStore' )}#${shortHash (this )}' ;
690
741
}
@@ -793,11 +844,17 @@ class UpdateMachine {
793
844
}());
794
845
}
795
846
847
+ // Abort if the account has been logged out.
848
+ if (store.maybeAccount == null ) return ;
849
+
796
850
final GetEventsResult result;
797
851
try {
798
852
result = await getEvents (store.connection,
799
853
queueId: queueId, lastEventId: lastEventId);
800
854
} catch (e) {
855
+ // Abort if the account has been logged out.
856
+ if (store.maybeAccount == null ) return ;
857
+
801
858
store.isLoading = true ;
802
859
switch (e) {
803
860
case ZulipApiException (code: 'BAD_EVENT_QUEUE_ID' ):
@@ -825,6 +882,9 @@ class UpdateMachine {
825
882
}
826
883
}
827
884
885
+ // Abort if the account has been logged out.
886
+ if (store.maybeAccount == null ) return ;
887
+
828
888
// After one successful request, we reset backoff to its initial state.
829
889
// That way if the user is off the network and comes back on, the app
830
890
// doesn't wind up in a state where it's slow to recover the next time
@@ -847,6 +907,10 @@ class UpdateMachine {
847
907
for (final event in events) {
848
908
await store.handleEvent (event);
849
909
}
910
+
911
+ // Abort if the account has been logged out.
912
+ if (store.maybeAccount == null ) return ;
913
+
850
914
if (events.isNotEmpty) {
851
915
lastEventId = events.last.id;
852
916
}
@@ -881,6 +945,9 @@ class UpdateMachine {
881
945
// TODO(#322) save acked token, to dedupe updating it on the server
882
946
// TODO(#323) track the registerFcmToken/etc request, warn if not succeeding
883
947
Future <void > registerNotificationToken () async {
948
+ // Abort if the account has been logged out.
949
+ if (store.maybeAccount == null ) return ;
950
+
884
951
if (! debugEnableRegisterNotificationToken) {
885
952
return ;
886
953
}
0 commit comments