0
0
Lập trình
Harry Tran
Harry Tran106580903228332612117

Hiểu và áp dụng mẫu State trong Flutter (Phần 2)

Đăng vào 1 tháng trước

• 6 phút đọc

Giới thiệu

Trong phần 2 này, chúng ta sẽ tiếp tục tìm hiểu về mẫu State trong Flutter, với trọng tâm là xây dựng một màn hình danh sách liên lạc. Mục tiêu là hiện thực hóa mẫu này một cách đơn giản, sử dụng các thực tiễn tốt nhất để quản lý trạng thái ứng dụng. Chúng ta sẽ cùng khám phá cách quản lý các trạng thái khác nhau như đang tải, lỗi và đã tải dữ liệu.

Mục lục

  1. Nhắc lại
  2. Cấu trúc
  3. Trạng thái của chúng ta
  4. ViewModel của chúng ta
  5. Màn hình của chúng ta
  6. Kết luận

Nhắc lại

Mẫu State là gì?

Mẫu State là một công cụ hữu ích trong phát triển hệ thống, giúp quản lý cách mà hệ thống phản ứng và cách nó có thể hoạt động. Việc sử dụng mẫu này giúp dễ dàng duy trì và kiểm soát những gì mà hệ thống nên hiển thị hoặc phản ứng.

Chúng ta sẽ thực hiện gì?

Như đã thảo luận trong phần 1, chúng ta sẽ sử dụng ví dụ về một màn hình danh sách liên lạc, nơi hiển thị tên người dùng tùy chỉnh, danh sách liên lạc và các thành phần khác nhau tùy thuộc vào trạng thái hiện tại (đang tải, lỗi hoặc đã tải).

Cấu trúc

Vì mục tiêu không phải là kiến trúc, chúng ta sẽ tạo 2 thư mục đơn giản: một cho kho lưu trữ và một cho giao diện người dùng, ViewModel và trạng thái. Cấu trúc dự án của chúng ta như sau:

Copy
📂 lib  
├─ 📄 main.dart - Tập tin trung tâm của dự án  
├─ 📂 pages  
│ └─ 📂 contacts_list  
│ ├─ 📄 contacts_list.viewmodel.dart - ViewModel của màn hình  
│ ├─ 📄 contacts_list.page.dart - Màn hình trung tâm của UI  
│ ├─ 📄 contacts_list.state.dart - Tập tin trạng thái  
│ └─ 📂 widgets - Thư mục chứa các thành phần sử dụng trên trang  
│ ├─ 📂 repositories  
│ ├─ 📄 contacts.repository.dart - Kho lưu trữ truy cập dữ liệu liên lạc  
│ └─ 📄 user.repository.dart - Kho lưu trữ truy cập dữ liệu người dùng  
│ └─ 📂 models  
├─ 📄 contact.dart - Mô hình liên lạc  
└─ 📄 user.dart - Mô hình người dùng

Mô hình

Các mô hình này sẽ rất đơn giản, chỉ để hiển thị thông tin.

dart Copy
// user.dart
class User {
  final int id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});
}
dart Copy
// contact.dart
class Contact {
  final int id;
  final String name;
  final String email;
  final String phoneNumber;

  Contact({
    required this.id,
    required this.name,
    required this.email,
    required this.phoneNumber,
  });
}

Kho lưu trữ

Kho lưu trữ sẽ rất đơn giản, với chỉ một hàm truy cập dữ liệu.

dart Copy
// contacts.repository.dart
import 'package:state_pattern/models/contact.dart';

class ContactsRepository {
  Future<List<Contact>> fetchContacts() async {
    await Future.delayed(Duration(seconds: 2));
    return [
      Contact(id: 1, name: 'Alice', email: 'alice@example.com', phoneNumber: '123-456-7890'),
      Contact(id: 2, name: 'Bob', email: 'bob@example.com', phoneNumber: '987-654-3210'),
      Contact(id: 3, name: 'Charlie', email: 'charlie@example.com', phoneNumber: '555-555-5555'),
    ];
  }
}
dart Copy
// user.repository.dart
import 'package:state_pattern/models/user.dart';

class UserRepository {
  Future<User> fetchUser() async {
    await Future.delayed(Duration(seconds: 2));
    return User(id: 1, name: "John Doe", email: "john.doe@example.com");
  }
}

Trạng thái của chúng ta

Khai báo

Trạng thái của chúng ta sẽ là một lớp trống, được khai báo với từ khóa "sealed".

