* ໃນ 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