
PageStorageBucket: Cái Túi Thần Kỳ Lưu Giữ Ký Ức Cuộn Trang
Chào các chiến thần Gen Z! Hôm nay, anh Creyt sẽ cùng các em 'phẫu thuật' một khái niệm nghe hơi… 'sách vở' nhưng lại cực kỳ 'thực chiến' trong Flutter: PageStorageBucket và PageStorageKey. Nghe tên có vẻ phức tạp, nhưng tin anh đi, nó chính là 'người hùng thầm lặng' giúp trải nghiệm app của các em 'mượt như lụa' đó!
1. PageStorageBucket là gì và để làm gì? (Hay: Tại sao cái list của mình cứ 'mất trí' hoài vậy?)
Các em có bao giờ lướt TikTok, cuộn đến mỏi tay, thấy một cái video hay ho rồi bấm vào xem profile của đứa đăng không? Sau đó, bấm nút back quay lại feed, phù, cái feed vẫn y nguyên ở vị trí em vừa cuộn tới, chứ không phải 'nhảy' về đầu trang đúng không? Đó chính là 'phép thuật' của việc lưu giữ trạng thái cuộn (scroll position) đó.
Trong Flutter, các widget có khả năng cuộn như ListView, GridView, CustomScrollView... khi chúng ta rời khỏi màn hình (ví dụ: navigate sang màn hình khác) rồi quay lại, theo 'mặc định' thì chúng sẽ... 'mất trí nhớ'. Tức là, chúng sẽ reset về vị trí cuộn ban đầu (thường là đầu trang). Tưởng tượng đang cuộn một danh sách sản phẩm dài dằng dặc, thấy cái ưng ý, bấm vào xem chi tiết, rồi quay lại thì nó lại 'nhảy' lên đầu. Bực mình không? Bực mình chứ!
Đây chính là lúc PageStorageBucket 'lên sàn'. Các em cứ hình dung nó như một cái 'tủ hồ sơ' thông minh, hoặc chuẩn hơn là một cái 'túi thần kỳ' có khả năng 'ghi nhớ' vị trí cuộn của từng widget scrollable. Khi một widget scrollable được gắn vào một PageStorageBucket, nó sẽ tự động lưu lại vị trí cuộn của mình vào cái túi đó trước khi bị 'biến mất' khỏi màn hình. Và khi nó 'quay trở lại', cái túi sẽ 'nhắc nhở' nó về vị trí cũ. Tuyệt vời chưa!
Còn PageStorageKey là gì? Đơn giản thôi. Nếu PageStorageBucket là cái tủ hồ sơ, thì mỗi cái PageStorageKey chính là cái 'nhãn' hay 'mã số' duy nhất mà các em dán lên từng 'hồ sơ' (tức là từng widget scrollable). Nhờ có cái nhãn này, cái tủ mới biết 'ký ức cuộn' này là của 'ai', để sau này trả lại đúng chỗ. Không có PageStorageKey, cái tủ sẽ không biết phải lưu hay lấy ký ức cho widget nào đâu nha!
2. Code Ví Dụ Minh Họa: 'Hồi Ức' Cho ListView
Để các em dễ hình dung, anh Creyt sẽ dựng một ví dụ đơn giản: Một app có 2 màn hình. Màn hình đầu tiên là một ListView dài, màn hình thứ hai là một màn hình chi tiết. Chúng ta sẽ xem khi có và không có PageStorageKey, trải nghiệm sẽ khác nhau như thế nào.
Bước 1: Chuẩn bị app cơ bản

