Fixed bug where homescreen would load twice

Moved AccountMeta provider into <User> context
and migrated to using a FutureProvider to perform the network request

NOTE: Bug was caused by AccountMeta? being loaded late,
causing the root provider to be reloaded.
This commit is contained in:
Jakob Meier 2023-03-29 18:23:46 +02:00
parent 1af8d6f068
commit ecccec7950
No known key found for this signature in database
GPG key ID: 66BDC7E6A01A6152
3 changed files with 172 additions and 161 deletions

View file

@ -24,9 +24,9 @@ void main() {
} }
final GlobalKey<NavigatorState> _rootNavigatorKey = final GlobalKey<NavigatorState> _rootNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'root'); GlobalKey<NavigatorState>(debugLabel: 'root');
final GlobalKey<NavigatorState> _userShellNavigatorKey = final GlobalKey<NavigatorState> _userShellNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'user'); GlobalKey<NavigatorState>(debugLabel: 'user');
class OutbagApp extends StatefulWidget { class OutbagApp extends StatefulWidget {
const OutbagApp({super.key}); const OutbagApp({super.key});
@ -39,7 +39,6 @@ class _OutbagAppState extends State {
// assume user is logged in // assume user is logged in
// unless not userdata is found // unless not userdata is found
// or the userdata turns out to be wrong // or the userdata turns out to be wrong
AccountMeta? info;
User? user; User? user;
AppTheme theme = AppTheme.auto; AppTheme theme = AppTheme.auto;
@ -49,7 +48,7 @@ class _OutbagAppState extends State {
try { try {
final theme = await AppTheme.fromDisk(); final theme = await AppTheme.fromDisk();
setState(() { setState(() {
this.theme = theme; this.theme = theme;
}); });
} catch (_) {} } catch (_) {}
} }
@ -59,15 +58,14 @@ class _OutbagAppState extends State {
try { try {
final user = await User.fromDisk(); final user = await User.fromDisk();
setState(() { setState(() {
this.user = user; this.user = user;
}); });
fetchInfo(user);
} catch (_) { } catch (_) {
// user unavailable // user unavailable
// invalid credentials // invalid credentials
// log out // log out
setState(() { setState(() {
user = null; user = null;
}); });
} }
} }
@ -77,177 +75,186 @@ class _OutbagAppState extends State {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async { WidgetsBinding.instance.addPostFrameCallback((_) async {
loadTheme(); loadTheme();
loadUser(); loadUser();
}); });
} }
void fetchInfo(User user) async { Future<AccountMeta?> fetchInfo(User user) async {
AccountMeta? info;
// try to obtain user account information // try to obtain user account information
// with existing details // with existing details
// NOTE: also functions as a way to verify ther data // NOTE: also functions as a way to verify ther data
doNetworkRequest(null, await doNetworkRequest(null,
req: () => postWithCreadentials( req: () => postWithCreadentials(
target: user.server, target: user.server,
path: 'getMyAccount', path: 'getMyAccount',
credentials: user, credentials: user,
body: {}), body: {}),
onOK: (body) { onOK: (body) async {
final info = AccountMeta.fromJSON(body['data']); final i = AccountMeta.fromJSON(body['data']);
setState(() { info = i;
this.info = info; },
}); onServerErr: (_) {
}, info = null;
onServerErr: (_) {
// credentials are wrong
// log out
setState(() { setState(() {
info = null;
this.user = null; this.user = null;
});
return true;
},
onNetworkErr: () {
info = null;
// user is currently offline
// approve login,
// until user goes back offline
// NOTE TODO: check user data once online
return true;
}); });
return true;
}, return info;
onNetworkErr: () {
// user is currently offline
// approve login,
// until user goes back offline
// NOTE TODO: check user data once online
return true;
});
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiProvider( return MultiProvider(
providers: [ providers: [
Provider<AccountMeta?>.value( Provider<AppTheme>.value(value: theme),
value: info,
),
Provider<AppTheme>.value(value: theme),
],
child: MaterialApp.router(
title: "Outbag",
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
AppLocalizations.delegate
], ],
supportedLocales: AppLocalizations.supportedLocales, child: MaterialApp.router(
themeMode: theme.mode, title: "Outbag",
theme: ThemeData(useMaterial3: true, brightness: Brightness.light), localizationsDelegates: const [
darkTheme: ThemeData(useMaterial3: true, brightness: Brightness.dark), GlobalMaterialLocalizations.delegate,
routerConfig: GoRouter( GlobalWidgetsLocalizations.delegate,
navigatorKey: _rootNavigatorKey, GlobalCupertinoLocalizations.delegate,
initialLocation: '/', AppLocalizations.delegate
redirect: (context, state) async { ],
if (user == null) { supportedLocales: AppLocalizations.supportedLocales,
// prelogin themeMode: theme.mode,
if (!state.subloc.startsWith('/welcome')) { theme: ThemeData(useMaterial3: true, brightness: Brightness.light),
// prevent unauthorized user from accessing home darkTheme: ThemeData(useMaterial3: true, brightness: Brightness.dark),
return '/welcome'; routerConfig: GoRouter(
} navigatorKey: _rootNavigatorKey,
} else { initialLocation: '/',
// post login redirect: (context, state) async {
if (state.subloc.startsWith('/welcome')) { if (user == null) {
// prevent authorized user from accessing /welcome // prelogin
return '/'; if (!state.subloc.startsWith('/welcome')) {
} // prevent unauthorized user from accessing home
} return '/welcome';
}
} else {
// post login
if (state.subloc.startsWith('/welcome')) {
// prevent authorized user from accessing /welcome
return '/';
}
}
return null; return null;
}, },
routes: <RouteBase>[
// unauthorized routes
GoRoute(
name: 'welcome',
path: '/welcome',
builder: (context, state) => const WelcomePage(),
routes: <RouteBase>[ routes: <RouteBase>[
// unauthorized routes
GoRoute( GoRoute(
name: 'signin', name: 'welcome',
path: 'signin', path: '/welcome',
builder: (context, state) => AuthPage(mode: Mode.signin, refresh: loadUser), builder: (context, state) => const WelcomePage(),
), routes: <RouteBase>[
GoRoute( GoRoute(
name: 'signup', name: 'signin',
path: 'signup', path: 'signin',
builder: (context, state) => AuthPage(mode: Mode.signup, refresh: loadUser), builder: (context, state) =>
), AuthPage(mode: Mode.signin, refresh: loadUser),
GoRoute( ),
name: 'signup-ota', GoRoute(
path: 'signup-ota', name: 'signup',
builder: (context, state) => AuthPage(mode: Mode.signupOTA, refresh: loadUser), path: 'signup',
), builder: (context, state) =>
]), AuthPage(mode: Mode.signup, refresh: loadUser),
),
// authorized routes GoRoute(
ShellRoute( name: 'signup-ota',
navigatorKey: _userShellNavigatorKey, path: 'signup-ota',
builder: (context, state, child) => builder: (context, state) =>
Provider.value(value: user!, child: child), AuthPage(mode: Mode.signupOTA, refresh: loadUser),
routes: <RouteBase>[ ),
GoRoute(
path: '/',
name: 'home',
builder: (context, state) => const HomePage(),
routes: [
GoRoute(
name: 'settings',
path: 'settings',
builder: (context, state) => SettingsPage(refreshTheme: loadTheme)),
GoRoute(
path: 'join-room',
name: 'add-room',
builder: (context, state) =>
const JoinRoomPage(),
routes: <RouteBase>[
GoRoute(
path: 'new',
name: 'new-room',
builder: (context, state) =>
const NewRoomPage()),
]), ]),
GoRoute(
name: 'room',
path: 'r/:server/:id',
builder: (context, state) => RoomPage(
state.params['server'] ?? '',
state.params['id'] ?? ''),
routes: <RouteBase>[
GoRoute(
name: 'edit-room',
path: 'edit',
builder: (context, state) => EditRoomPage(
state.params['server'] ?? '',
state.params['id'] ?? '')),
GoRoute(
name: 'room-members',
path: 'members',
builder: (context, state) =>
ManageRoomMembersPage(
state.params['server'] ?? '',
state.params['id'] ?? '')),
GoRoute(
name: 'room-permissions',
path: 'roles',
builder: (context, state) =>
EditRoomPermissionSetPage(
state.params['server'] ?? '',
state.params['id'] ?? '')),
])
]),
]),
// routes that can be accessed // authorized routes
// with and without an account ShellRoute(
// i.e the about screen navigatorKey: _userShellNavigatorKey,
GoRoute( builder: (context, state, child) => Provider.value(
path: '/about', value: user!,
name: 'about', child: FutureProvider(
builder: (context, state) => const Text('About')) initialData: null,
]), child: child,
)); create: (context)=>fetchInfo(context.read<User>()),
)),
routes: <RouteBase>[
GoRoute(
path: '/',
name: 'home',
builder: (context, state) => const HomePage(),
routes: [
GoRoute(
name: 'settings',
path: 'settings',
builder: (context, state) =>
SettingsPage(
refreshTheme: loadTheme,
refreshUser: loadUser
)),
GoRoute(
path: 'join-room',
name: 'add-room',
builder: (context, state) =>
const JoinRoomPage(),
routes: <RouteBase>[
GoRoute(
path: 'new',
name: 'new-room',
builder: (context, state) =>
const NewRoomPage()),
]),
GoRoute(
name: 'room',
path: 'r/:server/:id',
builder: (context, state) => RoomPage(
state.params['server'] ?? '',
state.params['id'] ?? ''),
routes: <RouteBase>[
GoRoute(
name: 'edit-room',
path: 'edit',
builder: (context, state) => EditRoomPage(
state.params['server'] ?? '',
state.params['id'] ?? '')),
GoRoute(
name: 'room-members',
path: 'members',
builder: (context, state) =>
ManageRoomMembersPage(
state.params['server'] ?? '',
state.params['id'] ?? '')),
GoRoute(
name: 'room-permissions',
path: 'roles',
builder: (context, state) =>
EditRoomPermissionSetPage(
state.params['server'] ?? '',
state.params['id'] ?? '')),
])
]),
]),
// routes that can be accessed
// with and without an account
// i.e the about screen
GoRoute(
path: '/about',
name: 'about',
builder: (context, state) => const Text('About'))
]),
));
} }
} }