dart Copy
// contacts_list.state.dart
sealed class ContactsListState {}

Định nghĩa các trạng thái

Chúng ta sẽ có 4 lớp mở rộng từ ContactsListState cho mỗi trạng thái.

dart Copy
// contacts_list.state.dart
class InitialState extends ContactsListState {}
class LoadingState extends ContactsListState {}
class ErrorState extends ContactsListState {
  final String message;
  ErrorState(this.message);
}
class LoadedState extends ContactsListState {
  final List<Contact> contactList;
  final User user;
  LoadedState({required this.contactList, required this.user});
}

when()

Hàm when() rất phổ biến trong BLoC, cho phép thực hiện hành động từ mỗi lớp đã định nghĩa.

dart Copy
// contacts_list.state.dart
T when<T>({
  required T Function() initial,
  required T Function() loading,
  required T Function(String message) error,
  required T Function(List<Contact> contactList, User user) loaded,
}) {
  switch (this) {
    case InitialState():
      return initial();
    case LoadingState():
      return loading();
    case ErrorState(:final message):
      return error(message);
    case LoadedState(:final contactList, :final user):
      return loaded(contactList, user);
  }
}

ViewModel của chúng ta

Khởi tạo ValueNotifier

Chúng ta sẽ sử dụng ValueNotifier để quản lý trạng thái.

dart Copy
final ValueNotifier<ContactsListState> stateNotifier = ValueNotifier(InitialState());

Constructor và phụ thuộc

Chúng ta sẽ khai báo các phụ thuộc và nhận chúng qua constructor.

dart Copy
final ContactsRepository _contactsRepository;
final UserRepository _userRepository;

ContactsListViewModel({
  required ContactsRepository contactsRepository,
  required UserRepository userRepository,
}) : _contactsRepository = contactsRepository,
     _userRepository = userRepository;

Hàm truy cập dữ liệu

Hàm getAllData() để truy cập dữ liệu.

dart Copy
Future<void> getAllData() async {
  stateNotifier.value = LoadingState();
  try {
    final user = await _userRepository.fetchUser();
    final contacts = await _contactsRepository.fetchContacts();
    stateNotifier.value = LoadedState(user: user, contactList: contacts);
  } catch (error) {
    stateNotifier.value = ErrorState(error.toString());
  }
}

Màn hình của chúng ta

Chúng ta sẽ tạo các widget riêng cho từng trạng thái.

LoadedPageWidget

dart Copy
class LoadedWidget extends StatelessWidget {
  final User user;
  final List<Contact> contacts;
  const LoadedWidget({super.key, required this.user, required this.contacts});

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          children: [
            Text('Hello, ${user.name}, here are your contacts:')
          ],
        ),
      ),
    );
  }
}

LoadingWidget

dart Copy
class LoadingWidget extends StatelessWidget {
  const LoadingWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(child: CircularProgressIndicator());
  }
}

ContactsErrorWidget

dart Copy
class ContactsErrorWidget extends StatelessWidget {
  final String message;
  const ContactsErrorWidget({super.key, required this.message});

  @override
  Widget build(BuildContext context) {
    return Center(child: Text('Error: $message'));
  }
}

Trang chính của chúng ta

dart Copy
class ContactsListPage extends StatelessWidget {
  const ContactsListPage({super.key});

  @override
  Widget build(BuildContext context) {
    final viewModel = ContactsListViewModel(
      contactsRepository: ContactsRepository(),
      userRepository: UserRepository(),
    );
    viewModel.getAllData();

    return Scaffold(
      appBar: AppBar(title: const Text('Danh sách liên lạc')),
      body: ValueListenableBuilder(
        valueListenable: viewModel.stateNotifier,
        builder: (context, state, child) {
          return state.when(
            initial: () => LoadingWidget(),
            loading: () => LoadingWidget(),
            error: (errorMessage) => ContactsErrorWidget(message: errorMessage),
            loaded: (contacts, user) => LoadedWidget(user: user, contacts: contacts),
          );
        },
      ),
    );
  }
}

Kết luận

Chúng ta đã có một tính năng hoạt động sử dụng mẫu State. Ví dụ này đã đơn giản hóa thành 3 trạng thái cơ bản, nhưng nên nhớ rằng mỗi trạng thái cần phản ánh đúng nhu cầu của ứng dụng.

Hy vọng bài viết này hữu ích cho bạn. Cảm ơn bạn đã đọc :)

Mã nguồn có sẵn trên Github của tôi: StatePatternFlutter

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào