
Chào các đệ tử mê code, anh Creyt đây! Hôm nay chúng ta sẽ cùng "đào" một khái niệm nghe hơi "nghiêm trọng" nhưng lại cực kỳ "chill phết" khi hiểu rõ: RestorableRouteFuture trong Flutter. Nghe cái tên đã thấy có vibe "cứu vớt" rồi đúng không?
RestorableRouteFuture là gì và để làm gì?
Tưởng tượng thế này, các bạn đang "lướt phím" trên một ứng dụng Flutter nào đó, ví dụ như đang điền một cái form đăng ký dài "thượt" hoặc đang trong quá trình thanh toán online phức tạp. Bỗng dưng, điện thoại của bạn báo pin yếu, hoặc bạn mở quá nhiều app, và "BÙM!", hệ điều hành quyết định "kill" ứng dụng của bạn để giải phóng bộ nhớ.
Khi bạn mở lại app, "cái nư" của bạn là muốn nó quay lại đúng cái màn hình bạn đang dang dở, đúng không? Chứ đâu muốn nó về lại trang chủ, rồi lại phải điền lại từ đầu, "ô dề" cực!
Đó chính là lúc RestorableRouteFuture xuất hiện như một "siêu anh hùng" của sự kiên nhẫn người dùng. Nó không chỉ là một cái "bookmark" đơn thuần. Nó giống như một cỗ máy thời gian, ghi nhớ không chỉ "bạn đang ở đâu" mà còn "bạn đang chờ đợi điều gì từ chuyến đi đó" (tức là cái Future mà route đó trả về). Khi app bị "hồi sinh", nó sẽ biết cách đưa bạn trở lại đúng cái "ngã ba đường" đó, và thậm chí còn tiếp tục chờ đợi kết quả như thể chưa hề có cuộc chia ly!
Nói một cách "học thuật" hơn, RestorableRouteFuture là một loại RestorableProperty trong Flutter, chuyên dùng để lưu trữ và khôi phục trạng thái của một Future liên quan đến việc điều hướng (navigation). Đặc biệt hữu ích khi bạn dùng Navigator.push mà có await để chờ kết quả từ màn hình tiếp theo (ví dụ: showDialog để chọn ngày, push sang màn hình chọn sản phẩm rồi trả về sản phẩm đã chọn). Nó giúp ứng dụng của bạn có khả năng "hồi phục" (state restoration) một cách mượt mà sau khi bị hệ điều hành "giết" đi và khởi động lại.
Code Ví Dụ Minh Họa: "Cuộc Phiêu Lưu Của Một Lựa Chọn"
Để "flex" cái sự mạnh mẽ của RestorableRouteFuture, anh em mình cùng xây dựng một app nhỏ nhé. App này có 2 màn hình: màn hình chính và màn hình chọn một thứ gì đó (ví dụ, chọn một con số). Màn hình chính sẽ chờ kết quả từ màn hình chọn.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
restorationScopeId: 'app_restoration_id', // Rất quan trọng cho restoration
title: 'RestorableRouteFuture Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomeScreen(),
);
}
}
// Màn hình thứ hai: Chọn một giá trị
class SelectionScreen extends StatelessWidget {
const SelectionScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Chọn Con Số Yêu Thích')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Chọn một con số để gửi về màn hình chính:'),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(42), // Trả về số 42
child: const Text('Chọn 42'),
),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(100), // Trả về số 100
child: const Text('Chọn 100'),
),
],
),
),
);
}
}
// Màn hình chính: Chờ đợi kết quả từ SelectionScreen
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> with RestorationMixin {
// 1. Khai báo RestorableRouteFuture
// Nó sẽ lưu trữ "tương lai" của việc push route
final RestorableRouteFuture<int?> _selectionRoute =
RestorableRouteFuture<int?>(onPresent: (navigator, arguments) {
// Hàm này được gọi khi route cần được "hiện diện" lại
return navigator.push<int?>(
MaterialPageRoute(builder: (context) => const SelectionScreen()));
});
// Một RestorableProperty để lưu kết quả đã chọn
final RestorableIntN _selectedNumber = RestorableIntN(null);
@override
String? get restorationId => 'home_screen_restoration_id'; // ID cho màn hình
@override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
// 2. Đăng ký RestorableProperty
registerForRestoration(_selectionRoute, 'selection_route_future');
registerForRestoration(_selectedNumber, 'selected_number');
// 3. Xử lý kết quả khi RestorableRouteFuture hoàn thành
_selectionRoute.addListener(() {
if (_selectionRoute.status == RestorableRouteFutureStatus.present) {
// Nếu route đang được hiển thị lại, chúng ta chờ kết quả
_selectionRoute.value!.then((value) {
if (value != null) {
setState(() {
_selectedNumber.value = value;
});
}
});
} else if (_selectionRoute.status == RestorableRouteFutureStatus.empty) {
// Nếu route đã hoàn thành và không còn "đợi" nữa,
// (tức là người dùng đã chọn xong hoặc thoát khỏi màn hình chọn)
// chúng ta có thể làm gì đó nếu cần.
// Trong trường hợp này, kết quả đã được xử lý ở trên.
}
});
}
// Hàm để mở màn hình chọn
void _openSelectionScreen() async {
// 4. Sử dụng RestorableRouteFuture để push route
// Thay vì dùng Navigator.push trực tiếp, ta dùng _selectionRoute.present()
final int? result = await _selectionRoute.present();
if (result != null) {
setState(() {
_selectedNumber.value = result;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Màn Hình Chính')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
_selectedNumber.value == null
? 'Chưa có số nào được chọn.'
: 'Số đã chọn: ${_selectedNumber.value}',
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 30),
ElevatedButton(
onPressed: _openSelectionScreen,
child: const Text('Đi Chọn Số!'),
),
],
),
),
);
}
@override
void dispose() {
_selectionRoute.dispose();
_selectedNumber.dispose();
super.dispose();
}
}
Cách thử nghiệm:
- Chạy ứng dụng.
- Nhấn nút "Đi Chọn Số!".
- Khi màn hình chọn số hiện ra, đừng chọn gì cả.
- Đưa ứng dụng vào chạy nền (background).
- Từ trình quản lý tác vụ của điện thoại (recent apps), "vuốt" để đóng hoàn toàn ứng dụng.
- Mở lại ứng dụng từ biểu tượng trên màn hình chính.
- Bạn sẽ thấy ứng dụng quay lại đúng màn hình "Chọn Con Số Yêu Thích" và khi bạn chọn một số, kết quả sẽ được truyền về màn hình chính như bình thường! Thật "vi diệu" đúng không?

