UnmanagedRestorationScope: "Khu Vực Cấm Lưu" Trạng Thái Trong Flutter
Flutter

UnmanagedRestorationScope: "Khu Vực Cấm Lưu" Trạng Thái Trong Flutter

Author

Admin System

@root

Ngày xuất bản

23 Mar, 2026

Lượt xem

3 Lượt

"UnmanagedRestorationScope"

1. UnmanagedRestorationScope là cái gì mà nghe "ghê" vậy?

Tưởng tượng thế này, app của các em giống như một game console cũ. Mỗi khi các em chơi xong, tắt máy, rồi bật lại, game sẽ bắt đầu lại từ đầu, đúng không? Nhưng có những game hiện đại hơn, nó có chức năng "save game" tự động, tức là nó sẽ ghi nhớ vị trí, điểm số, trang bị của các em ngay cả khi các em tắt máy. Trong Flutter, cái chức năng "save game" này chính là State Restoration (Khôi phục trạng thái).

Khi các em thoát app (nhưng không phải force close hoàn toàn, ví dụ như hệ điều hành tự 'giết' app để giải phóng RAM), rồi mở lại, Flutter sẽ cố gắng 'khôi phục' lại trạng thái cuối cùng của app. Ví dụ, nếu các em đang ở màn hình thứ 3, với một text field đã điền sẵn, thì khi mở lại, nó sẽ quay về màn hình thứ 3 đó và giữ nguyên nội dung text field.

UnmanagedRestorationScope giống như một 'khu vực cấm lưu' trong cái game đó vậy. Bất cứ thứ gì nằm trong phạm vi của UnmanagedRestorationScope sẽ KHÔNG được lưu lại trạng thái. Khi app được khôi phục, mọi thứ trong khu vực này sẽ trở về trạng thái ban đầu, như chưa từng có chuyện gì xảy ra. Nghe cool không?

Để làm gì? Đơn giản là có những thứ các em KHÔNG muốn nó được lưu lại. Ví dụ, một cái loading spinner, một thông báo tạm thời, hoặc một cái form mà các em muốn nó luôn trống trơn khi người dùng mở lại. Nó giúp các em kiểm soát chặt chẽ hơn trải nghiệm người dùng, đảm bảo những gì cần "fresh" thì phải fresh.

2. Code Ví Dụ minh hoạ rõ ràng, chuẩn kiến thức.

