aedev.net
  • Trang chủ
  • AI
  • Mobile
  • Flutter
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
No Result
View All Result
SAVED POSTS
aedev.net
  • Trang chủ
  • AI
  • Mobile
  • Flutter
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
No Result
View All Result
aedev.net
No Result
View All Result
Minh họa phát triển ứng dụng mobile đa nền tảng với Flutter

Flutter IndexedStack: giữ state riêng khi chuyển giữa hai tab

Suong Nguyen by Suong Nguyen
30/10/2026
in Flutter
0
585
SHARES
3.2k
VIEWS
Summarize with ChatGPTShare to Facebook

Bạn đổi tab rồi quay lại, bộ đếm hoặc nội dung đang nhập trở về ban đầu. Một nguyên nhân là trang cũ đã bị loại khỏi cây widget khi tab khác được chọn. Nếu muốn giữ một số trang đơn giản cùng tồn tại, IndexedStack là một lựa chọn cần hiểu cả lợi ích lẫn giới hạn.

Bài đối chiếu Flutter API ngày 30/10/2026. Ví dụ dùng Material 3 NavigationBar và IndexedStack; cần Flutter SDK có các widget này. Code được rà theo API, chưa chạy build, widget test hoặc thiết bị. Các bước thao tác bên dưới là kế hoạch kiểm tra cho project thử nghiệm của bạn.

Giữ trang trong cây, chỉ đổi trang được vẽ

IndexedStack API mô tả một stack chỉ hiển thị child ở index được chọn. Các child vẫn thuộc cây widget, nên State của từng trang có thể tiếp tục tồn tại khi đổi index. Widget này không phải cơ chế tự ghi state xuống bộ nhớ bền vững.

Nếu thay cả danh sách child, đổi key hoặc loại IndexedStack khỏi cây, quy tắc giữ State lại phụ thuộc cấu trúc mới. Đừng diễn giải “giữ khi đổi tab” thành “giữ qua mọi thao tác điều hướng hoặc restart app”.

Ví dụ hai trang có bộ đếm riêng

Thay lib/main.dart trong project thử nghiệm bằng code sau. Mỗi CounterPage giữ count của chính nó; trang cha chỉ quản lý tab đang được chọn.

import 'package:flutter/material.dart';

void main() => runApp(MaterialApp(
      theme: ThemeData(useMaterial3: true),
      home: const TabsDemo(),
    ));

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

  @override
  State<TabsDemo> createState() => _TabsDemoState();
}

class _TabsDemoState extends State<TabsDemo> {
  int selected = 0;
  final pages = const [
    CounterPage(key: ValueKey('drafts'), label: 'Bản nháp'),
    CounterPage(key: ValueKey('saved'), label: 'Đã lưu'),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('State theo tab')),
      body: IndexedStack(index: selected, children: pages),
      bottomNavigationBar: NavigationBar(
        selectedIndex: selected,
        onDestinationSelected: (index) => setState(() => selected = index),
        destinations: const [
          NavigationDestination(icon: Icon(Icons.edit), label: 'Bản nháp'),
          NavigationDestination(icon: Icon(Icons.bookmark), label: 'Đã lưu'),
        ],
      ),
    );
  }
}

class CounterPage extends StatefulWidget {
  const CounterPage({super.key, required this.label});
  final String label;

  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Text('${widget.label}: $count'),
          const SizedBox(height: 12),
          FilledButton(
            onPressed: () => setState(() => count += 1),
            child: const Text('Tăng bộ đếm'),
          ),
        ],
      ),
    );
  }
}

selectedIndex và onDestinationSelected thuộc NavigationBar API. Callback chỉ đổi index; không gán count về 0 và không tạo một kho state dùng chung cho hai trang.

Kiểm tra state riêng và state được giữ

Ở tab Bản nháp, bấm tăng hai lần. Chuyển sang Đã lưu và kiểm tra bộ đếm trang này bắt đầu từ 0, rồi tăng một lần. Quay về Bản nháp: giá trị cần kiểm tra là 2, sau đó quay lại Đã lưu để kiểm tra giá trị 1.

Hai điều kiện cần được kiểm tra riêng: mỗi trang có State độc lập, và State đó vẫn còn khi child tạm không được hiển thị. Nếu chỉ bấm qua lại mà không thay dữ liệu, bạn chưa chứng minh được điều thứ hai.

Thử hot restart hoặc đóng rồi mở app. Ví dụ không lưu count xuống storage, nên không dùng việc giữ trong IndexedStack để kỳ vọng dữ liệu sống qua process mới. Đây là giới hạn có chủ đích của demo, không phải một lỗi cần thêm key để sửa.

Giữ sống có chi phí

IndexedStack không phải lazy loader. Nó giữ các child và tham gia layout; API còn ghi kích thước stack dựa trên child lớn nhất. Với nhiều màn hình nặng, việc giữ tất cả có thể tăng chi phí khởi tạo, bộ nhớ và layout. Bài không đo những chi phí đó hoặc đưa một số tab tối đa áp dụng cho mọi app.

