I want to initialize a variable before getting to the GoRouter routes.
This authenticated below is the variable I want to initialize:
class AuthProvider with ChangeNotifier {
late bool? authenticated;
static late User? user;
AuthProvider() {
checkAndNotify();
}
Future<bool?> checkAndNotify() async {
try {
String? token = ((await AppStorage.appBox.get("token")) as String?);
authenticated = token!=null;
notifyListeners();
return authenticated;
} catch (err) {
logError(
message: "Seems like the user is not authenticated",
error: err.toString(),
method: "check",
);
return null;
}
}
}
Notice that I added a constructor so that checkAndNotify will be called once the provider is created.
And this is where I try to access authenticated: routes.dart
final routes = GoRouter(
initialLocation: "/",
errorBuilder: (context,state) => const ErrorScreen(),
routes: [
GoRoute(
path: "/",
redirect: (context, state) {
if (context.watch<AuthProvider>().authenticated != true) {
return "/auth/login";
} else {
return "/home";
}
},
routes: [
GoRoute(
path: "auth",
redirect: (context, state) {
return "/auth/login";
},
routes: [
GoRoute(
path: "login",
builder: (context, state) => const LoginScreen(),
),
GoRoute(
path: "register",
builder: (context, state) => const RegistrationScreen(),
),
],
),
ShellRoute(
restorationScopeId: "route.shell",
routes: [
GoRoute(
path: "home",
name: "home",
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: "orders",
name: "orders",
builder: (context, state) => const OrdersScreen(),
),
GoRoute(
path: "profile",
name: "profile",
builder: (context, state) => const ProfileScreen(),
),
],
builder: (context, state, child) {
return Shell(child: child);
},
),
]),
],
)
Pay attention to:
redirect: (context, state) {
if (context.watch<AuthProvider>().authenticated != true) {
return "/auth/login";
} else {
return "/home";
}
},
This is how my main.dart looks like:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await dotenv.load();
await AppStorage.init();
await LanguageProvider.init();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => LanguageProvider()),
ChangeNotifierProvider(create: (_) => AuthProvider()),
],
child: const Eshop(),
),
);
}
class Eshop extends StatelessWidget {
const Eshop({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
routerConfig: routes,
theme: AppTheme.light,
);
}
}
When i run this code, I get a LateInitializationError then I get redirected as epxected to the LogInScreen. I guess that is because of the fact that checkAndNotify is asynchronous.
I also tried to modify the Eshop widget to be stateful in order to be able to call checkAndNotify inside WidgetsBinding.instance.addPostFrameCallback but it did not change anything:
class _EshopState extends State<Eshop> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(( timeStamp) {
context.read<AuthProvider>().checkAndNotify();
});
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
routerConfig: routes,
theme: AppTheme.light,
);
}
}
NOTE TO MY QUESTION: I don't want to remove the late modifier like I saw in order's answer related to this kind of situation, What I want is know how to properly use it in my app so that authenticated won't ever be null or have a default value.