More background refreshing

This commit is contained in:
Mike Schwörer 2024-06-15 16:33:30 +02:00
parent 35ab9a26c0
commit eea219a205
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
8 changed files with 180 additions and 128 deletions

View File

@ -7,9 +7,11 @@ import 'package:provider/provider.dart';
import 'package:simplecloudnotifier/api/api_client.dart'; import 'package:simplecloudnotifier/api/api_client.dart';
import 'package:simplecloudnotifier/models/user.dart'; import 'package:simplecloudnotifier/models/user.dart';
import 'package:simplecloudnotifier/pages/account/login.dart'; import 'package:simplecloudnotifier/pages/account/login.dart';
import 'package:simplecloudnotifier/state/app_bar_state.dart';
import 'package:simplecloudnotifier/state/application_log.dart'; import 'package:simplecloudnotifier/state/application_log.dart';
import 'package:simplecloudnotifier/state/globals.dart'; import 'package:simplecloudnotifier/state/globals.dart';
import 'package:simplecloudnotifier/state/app_auth.dart'; import 'package:simplecloudnotifier/state/app_auth.dart';
import 'package:simplecloudnotifier/types/immediate_future.dart';
import 'package:simplecloudnotifier/utils/navi.dart'; import 'package:simplecloudnotifier/utils/navi.dart';
import 'package:simplecloudnotifier/utils/toaster.dart'; import 'package:simplecloudnotifier/utils/toaster.dart';
import 'package:simplecloudnotifier/utils/ui.dart'; import 'package:simplecloudnotifier/utils/ui.dart';
@ -25,11 +27,12 @@ class AccountRootPage extends StatefulWidget {
} }
class _AccountRootPageState extends State<AccountRootPage> { class _AccountRootPageState extends State<AccountRootPage> {
late Future<int>? futureSubscriptionCount; late ImmediateFuture<int>? futureSubscriptionCount;
late Future<int>? futureClientCount; late ImmediateFuture<int>? futureClientCount;
late Future<int>? futureKeyCount; late ImmediateFuture<int>? futureKeyCount;
late Future<int>? futureChannelAllCount; late ImmediateFuture<int>? futureChannelAllCount;
late Future<int>? futureChannelSubscribedCount; late ImmediateFuture<int>? futureChannelSubscribedCount;
late ImmediateFuture<User>? futureUser;
late AppAuth userAcc; late AppAuth userAcc;
@ -44,7 +47,7 @@ class _AccountRootPageState extends State<AccountRootPage> {
userAcc = Provider.of<AppAuth>(context, listen: false); userAcc = Provider.of<AppAuth>(context, listen: false);
userAcc.addListener(_onAuthStateChanged); userAcc.addListener(_onAuthStateChanged);
if (widget.isVisiblePage && !_isInitialized) realInitState(); if (widget.isVisiblePage && !_isInitialized) _realInitState();
} }
@override @override
@ -53,25 +56,32 @@ class _AccountRootPageState extends State<AccountRootPage> {
if (oldWidget.isVisiblePage != widget.isVisiblePage && widget.isVisiblePage) { if (oldWidget.isVisiblePage != widget.isVisiblePage && widget.isVisiblePage) {
if (!_isInitialized) { if (!_isInitialized) {
realInitState(); _realInitState();
} else { } else {
//TODO background refresh _backgroundRefresh();
} }
} }
} }
void realInitState() { void _realInitState() {
ApplicationLog.debug('AccountRootPage::_realInitState');
_onAuthStateChanged(); _onAuthStateChanged();
_isInitialized = true; _isInitialized = true;
} }
@override @override
void dispose() { void dispose() {
ApplicationLog.debug('AccountRootPage::dispose');
userAcc.removeListener(_onAuthStateChanged); userAcc.removeListener(_onAuthStateChanged);
super.dispose(); super.dispose();
} }
void _onAuthStateChanged() { void _onAuthStateChanged() {
ApplicationLog.debug('AccountRootPage::_onAuthStateChanged');
_createFutures();
}
void _createFutures() {
futureSubscriptionCount = null; futureSubscriptionCount = null;
futureClientCount = null; futureClientCount = null;
futureKeyCount = null; futureKeyCount = null;
@ -79,35 +89,70 @@ class _AccountRootPageState extends State<AccountRootPage> {
futureChannelSubscribedCount = null; futureChannelSubscribedCount = null;
if (userAcc.isAuth()) { if (userAcc.isAuth()) {
futureChannelAllCount = () async { futureChannelAllCount = ImmediateFuture.ofFuture(() async {
if (!userAcc.isAuth()) throw new Exception('not logged in'); if (!userAcc.isAuth()) throw new Exception('not logged in');
final channels = await APIClient.getChannelList(userAcc, ChannelSelector.all); final channels = await APIClient.getChannelList(userAcc, ChannelSelector.all);
return channels.length; return channels.length;
}(); }());
futureChannelSubscribedCount = () async { futureChannelSubscribedCount = ImmediateFuture.ofFuture(() async {
if (!userAcc.isAuth()) throw new Exception('not logged in'); if (!userAcc.isAuth()) throw new Exception('not logged in');
final channels = await APIClient.getChannelList(userAcc, ChannelSelector.subscribed); final channels = await APIClient.getChannelList(userAcc, ChannelSelector.subscribed);
return channels.length; return channels.length;
}(); }());
futureSubscriptionCount = () async { futureSubscriptionCount = ImmediateFuture.ofFuture(() async {
if (!userAcc.isAuth()) throw new Exception('not logged in'); if (!userAcc.isAuth()) throw new Exception('not logged in');
final subs = await APIClient.getSubscriptionList(userAcc); final subs = await APIClient.getSubscriptionList(userAcc);
return subs.length; return subs.length;
}(); }());
futureClientCount = () async { futureClientCount = ImmediateFuture.ofFuture(() async {
if (!userAcc.isAuth()) throw new Exception('not logged in'); if (!userAcc.isAuth()) throw new Exception('not logged in');
final clients = await APIClient.getClientList(userAcc); final clients = await APIClient.getClientList(userAcc);
return clients.length; return clients.length;
}(); }());
futureKeyCount = () async { futureKeyCount = ImmediateFuture.ofFuture(() async {
if (!userAcc.isAuth()) throw new Exception('not logged in'); if (!userAcc.isAuth()) throw new Exception('not logged in');
final keys = await APIClient.getKeyTokenList(userAcc); final keys = await APIClient.getKeyTokenList(userAcc);
return keys.length; return keys.length;
}(); }());
futureUser = ImmediateFuture.ofFuture(userAcc.loadUser(force: false));
}
}
Future<void> _backgroundRefresh() async {
if (userAcc.isAuth()) {
try {
await Future.delayed(const Duration(seconds: 0), () {}); // this is annoyingly important - otherwise we call setLoadingIndeterminate directly in initStat() and get an exception....
AppBarState().setLoadingIndeterminate(true);
// refresh all data and then replace teh futures used in build()
final channelsAll = await APIClient.getChannelList(userAcc, ChannelSelector.all);
final channelsSubscribed = await APIClient.getChannelList(userAcc, ChannelSelector.subscribed);
final subs = await APIClient.getSubscriptionList(userAcc);
final clients = await APIClient.getClientList(userAcc);
final keys = await APIClient.getKeyTokenList(userAcc);
final user = await userAcc.loadUser(force: true);
setState(() {
futureChannelAllCount = ImmediateFuture.ofValue(channelsAll.length);
futureChannelSubscribedCount = ImmediateFuture.ofValue(channelsSubscribed.length);
futureSubscriptionCount = ImmediateFuture.ofValue(subs.length);
futureClientCount = ImmediateFuture.ofValue(clients.length);
futureKeyCount = ImmediateFuture.ofValue(keys.length);
futureUser = ImmediateFuture.ofValue(user);
});
} catch (exc, trace) {
ApplicationLog.error('Failed to refresh account data: ' + exc.toString(), trace: trace);
Toaster.error("Error", 'Failed to refresh account data');
} finally {
AppBarState().setLoadingIndeterminate(false);
}
} }
} }
@ -121,15 +166,17 @@ class _AccountRootPageState extends State<AccountRootPage> {
return _buildNoAuth(context); return _buildNoAuth(context);
} else { } else {
return FutureBuilder( return FutureBuilder(
future: acc.loadUser(force: false), future: futureUser!.future,
builder: ((context, snapshot) { builder: ((context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) { if (futureUser?.value != null) {
if (snapshot.hasError) { return _buildShowAccount(context, acc, futureUser!.value!);
return Text('Error: ${snapshot.error}'); //TODO better error display } else if (snapshot.connectionState == ConnectionState.done && snapshot.hasError) {
} return Text('Error: ${snapshot.error}'); //TODO better error display
} else if (snapshot.connectionState == ConnectionState.done) {
return _buildShowAccount(context, acc, snapshot.data!); return _buildShowAccount(context, acc, snapshot.data!);
} else {
return Center(child: CircularProgressIndicator());
} }
return Center(child: CircularProgressIndicator());
}), }),
); );
} }
@ -281,12 +328,15 @@ class _AccountRootPageState extends State<AccountRootPage> {
children: [ children: [
SizedBox(width: 80, child: Text("Channels", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)))), SizedBox(width: 80, child: Text("Channels", style: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color?.withAlpha(160)))),
FutureBuilder( FutureBuilder(
future: futureChannelAllCount, future: futureChannelAllCount!.future,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) { if (futureChannelAllCount?.value != null) {
return Text('${futureChannelAllCount!.value}');
} else if (snapshot.connectionState == ConnectionState.done) {
return Text('${snapshot.data}'); return Text('${snapshot.data}');
} else {
return const SizedBox(width: 8, height: 8, child: Center(child: CircularProgressIndicator()));
} }
return const SizedBox(width: 8, height: 8, child: Center(child: CircularProgressIndicator()));
}, },
) )
], ],
@ -315,86 +365,10 @@ class _AccountRootPageState extends State<AccountRootPage> {
List<Widget> _buildCards(BuildContext context, User user) { List<Widget> _buildCards(BuildContext context, User user) {
return [ return [
UI.buttonCard( _buildNumberCard(context, 'Subscriptions', futureSubscriptionCount, () {/*TODO*/}),
context: context, _buildNumberCard(context, 'Clients', futureClientCount, () {/*TODO*/}),
margin: EdgeInsets.fromLTRB(0, 4, 0, 4), _buildNumberCard(context, 'Keys', futureKeyCount, () {/*TODO*/}),
child: Row( _buildNumberCard(context, 'Channels', futureChannelSubscribedCount, () {/*TODO*/}),
children: [
FutureBuilder(
future: futureSubscriptionCount,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Text('${snapshot.data}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20));
}
return const SizedBox(width: 12, height: 12, child: Center(child: CircularProgressIndicator()));
},
),
const SizedBox(width: 12),
Text('Subscriptions', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
],
),
onTap: () {/*TODO*/},
),
UI.buttonCard(
context: context,
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
child: Row(
children: [
FutureBuilder(
future: futureClientCount,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Text('${snapshot.data}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20));
}
return const SizedBox(width: 12, height: 12, child: Center(child: CircularProgressIndicator()));
},
),
const SizedBox(width: 12),
Text('Clients', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
],
),
onTap: () {/*TODO*/},
),
UI.buttonCard(
context: context,
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
child: Row(
children: [
FutureBuilder(
future: futureKeyCount,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Text('${snapshot.data}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20));
}
return const SizedBox(width: 12, height: 12, child: Center(child: CircularProgressIndicator()));
},
),
const SizedBox(width: 12),
Text('Keys', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
],
),
onTap: () {/*TODO*/},
),
UI.buttonCard(
context: context,
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
child: Row(
children: [
FutureBuilder(
future: futureChannelSubscribedCount,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Text('${snapshot.data}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20));
}
return const SizedBox(width: 12, height: 12, child: Center(child: CircularProgressIndicator()));
},
),
const SizedBox(width: 12),
Text('Channels', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
],
),
onTap: () {/*TODO*/},
),
UI.buttonCard( UI.buttonCard(
context: context, context: context,
margin: EdgeInsets.fromLTRB(0, 4, 0, 4), margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
@ -410,6 +384,32 @@ class _AccountRootPageState extends State<AccountRootPage> {
]; ];
} }
Widget _buildNumberCard(BuildContext context, String txt, ImmediateFuture<int>? future, void Function() action) {
return UI.buttonCard(
context: context,
margin: EdgeInsets.fromLTRB(0, 4, 0, 4),
child: Row(
children: [
FutureBuilder(
future: future?.future,
builder: (context, snapshot) {
if (future?.value != null) {
return Text('${future?.value}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20));
} else if (snapshot.connectionState == ConnectionState.done) {
return Text('${snapshot.data}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20));
} else {
return const SizedBox(width: 12, height: 12, child: Center(child: CircularProgressIndicator()));
}
},
),
const SizedBox(width: 12),
Text(txt, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
],
),
onTap: action,
);
}
Widget _buildFooter(BuildContext context, User user) { Widget _buildFooter(BuildContext context, User user) {
return Padding( return Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),

View File

@ -3,6 +3,7 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:simplecloudnotifier/api/api_client.dart'; import 'package:simplecloudnotifier/api/api_client.dart';
import 'package:simplecloudnotifier/models/channel.dart'; import 'package:simplecloudnotifier/models/channel.dart';
import 'package:simplecloudnotifier/state/app_bar_state.dart';
import 'package:simplecloudnotifier/state/application_log.dart'; import 'package:simplecloudnotifier/state/application_log.dart';
import 'package:simplecloudnotifier/state/app_auth.dart'; import 'package:simplecloudnotifier/state/app_auth.dart';
import 'package:simplecloudnotifier/pages/channel_list/channel_list_item.dart'; import 'package:simplecloudnotifier/pages/channel_list/channel_list_item.dart';
@ -27,11 +28,12 @@ class _ChannelRootPageState extends State<ChannelRootPage> {
_pagingController.addPageRequestListener(_fetchPage); _pagingController.addPageRequestListener(_fetchPage);
if (widget.isVisiblePage && !_isInitialized) realInitState(); if (widget.isVisiblePage && !_isInitialized) _realInitState();
} }
@override @override
void dispose() { void dispose() {
ApplicationLog.debug('ChannelRootPage::dispose');
_pagingController.dispose(); _pagingController.dispose();
super.dispose(); super.dispose();
} }
@ -42,14 +44,15 @@ class _ChannelRootPageState extends State<ChannelRootPage> {
if (oldWidget.isVisiblePage != widget.isVisiblePage && widget.isVisiblePage) { if (oldWidget.isVisiblePage != widget.isVisiblePage && widget.isVisiblePage) {
if (!_isInitialized) { if (!_isInitialized) {
realInitState(); _realInitState();
} else { } else {
//TODO background refresh _backgroundRefresh();
} }
} }
} }
void realInitState() { void _realInitState() {
ApplicationLog.debug('ChannelRootPage::_realInitState');
_pagingController.refresh(); _pagingController.refresh();
_isInitialized = true; _isInitialized = true;
} }
@ -69,13 +72,41 @@ class _ChannelRootPageState extends State<ChannelRootPage> {
items.sort((a, b) => -1 * (a.timestampLastSent ?? '').compareTo(b.timestampLastSent ?? '')); items.sort((a, b) => -1 * (a.timestampLastSent ?? '').compareTo(b.timestampLastSent ?? ''));
_pagingController.appendLastPage(items); _pagingController.value = PagingState(nextPageKey: null, itemList: items, error: null);
} catch (exc, trace) { } catch (exc, trace) {
_pagingController.error = exc.toString(); _pagingController.error = exc.toString();
ApplicationLog.error('Failed to list channels: ' + exc.toString(), trace: trace); ApplicationLog.error('Failed to list channels: ' + exc.toString(), trace: trace);
} }
} }
Future<void> _backgroundRefresh() async {
final acc = Provider.of<AppAuth>(context, listen: false);
ApplicationLog.debug('Start background refresh of channel list');
if (!acc.isAuth()) {
_pagingController.error = 'Not logged in';
return;
}
try {
await Future.delayed(const Duration(seconds: 0), () {}); // this is annoyingly important - otherwise we call setLoadingIndeterminate directly in initStat() and get an exception....
AppBarState().setLoadingIndeterminate(true);
final items = (await APIClient.getChannelList(acc, ChannelSelector.all)).map((p) => p.channel).toList();
items.sort((a, b) => -1 * (a.timestampLastSent ?? '').compareTo(b.timestampLastSent ?? ''));
_pagingController.value = PagingState(nextPageKey: null, itemList: items, error: null);
} catch (exc, trace) {
_pagingController.error = exc.toString();
ApplicationLog.error('Failed to list channels: ' + exc.toString(), trace: trace);
} finally {
AppBarState().setLoadingIndeterminate(false);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return RefreshIndicator( return RefreshIndicator(

View File

@ -43,7 +43,7 @@ class _DebugPersistencePageState extends State<DebugPersistencePage> {
children: [ children: [
SizedBox(width: 30, child: Text('')), SizedBox(width: 30, child: Text('')),
Expanded(child: Text('Shared Preferences', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)), Expanded(child: Text('Shared Preferences', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)),
SizedBox(width: 30, child: Text('${prefs?.getKeys().length.toString()}', textAlign: TextAlign.end)), SizedBox(width: 40, child: Text('${prefs?.getKeys().length.toString()}', textAlign: TextAlign.end)),
], ],
), ),
), ),
@ -61,7 +61,7 @@ class _DebugPersistencePageState extends State<DebugPersistencePage> {
children: [ children: [
SizedBox(width: 30, child: Text('')), SizedBox(width: 30, child: Text('')),
Expanded(child: Text('Hive [scn-requests]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)), Expanded(child: Text('Hive [scn-requests]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)),
SizedBox(width: 30, child: Text('${Hive.box<SCNRequest>('scn-requests').length.toString()}', textAlign: TextAlign.end)), SizedBox(width: 40, child: Text('${Hive.box<SCNRequest>('scn-requests').length.toString()}', textAlign: TextAlign.end)),
], ],
), ),
), ),
@ -79,7 +79,7 @@ class _DebugPersistencePageState extends State<DebugPersistencePage> {
children: [ children: [
SizedBox(width: 30, child: Text('')), SizedBox(width: 30, child: Text('')),
Expanded(child: Text('Hive [scn-logs]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)), Expanded(child: Text('Hive [scn-logs]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)),
SizedBox(width: 30, child: Text('${Hive.box<SCNLog>('scn-logs').length.toString()}', textAlign: TextAlign.end)), SizedBox(width: 40, child: Text('${Hive.box<SCNLog>('scn-logs').length.toString()}', textAlign: TextAlign.end)),
], ],
), ),
), ),
@ -97,7 +97,7 @@ class _DebugPersistencePageState extends State<DebugPersistencePage> {
children: [ children: [
SizedBox(width: 30, child: Text('')), SizedBox(width: 30, child: Text('')),
Expanded(child: Text('Hive [scn-message-cache]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)), Expanded(child: Text('Hive [scn-message-cache]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)),
SizedBox(width: 30, child: Text('${Hive.box<Message>('scn-message-cache').length.toString()}', textAlign: TextAlign.end)), SizedBox(width: 40, child: Text('${Hive.box<Message>('scn-message-cache').length.toString()}', textAlign: TextAlign.end)),
], ],
), ),
), ),
@ -115,7 +115,7 @@ class _DebugPersistencePageState extends State<DebugPersistencePage> {
children: [ children: [
SizedBox(width: 30, child: Text('')), SizedBox(width: 30, child: Text('')),
Expanded(child: Text('Hive [scn-channel-cache]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)), Expanded(child: Text('Hive [scn-channel-cache]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)),
SizedBox(width: 30, child: Text('${Hive.box<Channel>('scn-channel-cache').length.toString()}', textAlign: TextAlign.end)), SizedBox(width: 40, child: Text('${Hive.box<Channel>('scn-channel-cache').length.toString()}', textAlign: TextAlign.end)),
], ],
), ),
), ),