import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
// MaterialApp tự động cung cấp một PageStorageBucket mặc định rồi đó các em.
// Nên thường chúng ta không cần bọc thêm PageStorageBucket bên ngoài nữa.
return MaterialApp(
title: 'Flutter PageStorageBucket Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
// Tạo một PageStorageKey duy nhất cho ListView này.
// Đây là 'cái nhãn' để PageStorageBucket nhận diện và lưu trữ vị trí cuộn.
static const PageStorageKey _scrollKey = PageStorageKey('myScrollableList');
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Màn hình chính - List dài dằng dặc'),
),
body: ListView.builder(
// Đây là chỗ mấu chốt: gắn PageStorageKey vào ListView!
// Hãy thử comment dòng này và chạy lại để xem sự khác biệt nhé!
key: _scrollKey,
itemCount: 100, // Một list dài 100 items cho đã tay cuộn.
itemBuilder: (context, index) {
return Card(
margin: const EdgeInsets.all(8.0),
child: ListTile(
title: Text('Item số $index'),
subtitle: Text('Đây là chi tiết của item $index'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(itemIndex: index),
),
);
},
),
);
},
),
);
}
}
class DetailScreen extends StatelessWidget {
final int itemIndex;
const DetailScreen({super.key, required this.itemIndex});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Màn hình chi tiết'),
),
body: Center(
child: Text(
'Bạn đang xem chi tiết Item số $itemIndex',
style: const TextStyle(fontSize: 24),
),
),
);
}
}
Giải thích ví dụ:
MyApp: Là widget gốc,MaterialApptự động tạo ra mộtPageStorageBucketở cấp độ cao nhất. Điều này có nghĩa là mọi widget con bên dưới nó đều có thể truy cập và sử dụngPageStorageBucketnày. Thường thì các em không cần tự tạo thêmPageStorageBucketđâu.HomeScreen: ChứaListView.builder. Đây là nơi chúng ta cần lưu giữ vị trí cuộn._scrollKey = PageStorageKey('myScrollableList'): Đây là 'chìa khóa' quan trọng nhất. Anh Creyt đã tạo mộtPageStorageKeyvới một giá trị chuỗi duy nhất ('myScrollableList'). Giá trị chuỗi này có thể là bất cứ thứ gì miễn là nó duy nhất trong phạm vi các widget scrollable mà em muốn lưu trạng thái cuộn.key: _scrollKey: Chúng ta gán_scrollKeynày vào thuộc tínhkeycủaListView.builder. Chính nhờ dòng này màListViewcủa chúng ta 'có trí nhớ'. Khi em cuộn xuống, bấm vào mộtitem, chuyển sangDetailScreen, rồipop(quay lại)HomeScreen,ListViewsẽ tự động cuộn về đúng vị trí mà em đã rời đi.
Thử nghiệm:
- Chạy lần 1 (có
key: _scrollKey): Cuộn xuống giữa list, bấm vào một item, quay lại. Thấy list vẫn ở vị trí cũ. Tuyệt vời! - Chạy lần 2 (comment dòng
key: _scrollKey): Cuộn xuống giữa list, bấm vào một item, quay lại. Thấy list 'nhảy' về đầu trang. Bực mình không? Đó là sự khác biệt đó!
3. Mẹo Vặt & Best Practices Từ Anh Creyt (Để không bị 'lú' giữa đường)
PageStorageKeylà 'linh hồn': Luôn nhớ gán mộtPageStorageKeycho các widget scrollable mà em muốn lưu trữ vị trí cuộn. Không có nó là 'mất trí' ngay!- Đảm bảo
Keylà duy nhất: MỗiPageStorageKeynên là duy nhất trong phạm vi mà nó hoạt động. Nếu có haiListViewcùng mộtPageStorageKeytrong cùng mộtPageStorageBucket, chúng sẽ 'đánh nhau' để giành quyền lưu trữ, và kết quả là không ai nhớ đúng cả. - Không phải 'thần dược' cho mọi loại state:
PageStorageBucketđược thiết kế đặc biệt để lưu vị trí cuộn. Đừng cố gắng dùng nó để lưu các loại state phức tạp khác của widget (như dữ liệu đã nhập vào form, trạng thái bật/tắt của switch...). Đối với các loại state đó, em cần dùng các giải pháp quản lý state khác nhưProvider,Bloc,Riverpod... - Vị trí của
PageStorageBucket: Như đã nói,MaterialAppmặc định đã cung cấp mộtPageStorageBucketrồi. Nhưng nếu em có một cấu trúc widget phức tạp hơn và muốn cácBucketriêng biệt cho các phần khác nhau của ứng dụng, em hoàn toàn có thể bọc một phần widget tree bằngPageStorageBucketmới. Tuy nhiên, trong hầu hết các trường hợp,Bucketmặc định là đủ.
4. Ứng Dụng Thực Tế (Ở Đâu Rồi?)
Nói đâu xa, các em đang dùng PageStorageBucket (hoặc các cơ chế tương tự trong các framework khác) hàng ngày mà không hay biết đó:
- Mạng xã hội: Instagram, Facebook, TikTok... Khi cuộn feed, xem profile, rồi quay lại, feed vẫn ở đúng chỗ.
- Ứng dụng đọc tin tức: Các app như VnExpress, Zing News... cuộn danh sách bài viết, bấm vào đọc một bài, rồi quay lại, danh sách vẫn giữ nguyên vị trí.
- Thương mại điện tử: Shopee, Lazada, Tiki... cuộn danh sách sản phẩm, xem chi tiết, rồi quay lại, danh sách vẫn 'yên vị'.
5. Thử Nghiệm Đã Từng và Hướng Dẫn Nên Dùng Cho Case Nào
Anh Creyt đã từng 'đau đầu' với việc các ListView cứ 'mất trí nhớ' khi làm các app có nhiều tab, mỗi tab là một danh sách. Ban đầu không biết PageStorageBucket, cứ nghĩ phải tự lưu scrollOffset vào Provider hay Bloc, rất lằng nhằng và tốn công. Đến khi phát hiện ra PageStorageKey, mọi thứ như 'mở cờ trong bụng'!
Nên dùng khi nào?
- Khi em có các widget scrollable (như
ListView,GridView,CustomScrollView,PageView...) mà người dùng mong muốn trạng thái cuộn được giữ lại khi họ điều hướng tạm thời ra khỏi màn hình đó và quay lại. - Đặc biệt hữu ích trong các ứng dụng có cấu trúc
BottomNavigationBarhoặcTabBarViewnơi các tab chứa các danh sách cuộn.
Không nên dùng khi nào?
- Khi nội dung của danh sách thay đổi quá thường xuyên hoặc quá nhanh đến mức việc giữ lại vị trí cuộn không còn ý nghĩa (ví dụ: một danh sách chat real-time mà tin nhắn mới luôn đẩy lên đầu).
- Đối với các danh sách quá ngắn, việc reset về đầu trang không gây khó chịu cho người dùng.
Vậy đó, PageStorageBucket và PageStorageKey không phải là thứ gì đó 'cao siêu' khó hiểu. Nó chỉ là một 'công cụ' nhỏ nhưng cực kỳ hiệu quả để làm cho app Flutter của các em 'có tâm hồn' hơn, 'nhân văn' hơn, và mang lại trải nghiệm người dùng 'đỉnh của chóp'. Thực hành ngay đi nhé các chiến thần!
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é!