NavigatorState: Đạo Diễn Sân Khấu Màn Hình Flutter Của Bạn
Flutter

NavigatorState: Đạo Diễn Sân Khấu Màn Hình Flutter Của Bạn

Author

Admin System

@root

Ngày xuất bản

19 Mar, 2026

Lượt xem

3 Lượt

"NavigatorState"

Chào các đệ tử Gen Z mê code, hôm nay anh Creyt sẽ giải mã một khái niệm mà nghe tên có vẻ khô khan nhưng lại là 'nội công thâm hậu' giúp các em điều khiển ứng dụng Flutter mượt mà như lướt TikTok: NavigatorState.

NavigatorState là gì mà 'hot' vậy Creyt?

Tưởng tượng ứng dụng Flutter của các em là một rạp chiếu phim hoành tráng, mỗi màn hình (screen) là một bộ phim đang chiếu. Navigator chính là 'người quản lý rạp' tổng thể, lo việc sắp xếp các bộ phim. Còn NavigatorState chính là 'cái bảng điều khiển' của ông quản lý đó. Nó lưu trữ toàn bộ thông tin về các bộ phim đang được chiếu, thứ tự chiếu, bộ phim nào vừa kết thúc, bộ phim nào sắp bắt đầu. Nói cách khác, nó là trạng thái hiện tại của chồng các Route (màn hình) trong ứng dụng của bạn.

Thường thì các em dùng Navigator.of(context).push(...) hay pop(...) đúng không? Ngon lành cành đào. Nhưng lỡ có lúc các em cần đẩy một màn hình mới lên, hay đóng màn hình hiện tại lại, mà lại đang ở 'hậu trường' (ví dụ: trong một service, một BLoC, hoặc một hàm không có BuildContext trực tiếp) thì sao? Lúc đó, NavigatorState được sinh ra để 'cứu bồ' đấy. Nó cho phép em điều khiển Navigator mà không cần phải 'mượn' BuildContext từ một Widget nào đó.

Làm sao để 'gọi hồn' NavigatorState từ bất cứ đâu?

Để 'gọi hồn' được cái bảng điều khiển này từ bất cứ đâu, chúng ta cần một 'đường dây nóng' đặc biệt: GlobalKey<NavigatorState>. Đây như là 'số điện thoại riêng' của ông quản lý rạp, giúp các em liên lạc trực tiếp mà không cần phải đi qua quầy vé (BuildContext) nữa.

Khi các em gán một GlobalKey<NavigatorState> vào thuộc tính navigatorKey của MaterialApp (hoặc CupertinoApp), cái GlobalKey đó sẽ giữ một tham chiếu đến NavigatorState của ứng dụng. Từ đó, bất cứ đâu trong ứng dụng, chỉ cần truy cập globalKeyCuaBan.currentState, các em sẽ có trong tay NavigatorState và có thể 'múa' các hàm như push, pop, pushNamed, popUntil, v.v.

Illustration

Code Ví Dụ Minh Hoạ Rõ Ràng

Để các em dễ hình dung, anh Creyt có một ví dụ 'nhẹ nhàng tình cảm' sau:

import 'package:flutter/material.dart';

