* ໃນ part 1 ນີ້ຈະເປັນການ setup ໃນແບບຂອງຜູ້ຂຽນເອງ
- ໃຊ້ pocketbase ເປັນ backend api (firebase alternative ຈະຂຽນ blog ເພີ່ມຕ່າງຫາກກ່ຽວກັບການໃຊ້ງານກັບ pocketbase)
- hive ສຳລັບ persist data (login credentials, theme, language, etc)
- go router
- get_it ສຳລັບ dependency injection
*project setup ໂດຍອີງຕາກ TDD architecture

structure ຄ່າວໆ ສຳລັບ project ທົດລອງ
main_dev.dart
void main() async {
  runZonedGuarded(
    () async {
      WidgetsFlutterBinding.ensureInitialized();
      // init hive
      final appDir = await getApplicationDocumentsDirectory();
      await Hive.initFlutter(appDir.path);
      await Hive.openBox('prefs-${Env.envName}');
      // detect platform type
      final platformType = detectPlatformType();
      // init dependencies injection
      dpInit();
      runApp(
        ProviderScope(
          overrides: [platformTypeProvider.overrideWithValue(platformType)],
          observers: [StateLogger()],
          child: App(
            key: Key('app-${Env.envName}'),
          ),
        ),
      );
    },
    (error, stack) {
      ...
    },
  );
}app.dart
class App extends ConsumerWidget {
  const App({super.key});
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final router = ref.watch(routerProvider);
    // final currentLocale =
    // final currentThemeMode =
    return MaterialApp.router(
      routerConfig: router,
      builder: (context, child) {
        child = ResponsiveBreakpoints.builder(
          child: BouncingScrollWrapper.builder(context, child!),
          breakpoints: [
            const Breakpoint(start: 0, end: 450, name: MOBILE),
            const Breakpoint(start: 451, end: 800, name: TABLET),
            const Breakpoint(start: 801, end: 1920, name: DESKTOP),
            const Breakpoint(start: 1921, end: double.infinity, name: '4K'),
          ],
        );
        return child;
      },
    );
  }
}ເຮົາຈະໃຊ້ເປັນ MaterialApp.router ແທນ Material ທຳມະດາ. ໂດຍຈະມີ routerConfig ແມ່ນ ມາຈາກ routerProvider (riverpod ref).
ສຳລັບ routerProvider ແມ່ນຜູ້ຂຽນວາງໄວ້ໃນ lib/app/core/routes
ໂດຍຈະມີຢູ່ 2 ໄຟລ໌ຫຼັກໆຄື: router_notifier.dart ແລະ router_provider.dart
router_provider.dart
final key = GlobalKey<NavigatorState>(debugLabel: '${Env.envName}-router-key');
final routerProvider = Provider.autoDispose<GoRouter>((ref) {
  // router notifier
  final notifier = ref.watch(routerNotifier.notifier);
  return GoRouter(
    navigatorKey: key,
    refreshListenable: notifier,
    debugLogDiagnostics: kDebugMode,
    initialLocation: SplashScreen.path,
    routes: notifier.routes,
    redirect: notifier.redirect,
    errorBuilder: (context, state) => const ErrorRouterWidget(),
  );
});router_notifier.dart
class RouterNotifier extends AutoDisposeAsyncNotifier<void>
    implements Listenable {
  VoidCallback? routerListener;
  bool isAuth = false;
  @override
  FutureOr<void> build() async {
    /// mock set default initial auth state to false
    /// UNAUTHENTICATED
    isAuth = false;
    ref.listenSelf((_, __) {
      // One could write more conditional logic for when to call redirection
      if (state.isLoading) return;
      routerListener?.call();
    });
  }
  /// Redirects the user when our authentication changes
  String? redirect(BuildContext context, GoRouterState state) {
    /// redirect none if state == null
    if (this.state.isLoading || this.state.hasError) return null;
    // login location
    final loginLocation = state.location == LoginScreen.path;
    // splash location
    final splashLocation = state.location == SplashScreen.path;
    // redirect from splash location
    if (splashLocation) {
      return isAuth ? HomeScreen.path : LoginScreen.path;
    }
    // redirect from login location
    if (loginLocation) {
      return isAuth ? HomeScreen.path : LoginScreen.path;
    }
    return null;
  }
  /// all available app routes
  ///
  /// `<GoRoute>[]`
  List<GoRoute> get routes => [
        GoRoute(
          path: SplashScreen.path,
          builder: (context, state) => const SplashScreen(),
        ),
        GoRoute(
          path: LoginScreen.path,
          builder: (context, state) => const LoginScreen(),
        ),
        GoRoute(
          path: RegisterScreen.path,
          builder: (context, state) => const RegisterScreen(),
        ),
        GoRoute(
          path: HomeScreen.path,
          builder: (context, state) => const HomeScreen(),
        ),
      ];
  /// Adds [GoRouter]'s listener as specified by its [Listenable]
  /// [GoRouteInformationProvider] uses this method on creation to handle its
  /// internal [ChangeNotifier].
  /// Check out the internal implementation of [GoRouter] and
  /// [GoRouteInformationProvider] to see this in action.
  @override
  void addListener(VoidCallback listener) {
    routerListener = listener;
  }
  /// Removes [GoRouter]'s listener as specified by its [Listenable].
  /// [GoRouteInformationProvider] uses this method when disposing,
  /// so that it removes its callback when destroyed.
  /// Check out the internal implementation of [GoRouter] and
  /// [GoRouteInformationProvider] to see this in action.
  @override
  void removeListener(VoidCallback listener) {
    routerListener = null;
  }
}
final routerNotifier = AutoDisposeAsyncNotifierProvider<RouterNotifier, void>(
  () => RouterNotifier(),
);ຫຼັກໆໃນ router_notifier.dart ນີ້ແມ່ນຈະໄວ້ listen ກໍລະນີມີ router redirect ເຊັ່ນ: ເວລາ user logged in, user token expired, user logged out, ແລະ ອື່ນໆ. ແລະ ຜູ້ຂຽນເອງກໍໄດ້ປະກາດ List<GoRoute> get routes ໄວ້ນຳ ຫຼື ກໍຄື routes ທັງໝົດໃນ app ເຮົາໄວ້ທີ່ນີ້.
ຫຼັງຈາກ run
ຜົນຫຼັງຈາກ run ກໍຈະສະແດງ GoRouter ຕາມໃນຮູບດັ່ງນີ້:

debug console
ເຊິ່ງຈະເຫັນວ່າມີທັງໝົດຢູ່ 4 routes ຕາມໃນ router_notifier.dart
init location ແມ່ນ /splash ຕາມໃນ router_provider.dart
initialLocation: SplashScreen.path,redirecting ແມ່ນອີງຕາມ redirect ໃນ router_notifier.dart ທີ່ build() ໃນ redirect ຈະ return route path ອີງຕາມທີ່ເຮົາໄດ້ check condition ໄວ້.
part 1 — ການໃຊ້ riverpod ສຳລັບເຮັດ go router notifier
ຂໍຈົບໄວ້ພຽງເທົ່ານີ້. ສຳລັບ part ຕໍ່ໄປແມ່ນເຮົາຈະເລີ່ມເຮັດ authentication ແລະ navigation ພ້ອມກັບການ pass argument.
ໝາຍເຫດ: ສຳລັບ project source code ຜູ້ຂຽນຈະ open public ໄວ້ທາງ github ໃນພາຍຫຼັງ.
ຂຽນໂດຍ: Noy Sengxayya


 
  
 