Advanced

Route Guards

Pengantar

Route guard menyediakan middleware untuk navigasi di Nylo Website. Route guard mengintersepsi transisi rute dan memungkinkan Anda mengontrol apakah pengguna dapat mengakses halaman, mengarahkan mereka ke tempat lain, atau memodifikasi data yang diteruskan ke rute.

Kasus penggunaan umum meliputi:

  • Pemeriksaan autentikasi -- mengarahkan pengguna yang belum terautentikasi ke halaman login
  • Akses berbasis peran -- membatasi halaman hanya untuk pengguna admin
  • Validasi data -- memastikan data yang diperlukan ada sebelum navigasi
  • Pengayaan data -- melampirkan data tambahan ke rute

Guard dieksekusi secara berurutan sebelum navigasi terjadi. Jika guard mana pun mengembalikan handled, navigasi berhenti (baik dengan mengarahkan ulang atau membatalkan).

Membuat Route Guard

Buat route guard menggunakan Metro CLI:

metro make:route_guard auth

Ini menghasilkan file guard:

import 'package:nylo_framework/nylo_framework.dart';

class AuthRouteGuard extends NyRouteGuard {
  AuthRouteGuard();

  @override
  Future<GuardResult> onBefore(RouteContext context) async {
    // Add your guard logic here
    return next();
  }
}

Siklus Hidup Guard

Setiap route guard memiliki tiga metode siklus hidup:

onBefore

Dipanggil sebelum navigasi terjadi. Di sinilah Anda memeriksa kondisi dan memutuskan apakah akan mengizinkan, mengarahkan ulang, atau membatalkan navigasi.

@override
Future<GuardResult> onBefore(RouteContext context) async {
  bool isLoggedIn = await Auth.isAuthenticated();

  if (!isLoggedIn) {
    return redirect(HomePage.path);
  }

  return next();
}

Nilai kembalian:

  • next() -- lanjutkan ke guard berikutnya atau navigasi ke rute
  • redirect(path) -- arahkan ulang ke rute yang berbeda
  • abort() -- batalkan navigasi sepenuhnya

onAfter

Dipanggil setelah navigasi berhasil. Gunakan ini untuk analitik, pencatatan, atau efek samping pasca-navigasi.

@override
Future<void> onAfter(RouteContext context) async {
  // Log page view
  Analytics.trackPageView(context.routeName);
}

onLeave

Dipanggil ketika pengguna meninggalkan rute. Kembalikan false untuk mencegah pengguna meninggalkan halaman.

@override
Future<bool> onLeave(RouteContext context) async {
  if (hasUnsavedChanges) {
    // Show confirmation dialog
    return await showConfirmDialog();
  }
  return true; // Allow leaving
}

RouteContext

Objek RouteContext diteruskan ke semua metode siklus hidup guard dan berisi informasi tentang navigasi:

Properti Tipe Deskripsi
context BuildContext? Build context saat ini
data dynamic Data yang diteruskan ke rute
queryParameters Map<String, String> Parameter query URL
routeName String Nama/path rute tujuan
originalRouteName String? Nama rute asli sebelum transformasi
@override
Future<GuardResult> onBefore(RouteContext context) async {
  // Access route information
  String route = context.routeName;
  dynamic routeData = context.data;
  Map<String, String> params = context.queryParameters;

  return next();
}

Mentransformasi Route Context

Membuat salinan dengan data yang berbeda:

// Change the data type
RouteContext<User> userContext = context.withData<User>(currentUser);

// Copy with modified fields
RouteContext updated = context.copyWith(
  data: enrichedData,
  queryParameters: {"tab": "settings"},
);

Aksi Guard

next

Lanjutkan ke guard berikutnya dalam rantai, atau navigasi ke rute jika ini adalah guard terakhir:

return next();

redirect

Arahkan ulang pengguna ke rute yang berbeda:

return redirect(LoginPage.path);

Dengan opsi tambahan:

return redirect(
  LoginPage.path,
  data: {"returnTo": context.routeName},
  navigationType: NavigationType.pushReplace,
  queryParameters: {"source": "guard"},
);
Parameter Tipe Default Deskripsi
path Object required String path rute atau RouteView
data dynamic null Data yang diteruskan ke rute redirect
queryParameters Map<String, dynamic>? null Parameter query
navigationType NavigationType pushReplace Metode navigasi
result dynamic null Hasil yang dikembalikan
removeUntilPredicate Function? null Predikat penghapusan rute
transitionType TransitionType? null Tipe transisi halaman
onPop Function(dynamic)? null Callback saat pop

abort

Batalkan navigasi tanpa mengarahkan ulang. Pengguna tetap di halaman saat ini:

