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
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:
📂 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
// user.dart
class User {
final int id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
}
dart
// 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
// 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
// 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
// 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
// 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
// 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
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
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
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
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
class LoadingWidget extends StatelessWidget {
const LoadingWidget({super.key});
@override
Widget build(BuildContext context) {
return Center(child: CircularProgressIndicator());
}
}
ContactsErrorWidget
dart
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
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