View file

@ -10,7 +10,9 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class SettingsPage extends StatefulWidget { class SettingsPage extends StatefulWidget {
Function refreshTheme; Function refreshTheme;
SettingsPage({super.key, required this.refreshTheme}); Function refreshUser;
SettingsPage(
{super.key, required this.refreshTheme, required this.refreshUser});
@override @override
State<StatefulWidget> createState() => _SettingsPageState(); State<StatefulWidget> createState() => _SettingsPageState();
@ -204,6 +206,7 @@ class _SettingsPageState extends State<SettingsPage> {
// go back home // go back home
router.pushReplacementNamed('home'); router.pushReplacementNamed('home');
widget.refreshUser();
}, },
after: () { after: () {
// close popup // close popup
@ -254,6 +257,7 @@ class _SettingsPageState extends State<SettingsPage> {
// go back home // go back home
router.pushReplacementNamed('home'); router.pushReplacementNamed('home');
widget.refreshUser();
}, },
child: Text(AppLocalizations.of(context)!.yes), child: Text(AppLocalizations.of(context)!.yes),
) )

View file

@ -4,7 +4,7 @@ import 'package:outbag_app/backend/request.dart';
import 'package:outbag_app/tools/snackbar.dart'; import 'package:outbag_app/tools/snackbar.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
void doNetworkRequest(ScaffoldMessengerState? sm, Future<void> doNetworkRequest(ScaffoldMessengerState? sm,
{required Future<Response> Function() req, {required Future<Response> Function() req,
Function(Map<String, dynamic> body)? onOK, Function(Map<String, dynamic> body)? onOK,
bool Function()? onNetworkErr, bool Function()? onNetworkErr,