return abort();

setData

Memodifikasi data yang akan diteruskan ke guard berikutnya dan rute tujuan:

@override
Future<GuardResult> onBefore(RouteContext context) async {
  User user = await fetchUser();

  // Enrich the route data
  setData({"user": user, "originalData": context.data});

  return next();
}

Menerapkan Guard ke Route

Tambahkan guard ke rute individual di file router Anda:

appRouter() => nyRoutes((router) {
  router.route(
    HomePage.path,
    (_) => HomePage(),
  ).initialRoute();

  // Add a single guard
  router.route(
    ProfilePage.path,
    (_) => ProfilePage(),
    routeGuards: [AuthRouteGuard()],
  );

  // Add multiple guards (executed in order)
  router.route(
    AdminPage.path,
    (_) => AdminPage(),
    routeGuards: [AuthRouteGuard(), AdminRoleGuard()],
  );
});

Guard Grup

Terapkan guard ke beberapa rute sekaligus menggunakan grup rute:

appRouter() => nyRoutes((router) {
  router.route(HomePage.path, (_) => HomePage()).initialRoute();

  // All routes in this group require authentication
  router.group(() {
    return {
      'prefix': '/dashboard',
      'route_guards': [AuthRouteGuard()],
    };
  }, (router) {
    router.route(DashboardPage.path, (_) => DashboardPage());
    router.route(SettingsPage.path, (_) => SettingsPage());
    router.route(ProfilePage.path, (_) => ProfilePage());
  });
});

Komposisi Guard

Nylo Website menyediakan alat untuk menyusun guard bersama-sama menjadi pola yang dapat digunakan kembali.

GuardStack

Gabungkan beberapa guard menjadi satu guard yang dapat digunakan kembali:

final protectedRoute = GuardStack([
  AuthRouteGuard(),
  VerifyEmailGuard(),
  TwoFactorGuard(),
]);

// Use the stack on a route
router.route(
  SecurePage.path,
  (_) => SecurePage(),
  routeGuards: [protectedRoute],
);

GuardStack mengeksekusi guard secara berurutan. Jika ada guard yang mengembalikan handled, guard yang tersisa akan dilewati.

ConditionalGuard

Terapkan guard hanya ketika kondisi bernilai true:

router.route(
  BetaPage.path,
  (_) => BetaPage(),
  routeGuards: [
    ConditionalGuard(
      condition: (context) => context.queryParameters.containsKey("beta"),
      guard: BetaAccessGuard(),
    ),
  ],
);

Jika kondisi mengembalikan false, guard akan dilewati dan navigasi berlanjut.

ParameterizedGuard

Buat guard yang menerima parameter konfigurasi:

class RoleGuard extends ParameterizedGuard<List<String>> {
  RoleGuard(super.params); // params = allowed roles

  @override
  Future<GuardResult> onBefore(RouteContext context) async {
    User? user = await Auth.user<User>();

    if (user == null || !params.contains(user.role)) {
      return redirect(UnauthorizedPage.path);
    }

    return next();
  }
}

// Usage
router.route(
  AdminPage.path,
  (_) => AdminPage(),
  routeGuards: [RoleGuard(["admin", "super_admin"])],
);

Contoh

Guard Autentikasi

class AuthRouteGuard extends NyRouteGuard {
  AuthRouteGuard();

  @override
  Future<GuardResult> onBefore(RouteContext context) async {
    bool isAuthenticated = await Auth.isAuthenticated();

    if (!isAuthenticated) {
      return redirect(HomePage.path);
    }

    return next();
  }
}

Guard Langganan dengan Parameter

class SubscriptionGuard extends ParameterizedGuard<List<String>> {
  SubscriptionGuard(super.params);

  @override
  Future<GuardResult> onBefore(RouteContext context) async {
    User? user = await Auth.user<User>();
    bool hasAccess = params.any((plan) => user?.subscription == plan);

    if (!hasAccess) {
      return redirect(UpgradePage.path, data: {"plans": params});
    }

    setData({"user": user});
    return next();
  }
}

// Require premium or pro subscription
router.route(
  PremiumPage.path,
  (_) => PremiumPage(),
  routeGuards: [
    AuthRouteGuard(),
    SubscriptionGuard(["premium", "pro"]),
  ],
);

Guard Pencatatan

class LoggingGuard extends NyRouteGuard {
  LoggingGuard();

  @override
  Future<GuardResult> onBefore(RouteContext context) async {
    print("Navigating to: ${context.routeName}");
    return next();
  }

  @override
  Future<void> onAfter(RouteContext context) async {
    print("Arrived at: ${context.routeName}");
  }
}