Mẹo "Hack Não" và Best Practices từ Creyt
- Chỉ dùng khi cần thiết:
RestorableRouteFuturekhông phải là viên đạn bạc cho mọi thứ. Nó dành cho các luồng điều hướng phức tạp, nơi người dùng mong đợi quay lại đúng điểm dang dở sau khi app bị "reset". Nếu chỉ là một trang tĩnh không có tương tác gì đặc biệt, đừng "over-engineering" làm gì. - Luôn đi kèm
RestorationMixin: Giống như việc bạn không thể đi "phượt" mà không có xe vậy.RestorableRouteFuturechỉ hoạt động trong mộtStatefulWidgetcóRestorationMixin. - Hiểu rõ "Future": Nó lưu trữ cái "lời hứa" về một kết quả trong tương lai. Khi app phục hồi, nó sẽ thực hiện lại lời hứa đó (tức là push lại route), và sau đó mới chờ đợi kết quả thực sự. Đừng nhầm lẫn là nó lưu luôn kết quả nhé!
restorationScopeIdlà bắt buộc: Đảm bảoMaterialApphoặc các widget cha cóRestorationScope(hoặcrestorationScopeId) để hệ thống biết "ai đang quản lý" cácRestorablePropertycủa bạn.dispose()đừng quên: Giống như dọn dẹp sau một bữa tiệc, hãydispose()cácRestorablePropertykhiStatecủa bạn bị loại bỏ để tránh rò rỉ bộ nhớ.
Góc Học Thuật Sâu Của Anh Creyt: "Cấu Trúc Hồi Sinh"
À, cái này mới là cái "hay ho" nè các đệ tử! RestorableRouteFuture không chỉ đơn giản là "nhớ" một cái route. Nó là một phần của hệ thống State Restoration rộng lớn hơn của Flutter.
Khi bạn gọi _selectionRoute.present(), nó sẽ push một route mới vào Navigator. Đồng thời, nó cũng ghi lại vào "Restoration Bucket" một ID và thông tin rằng "có một Future đang chờ kết quả từ route này".
Khi ứng dụng bị kill và khởi động lại, hệ thống Restoration sẽ quét qua các RestorableProperty đã được đăng ký. Khi nó thấy _selectionRoute của bạn, nó sẽ kiểm tra xem có một Future nào đang "dang dở" không. Nếu có, nó sẽ gọi hàm onPresent mà bạn đã cung cấp (trong ví dụ là navigator.push<int?>(MaterialPageRoute(...))) để tái tạo lại cái route đó. Sau đó, nó sẽ gắn lại cái Future mới này vào _selectionRoute.value và lắng nghe kết quả.
Điều này có nghĩa là, màn hình SelectionScreen của bạn sẽ được tạo lại từ đầu, chứ không phải là một phiên bản "đông lạnh" của màn hình cũ. Cái hay là, từ góc nhìn của HomeScreen, nó vẫn đang "await" một Future như thể chưa có chuyện gì xảy ra. Mượt mà như một dòng code viết bởi AI!
Ứng Dụng Thực Tế và Case Nào Nên Dùng?
Các ứng dụng lớn, có luồng người dùng phức tạp, đặc biệt là các ứng dụng tài chính, thương mại điện tử, hoặc các ứng dụng có tính năng tạo/chỉnh sửa nội dung dài hơi, là những "khách hàng tiềm năng" của RestorableRouteFuture.
- Ứng dụng E-commerce: Bạn đang trong quá trình thanh toán nhiều bước (nhập địa chỉ, chọn phương thức thanh toán, xác nhận đơn hàng). Nếu app bị kill, bạn muốn người dùng quay lại đúng bước thanh toán cuối cùng.
- Ứng dụng Ngân hàng: Đang thực hiện giao dịch chuyển tiền, đến bước xác nhận OTP. App bị kill, bạn muốn người dùng quay lại màn hình nhập OTP.
- Ứng dụng Mạng xã hội/Ghi chú: Đang viết một bài đăng dài, hoặc tạo một ghi chú quan trọng. Nếu có màn hình popup xác nhận hay chọn tag/category, và app bị kill, bạn muốn quay lại màn hình soạn thảo với popup đó vẫn đang chờ.
Thử nghiệm và Hướng dẫn nên dùng cho Case nào:
Anh Creyt đã từng "vật lộn" với việc này trong các dự án lớn. Hồi xưa chưa có RestorableRouteFuture, việc khôi phục state của các Future route là một cơn ác mộng. Phải tự lưu vào shared_preferences hay database, rồi tự viết logic để kiểm tra và push lại route. "Cực hình" lắm các đệ tử ơi!
Với RestorableRouteFuture, mọi thứ trở nên "dễ thở" hơn rất nhiều.
Nên dùng khi:
- Luồng người dùng quan trọng, không thể mất dữ liệu giữa chừng: Đặc biệt là các luồng tài chính, mua sắm.
- Có các
Futuretrả về kết quả từ các route khác:showDialog,Navigator.pushvớiawait,showModalBottomSheet, v.v. - Ứng dụng có thể chạy nền lâu và có nguy cơ bị OS kill: Các ứng dụng nặng hoặc chạy trên thiết bị có ít RAM.
Không nên dùng khi:
- Màn hình không có tương tác hoặc không quan trọng việc khôi phục trạng thái điều hướng: Ví dụ, một màn hình giới thiệu (splash screen) hoặc một màn hình chỉ hiển thị thông tin tĩnh.
- Việc mất trạng thái là chấp nhận được hoặc thậm chí mong muốn: Đôi khi, việc bắt đầu lại từ đầu lại là một tính năng (ví dụ, sau khi đăng xuất).
- Bạn chỉ muốn lưu trữ state của các widget đơn lẻ: Lúc đó, các
RestorablePropertykhác nhưRestorableString,RestorableInt, v.v., sẽ phù hợp hơn.
Nhớ nhé, các đệ tử! Dùng đúng công cụ cho đúng việc, đó mới là phong thái của một lập trình viên "level max"!
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é!