// Bước 1: Khai báo GlobalKey<NavigatorState> ở tầm toàn cục
// hoặc ở một lớp quản lý state (ví dụ: trong Provider, Riverpod, BLoC...)
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'NavigatorState Demo',
      // Bước 2: Gán GlobalKey vào navigatorKey của MaterialApp
      // Đây là cách để NavigatorState của ứng dụng 'kết nối' với GlobalKey của chúng ta.
      navigatorKey: navigatorKey,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomeScreen(),
      // Định nghĩa các routes để có thể điều hướng bằng tên
      routes: {
        '/detail': (context) => const DetailScreen(),
      },
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  // Một hàm giả lập ở 'hậu trường' không có BuildContext.
  // Ví dụ: đây có thể là một hàm trong service xử lý push notification,
  // hoặc trong một BLoC/ChangeNotifier sau khi xử lý xong data.
  void _navigateToDetailFromBackground() {
    // Bước 3: Sử dụng GlobalKey để truy cập NavigatorState và điều hướng.
    // Luôn kiểm tra currentState có null không trước khi dùng nhé các em!
    // Bởi vì GlobalKey có thể chưa được gắn vào Navigator lúc này.
    navigatorKey.currentState?.pushNamed('/detail');
    // Hoặc nếu muốn đẩy một MaterialPageRoute trực tiếp:
    // navigatorKey.currentState?.push(MaterialPageRoute(builder: (context) => const DetailScreen()));

    // Anh Creyt hay dùng snackbar để báo hiệu đã thực hiện hành động này từ 'hậu trường'
    // Tất nhiên, để show snackbar cũng cần context hoặc một GlobalKey cho ScaffoldMessengerState
    // Nhưng ở đây ta cứ tập trung vào NavigatorState trước đã.
    print('Đã điều hướng tới Trang Chi Tiết từ background function!');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Trang Chủ - HomeScreen'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'Đây là màn hình chính.',
              style: TextStyle(fontSize: 18),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                // Cách thông thường dùng context: an toàn và phổ biến khi có context
                Navigator.of(context).pushNamed('/detail');
              },
              child: const Text('Đi tới Trang Chi Tiết (dùng context)'),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                // Gọi hàm giả lập ở 'hậu trường' để xem GlobalKey hoạt động
                _navigateToDetailFromBackground();
              },
              child: const Text('Đi tới Trang Chi Tiết (dùng GlobalKey)'),
            ),
          ],
        ),
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  const DetailScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Trang Chi Tiết - DetailScreen'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'Bạn đã đến trang chi tiết!',
              style: TextStyle(fontSize: 18),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                // Dùng GlobalKey để quay lại từ màn hình này (chỉ demo)
                // Trong thực tế, dùng Navigator.of(context).pop() sẽ phổ biến và rõ ràng hơn ở đây
                // vì chúng ta đang có BuildContext sẵn.
                navigatorKey.currentState?.pop();
              },
              child: const Text('Quay lại (dùng GlobalKey)'),
            ),
             const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                // Cách thông thường dùng context để pop: rõ ràng và dễ hiểu
                Navigator.of(context).pop();
              },
              child: const Text('Quay lại (dùng context)'),
            ),
          ],
        ),
      ),
    );
  }
}

Giải thích code:

  1. final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();: Chúng ta khai báo một GlobalKey kiểu NavigatorState. Đây là 'chìa khóa vàng' để truy cập NavigatorState.
  2. navigatorKey: navigatorKey,: Trong MaterialApp (hoặc CupertinoApp), chúng ta gán GlobalKey này vào thuộc tính navigatorKey. Điều này báo cho Flutter biết rằng NavigatorState của ứng dụng sẽ được liên kết với GlobalKey này.
  3. navigatorKey.currentState?.pushNamed('/detail');: Bây giờ, từ bất kỳ đâu (ví dụ: trong hàm _navigateToDetailFromBackground không có BuildContext), chúng ta có thể truy cập navigatorKey.currentState để lấy NavigatorState và gọi các phương thức điều hướng như pushNamed, pop, v.v.

Mẹo 'xương máu' (Best Practices) từ anh Creyt

  • Dùng khi thực sự cần: GlobalKey<NavigatorState> là một công cụ mạnh, nhưng cũng như 'dao hai lưỡi'. Chỉ dùng nó khi các em cần điều hướng hoặc hiển thị UI từ một lớp logic không có BuildContext (ví dụ: service, BLoC, ViewModel, hàm xử lý push notification).
  • Ưu tiên Navigator.of(context): Khi các em đang ở trong một Widget và có BuildContext sẵn, hãy ưu tiên dùng Navigator.of(context). Cách này rõ ràng, an toàn và dễ theo dõi hơn nhiều. GlobalKey nên là 'phương án B' khi BuildContext không khả dụng.
  • Kiểm tra null: Luôn kiểm tra navigatorKey.currentStatenull không trước khi sử dụng (navigatorKey.currentState?.push(...)). Điều này đảm bảo ứng dụng không bị crash nếu NavigatorState chưa được khởi tạo hoặc đã bị hủy.
  • Đừng lạm dụng: Lạm dụng GlobalKey có thể làm code của các em khó kiểm soát và debug hơn, vì nó tạo ra một 'kết nối toàn cục' (global access) mà không bị giới hạn bởi cây widget.