Nói có sách, mách có code! Đây là ví dụ kinh điển để các em thấy sự khác biệt giữa một widget được quản lý bởi RestorationScope và một widget bị UnmanagedRestorationScope 'từ chối' quản lý.

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', // Quan trọng để bật State Restoration cho toàn bộ app
      title: 'Creyt\'s Restoration Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with RestorationMixin {
  // Biến này sẽ được khôi phục
  final RestorableInt _managedCounter = RestorableInt(0);
  // Biến này KHÔNG được khôi phục vì nó nằm trong UnmanagedRestorationScope
  int _unmanagedCounter = 0;

  @override
  String? get restorationId => 'homePage'; // ID cho RestorationMixin

  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    // Đăng ký biến _managedCounter để nó được khôi phục
    registerForRestoration(_managedCounter, 'managedCounter');
  }

  @override
  void dispose() {
    _managedCounter.dispose(); // Đừng quên dispose Restorable-objects
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('UnmanagedRestorationScope Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // --- Widget được quản lý bởi RestorationScope ---
            const Text(
              'Managed Counter (sẽ được khôi phục):',
              style: TextStyle(fontWeight: FontWeight.bold),
            ),
            Text(
              '${_managedCounter.value}',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _managedCounter.value++;
                });
              },
              child: const Text('Tăng Managed Counter'),
            ),
            const SizedBox(height: 40),

            // --- Widget bị UnmanagedRestorationScope "từ chối" khôi phục ---
            UnmanagedRestorationScope(
              bucket: null, // Rất quan trọng: truyền null để vô hiệu hóa khôi phục
              child: Column(
                children: <Widget>[
                  const Text(
                    'Unmanaged Counter (sẽ KHÔNG được khôi phục):',
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),
                  Text(
                    '$_unmanagedCounter',
                    style: Theme.of(context).textTheme.headlineMedium,
                  ),
                  ElevatedButton(
                    onPressed: () {
                      setState(() {
                        _unmanagedCounter++;
                      });
                    },
                    child: const Text('Tăng Unmanaged Counter'),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Cách test: Các em chạy app, tăng cả hai counter lên một vài giá trị. Sau đó, đừng nhấn nút back, mà hãy đưa app xuống nền (minimize) và sau đó 'giết' app từ trình quản lý ứng dụng của điện thoại (hoặc dùng flutter run --trace-startup rồi nhấn r để hot restart với flutter run bình thường, hoặc dùng IDE để stop và run lại nhưng đảm bảo app đã bị killed hoàn toàn). Khi mở lại app, các em sẽ thấy Managed Counter vẫn giữ nguyên giá trị cũ, còn Unmanaged Counter đã reset về 0. Thấy sức mạnh của 'khu vực cấm lưu' chưa?

Illustration

3. Một vài mẹo (Best Practices) để ghi nhớ hoặc dùng thực tế.

Anh Creyt có vài chiêu để các em nhớ lâu và dùng cho đúng case này:

  • Dùng khi nào cần 'tươi mới': Nếu có một phần UI hoặc dữ liệu mà các em muốn nó luôn bắt đầu lại từ đầu mỗi khi app được mở lại (sau khi bị hệ điều hành 'giết' đi), thì UnmanagedRestorationScope là chân ái.
  • Không lạm dụng: Đừng bao giờ quăng UnmanagedRestorationScope vào mọi chỗ. Hầu hết các trạng thái trong app của các em đều muốn được khôi phục để mang lại trải nghiệm liền mạch cho người dùng. Chỉ dùng nó khi thật sự cần ngăn chặn việc khôi phục.
  • Hiểu rõ bucket: null: Điểm mấu chốt là các em phải truyền bucket: null vào UnmanagedRestorationScope. Nếu không, nó sẽ không hoạt động như mong đợi đâu. Nó giống như nói "không có cái xô nào để lưu trạng thái ở đây cả!"
  • Phân biệt với RestorationScope: RestorationScope là để tạo một phạm vi khôi phục mới với một restorationId cụ thể, trong khi UnmanagedRestorationScope là để ngăn chặn việc khôi phục trong một phạm vi đã có (hoặc một phạm vi mặc định).
  • Thường dùng cho các transient UI: Ví dụ như các AlertDialog, BottomSheet tạm thời, các widget hiển thị thông báo ngắn hạn, hoặc các form nhập liệu mà các em muốn luôn trống khi người dùng mở lại.

4. Văn phong học thuật sâu của anh Creyt, dạy dễ hiểu tuyệt đối.

Nhìn sâu hơn chút, các em sẽ thấy Flutter dùng một hệ thống gọi là RestorationBucket để lưu trữ và khôi phục trạng thái. Mỗi RestorationScope sẽ tạo ra một RestorationBucket riêng, được định danh bằng restorationId. Khi app bị 'giết' và khởi động lại, Flutter sẽ tìm kiếm các RestorationBucket này, đọc dữ liệu đã lưu và 'bơm' lại vào các RestorableProperty (như RestorableInt, RestorableString,...).

UnmanagedRestorationScope với bucket: null về cơ bản là đang 'cắt đứt' cái sợi dây liên kết giữa phần UI bên trong nó với hệ thống RestorationBucket của cha mẹ nó. Nó nói rằng: 'Phần này tự quản lý, không cần hệ thống lưu trữ chung đâu'. Vì vậy, khi hệ thống khôi phục trạng thái được kích hoạt, nó sẽ 'nhảy' qua phần này và không hề đụng chạm gì đến các trạng thái bên trong nó. Mọi thứ bên trong UnmanagedRestorationScope sẽ được khởi tạo lại như lần đầu tiên widget đó được dựng lên, bất kể trước đó nó có trạng thái gì.

5. Ví dụ thực tế các ứng dụng/website đã ứng dụng.

Trong thế giới thực, các em sẽ gặp nó ở đâu?

  • Ứng dụng ngân hàng/tài chính: Khi các em mở lại app sau một thời gian dài, màn hình đăng nhập hoặc các trường nhập liệu nhạy cảm (như mã PIN, số tiền giao dịch) luôn luôn phải trống. Không ai muốn thông tin cá nhân bị khôi phục sẵn đâu, đúng không?
  • Game mobile: Màn hình "New Game" hoặc "Start Over". Nếu người chơi đang ở giữa một màn chơi, thoát ra rồi vào lại, họ có thể muốn tiếp tục. Nhưng nếu họ chọn "New Game", thì mọi thứ phải được reset về 0, không dính dáng gì đến trạng thái cũ.
  • Ứng dụng ghi chú/editor: Các cửa sổ soạn thảo tạm thời, hoặc các trường tìm kiếm nhanh. Nếu các em đang gõ dở một cái gì đó vào ô tìm kiếm, thoát app rồi vào lại, có thể các em muốn ô tìm kiếm đó trống để bắt đầu tìm kiếm mới.
  • Các pop-up, dialog tạm thời: Một SnackBar thông báo "Đã lưu thành công!" hiển thị rồi biến mất. Khi app được khôi phục, các em không muốn cái SnackBar đó hiện lại nữa.

6. Thử nghiệm đã từng và hướng dẫn nên dùng cho case nào.

Anh Creyt đã từng 'vật lộn' với state restoration khi phát triển một app ghi chú. Ban đầu, anh cứ nghĩ 'cứ lưu tất tần tật' là tốt. Nhưng sau đó nhận ra, có những popup xác nhận xóa, hay những trường nhập liệu tạm thời, nếu được khôi phục lại sẽ gây khó chịu cho người dùng. Ví dụ, người dùng đang ở màn hình xác nhận xóa một ghi chú, thoát app, rồi vào lại, tự nhiên thấy lại cái popup xác nhận xóa đó mà không hiểu tại sao. Rất khó chịu!

Từ đó, anh học được rằng:

  • Nên dùng cho:
    • Các widget hiển thị thông tin transient (tạm thời) như SnackBar, Toast, AlertDialog (nếu không muốn chúng xuất hiện lại sau khôi phục).
    • Các form nhập liệu nhạy cảm hoặc cần được reset hoàn toàn khi người dùng mở lại app (ví dụ: form đăng nhập, form tạo mới).
    • Các UI elements chỉ có giá trị trong phiên làm việc hiện tại và không cần duy trì qua các lần khởi động lại app.
    • Các widget chứa trạng thái mà việc khôi phục chúng sẽ dẫn đến trải nghiệm người dùng không mong muốn hoặc không an toàn.
  • Không nên dùng cho:
    • Các trạng thái quan trọng của ứng dụng (ví dụ: dữ liệu người dùng, vị trí hiện tại trong một danh sách dài, trạng thái của các tab điều hướng).
    • Bất kỳ trạng thái nào mà người dùng mong đợi sẽ được giữ nguyên khi quay lại ứng dụng.

Hãy luôn đặt mình vào vị trí người dùng để quyết định. Nếu họ sẽ bất ngờ hoặc khó chịu khi thấy trạng thái đó được khôi phục, thì UnmanagedRestorationScope chính là cứu tinh của các em!

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!