View File

@ -39,7 +39,7 @@ class _MessageListPageState extends State<MessageListPage> with RouteAware {
_pagingController.addPageRequestListener(_fetchPage); _pagingController.addPageRequestListener(_fetchPage);
if (widget.isVisiblePage && !_isInitialized) realInitState(); if (widget.isVisiblePage && !_isInitialized) _realInitState();
} }
@override @override
@ -48,14 +48,16 @@ class _MessageListPageState extends State<MessageListPage> with RouteAware {
if (oldWidget.isVisiblePage != widget.isVisiblePage && widget.isVisiblePage) { if (oldWidget.isVisiblePage != widget.isVisiblePage && widget.isVisiblePage) {
if (!_isInitialized) { if (!_isInitialized) {
realInitState(); _realInitState();
} else { } else {
_backgroundRefresh(false); _backgroundRefresh(false);
} }
} }
} }
void realInitState() { void _realInitState() {
ApplicationLog.debug('MessageListPage::_realInitState');
final chnCache = Hive.box<Channel>('scn-channel-cache'); final chnCache = Hive.box<Channel>('scn-channel-cache');
final msgCache = Hive.box<Message>('scn-message-cache'); final msgCache = Hive.box<Message>('scn-message-cache');
@ -86,6 +88,7 @@ class _MessageListPageState extends State<MessageListPage> with RouteAware {
@override @override
void dispose() { void dispose() {
ApplicationLog.debug('MessageListPage::dispose');
Navi.modalRouteObserver.unsubscribe(this); Navi.modalRouteObserver.unsubscribe(this);
_pagingController.dispose(); _pagingController.dispose();
super.dispose(); super.dispose();
@ -93,14 +96,13 @@ class _MessageListPageState extends State<MessageListPage> with RouteAware {
@override @override
void didPush() { void didPush() {
// Route was pushed onto navigator and is now the topmost route. // ...
ApplicationLog.debug('[MessageList::RouteObserver] --> didPush');
} }
@override @override
void didPopNext() { void didPopNext() {
// Covering route was popped off the navigator. ApplicationLog.debug('[MessageList::RouteObserver] --> didPopNext (will background-refresh)');
ApplicationLog.debug('[MessageList::RouteObserver] --> didPopNext'); _backgroundRefresh(false);
} }
Future<void> _fetchPage(String thisPageToken) async { Future<void> _fetchPage(String thisPageToken) async {

View File

@ -118,7 +118,6 @@ class AppAuth extends ChangeNotifier implements TokenSource {
final user = await APIClient.getUser(this, _userID!); final user = await APIClient.getUser(this, _userID!);
_user = user; _user = user;
notifyListeners();
await save(); await save();
@ -142,14 +141,12 @@ class AppAuth extends ChangeNotifier implements TokenSource {
final client = await APIClient.getClient(this, _clientID!); final client = await APIClient.getClient(this, _clientID!);
_client = client; _client = client;
notifyListeners();
await save(); await save();
return client; return client;
} on APIException catch (_) { } on APIException catch (_) {
_client = null; _client = null;
notifyListeners();
return null; return null;
} catch (exc) { } catch (exc) {
_client = null; _client = null;

View File

@ -5,6 +5,8 @@ import 'package:xid/xid.dart';
part 'application_log.g.dart'; part 'application_log.g.dart';
class ApplicationLog { class ApplicationLog {
//TODO max size, auto clear old
static void debug(String message, {String? additional, StackTrace? trace}) { static void debug(String message, {String? additional, StackTrace? trace}) {
(additional != null && additional != '') ? print('[DEBUG] ${message}: ${additional}') : print('[DEBUG] ${message}'); (additional != null && additional != '') ? print('[DEBUG] ${message}: ${additional}') : print('[DEBUG] ${message}');

View File

@ -6,6 +6,8 @@ import 'package:xid/xid.dart';
part 'request_log.g.dart'; part 'request_log.g.dart';
class RequestLog { class RequestLog {
//TODO max size, auto clear old
static void addRequestException(String name, DateTime tStart, String method, Uri uri, String reqbody, Map<String, String> reqheaders, dynamic e, StackTrace trace) { static void addRequestException(String name, DateTime tStart, String method, Uri uri, String reqbody, Map<String, String> reqheaders, dynamic e, StackTrace trace) {
Hive.box<SCNRequest>('scn-requests').add(SCNRequest( Hive.box<SCNRequest>('scn-requests').add(SCNRequest(
id: Xid().toString(), id: Xid().toString(),

View File

@ -0,0 +1,18 @@
// This class is useful togther with FutureBuilder
// Unfortunately Future.value(x) in FutureBuilder always results in one frame were snapshot.connectionState is waiting
// Whit way we can set the ImmediateFuture.value directly and circumvent that.
class ImmediateFuture<T> {
final Future<T> future;
final T? value;
ImmediateFuture(this.future, this.value);
ImmediateFuture.ofFuture(Future<T> v)
: future = v,
value = null;
ImmediateFuture.ofValue(T v)
: future = Future.value(v),
value = v;
}