Ứng dụng thực tế: Khi nào thì NavigatorState 'tỏa sáng'?

NavigatorState (thông qua GlobalKey) thường được dùng trong các tình huống sau:

  1. Xử lý Push Notification: Khi người dùng nhấn vào một thông báo đẩy (push notification) và ứng dụng cần điều hướng đến một màn hình cụ thể, dù ứng dụng đang ở background hay đã bị terminate. Các service xử lý notification thường không có BuildContext.
  2. Trong các kiến trúc quản lý trạng thái (BLoC, Provider, Riverpod): Khi các em muốn điều hướng sau một sự kiện (ví dụ: đăng nhập thành công, tải dữ liệu hoàn tất) được xử lý trong một BLoC hoặc ViewModel mà không muốn truyền BuildContext vào đó.
  3. Hiển thị Dialog/Snackbar từ Service: Cần hiển thị một SnackBar hoặc AlertDialog từ một lớp logic không phải widget (ví dụ: để báo lỗi API). Mặc dù có GlobalKey<ScaffoldMessengerState> cho việc này, nhưng NavigatorState cũng có thể được dùng để push dialog routes.
  4. Kiểm soát luồng ứng dụng tổng thể: Trong một số trường hợp đặc biệt, khi cần can thiệp vào toàn bộ stack navigation của ứng dụng từ một điểm truy cập duy nhất.

Thử nghiệm và Nên dùng cho case nào theo Creyt

Hồi xưa anh Creyt cũng 'loay hoay' mãi vụ này. Có lần làm con app gọi API xong cần show lỗi, mà cái hàm xử lý API nó nằm tít trong cái service không có BuildContext. Cứ tưởng 'tắc tị', ai dè GlobalKey<NavigatorState> nó 'giải cứu' một bàn thua trông thấy, giúp anh Creyt push một màn hình lỗi hoặc showDialog ngay lập tức.

Nên dùng khi:

  • Các em cần điều hướng hoặc hiển thị UI (dialog, snackbar) từ một lớp logic không có BuildContext (ví dụ: Repository, Service, ViewModel, BLoC). Đây là 'lý do sống còn' của GlobalKey<NavigatorState>.
  • Khi các em muốn tạo một 'điểm truy cập toàn cục' để kiểm soát navigation cho những chức năng cốt lõi, ví dụ như xử lý deep link.

Không nên lạm dụng khi:

  • Các em đang ở trong một Widget và có BuildContext sẵn rồi. Dùng Navigator.of(context) sẽ rõ ràng và an toàn hơn nhiều, tránh được những 'cú lừa' khó debug khi GlobalKey chưa được gán hoặc bị mất liên kết.

Nói chung, NavigatorState với GlobalKey là một 'công cụ quyền năng', nhưng mà 'quyền năng lớn thì trách nhiệm lớn'. Dùng đúng lúc, đúng chỗ thì nó là 'siêu anh hùng', dùng sai thì nó thành 'phản diện' làm code khó hiểu đấy nhé! Vậy là hôm nay chúng ta đã 'mổ xẻ' xong NavigatorState. Nhớ nhé, học là phải thực hành, về nhà 'mần' ngay một con app nhỏ để thử nghiệm đi. Có gì khó khăn cứ 'alô' anh Creyt!

Thuộc Series: Flutter

Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!

#tech #cyberpunk #laravel
Chỉnh sửa bài viết

Bình luận (0)

Vui lòng Đăng Nhập để Bình luận

Hỗ trợ Markdown cơ bản
Nguyễn Văn A
1 ngày trước

Tính năng này đỉnh quá ad ơi, chờ mãi mới thấy một blog Tiếng Việt có UI/UX xịn như vầy!