How to initialize a lazy variable before rendering using provider?

141 Views Asked by At

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.

0

There are 0 best solutions below