Trang không được vẽ cũng chưa có nghĩa Timer, stream subscription hoặc công việc của ứng dụng đã tự dừng. Nếu trang có hoạt động nền, quản lý theo trạng thái tab hoặc vòng đời thích hợp. TickerMode chỉ liên quan ticker được cấu hình theo cơ chế của nó, không tự hủy mọi request hay Timer.

Offstage API cũng nhắc rằng việc ẩn child không đồng nghĩa child ngừng hoạt động. Đây là điểm cần rà khi chọn giải pháp giữ cây sống, thay vì chỉ quan sát giao diện không còn thấy trang đó.

Khi nào nên chọn cách khác?

Với hai hoặc vài trang nhỏ mà người dùng thường đổi qua lại, giữ cây là cách trực tiếp để giữ state cục bộ. Với trang tốn tài nguyên, có thể cần tách dữ liệu quan trọng ra khỏi widget, tạo trang khi cần hoặc dùng cơ chế điều hướng phù hợp hơn.

Nếu mỗi tab có lịch sử điều hướng riêng, bài toán còn bao gồm back stack của từng tab; ví dụ bộ đếm này chưa xây nested Navigator. Không coi một IndexedStack có hai child là lời giải đầy đủ cho mọi hệ thống navigation.

Lỗi thường gặp

  • Chỉ giữ selectedIndex rồi vô tình loại trang cũ khỏi cây.
  • Cho rằng const mới là thứ lưu State; State thực sự gắn với element còn tồn tại.
  • Dùng cùng một biến count ở trang cha rồi tưởng hai tab có state riêng.
  • Quên rà công việc đang chạy trong trang bị ẩn.

Chọn giải pháp theo vòng đời dữ liệu cần giữ. IndexedStack hữu ích khi bạn muốn các trang cùng tồn tại trong cây; dữ liệu cần sống lâu hơn cây widget vẫn phải có nơi quản lý riêng.

Nguồn tham khảo

  • Flutter API: IndexedStack
  • Flutter API: NavigationBar
  • Flutter API: Offstage

Tags: FlutterIndexedStackState management
SummarizeShare234
Suong Nguyen

Suong Nguyen

Related Stories

Minh họa phát triển ứng dụng mobile đa nền tảng với Flutter

Flutter LayoutBuilder: chọn layout theo vùng cha thay vì toàn cửa sổ

by Suong Nguyen
29/10/2026
0

Đặt một component vào vùng rộng 360 logical pixel để thấy vì sao kích thước cửa sổ không thay thế constraints của cha.

Minh họa phát triển ứng dụng mobile đa nền tảng với Flutter

Flutter showDialog: nhận kết quả xác nhận và kiểm tra context sau await

by Suong Nguyen
28/10/2026
0

Trả bool từ dialog qua Navigator.pop, phân biệt false với null và chỉ cập nhật giao diện khi context gọi vẫn còn mounted.

Minh họa phát triển ứng dụng mobile đa nền tảng với Flutter

Flutter Expanded và Flexible: cho Text trong Row một giới hạn chiều rộng

by Suong Nguyen
27/10/2026
0

Sửa một Row có tiêu đề dài bằng Expanded, phân biệt giới hạn tight và loose, rồi kiểm tra đúng vị trí của Padding và constraints.

Minh họa phát triển ứng dụng mobile đa nền tảng với Flutter

Flutter ValueKey: giữ state đi cùng dữ liệu khi đảo thứ tự danh sách

by Suong Nguyen
26/10/2026
0

Dựng hai hàng có bộ đếm riêng để hiểu key ổn định, vị trí widget và lý do UniqueKey mới mỗi lần build có thể làm mất state.

0 0 votes
Article Rating
Subscribe
Login
Notify of
guest
guest
0 Comments
Oldest
Newest Most Voted
AE Dev

AE Dev mang đến các bài viết về lập trình, mobile app, backend, frontend, WordPress và công nghệ, phù hợp cho developer muốn học hỏi và cập nhật kiến thức thực tế.

Recent Posts

  • React Native RefreshControl: giữ refreshing đúng vòng đời của lượt tải
  • Phỏng vấn JavaScript: await forEach không chờ các callback async
  • Android ACTION_VIEW: mở liên kết HTTPS và xử lý khi không có app nhận

Categories

  • AI
  • Backend
  • Đánh giá
  • DevOps
  • Flutter
  • Hướng dẫn
  • Lập trình Mobile
  • Phỏng vấn
  • React Native
  • Uncategorized

Weekly Newsletter

  • Buy JNews
  • Support Forum
  • Pre-sale Question
  • Contact Us

© 2026 AE Dev - Blog công nghệ, lập trình và chia sẻ kiến thức IT.

Welcome Back!

Login to your account below

Forgotten Password?

Retrieve your password

Please enter your username or email address to reset your password.

Log In
No Result
View All Result
  • Trang chủ
  • AI
  • Mobile
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
  • Flutter

© 2026 AE Dev - Blog công nghệ, lập trình và chia sẻ kiến thức IT.

wpDiscuz
0
0
Would love your thoughts, please comment.x
()
x
| Reply