Skip to main content

31 posts tagged with "flutter"

View All Tags

Lập trình Flutter đa nền tảng Level 3 (03.2025) - Buổi 1

· 3 min read

Lập trình Flutter đa nền tảng Level 3 (03.2025) - Buổi 1

Video


Giới thiệu

Buổi học đầu tiên của khóa học Lập trình Flutter đa nền tảng Level 3 tập trung vào việc xây dựng kiến trúc ứng dụng Flutter kết hợp với backend và thực hành gọi API. Đây là bước đầu tiên để các bạn hiểu rõ hơn về cách xây dựng một ứng dụng hoàn chỉnh từ frontend đến backend.


Nội dung chính

1. Tổng quan về kiến trúc Flutter kết hợp với backend

  • Frontend (Flutter App): Giao diện người dùng (UI), quản lý trạng thái (State Management), và xử lý dữ liệu (Repository).
  • Backend: API, cơ sở dữ liệu (Database), và các dịch vụ hỗ trợ (Services).
  • Luồng dữ liệu: Cách dữ liệu được truyền từ backend lên frontend và ngược lại.

2. Các kỹ năng cần thiết để lập trình Flutter Level 3

  • Quản lý trạng thái nâng cao: Sử dụng các thư viện như Provider, Bloc, Riverpod.
  • Làm việc với API: Gọi API, xử lý dữ liệu JSON, và tích hợp với backend.
  • Xây dựng kiến trúc ứng dụng: Tạo sơ đồ kiến trúc rõ ràng để dễ dàng bảo trì và mở rộng.
  • Triển khai ứng dụng: Đóng gói và xuất bản ứng dụng lên Google Play Store và Apple App Store.

3. Thực hành gọi API

  • Demo API: Sử dụng API mẫu để hiển thị danh sách sản phẩm trên ứng dụng Flutter.
  • Các bước thực hiện:
    1. Tạo project Flutter.
    2. Thêm thư viện hỗ trợ gọi API (ví dụ: http, dio).
    3. Xây dựng model để định nghĩa dữ liệu.
    4. Gọi API và hiển thị dữ liệu lên giao diện.

Bài tập thực hành

  • Yêu cầu: Xây dựng một ứng dụng Flutter đơn giản để hiển thị danh sách sản phẩm từ API.
  • Các bước thực hiện:
    1. Vẽ sơ đồ kiến trúc ứng dụng.
    2. Tạo project Flutter và cấu hình các thư viện cần thiết.
    3. Gọi API và hiển thị dữ liệu lên giao diện.

Kết luận

Buổi học đầu tiên đã giúp các bạn nắm được tổng quan về kiến trúc ứng dụng Flutter kết hợp với backend và cách gọi API để hiển thị dữ liệu. Đây là nền tảng quan trọng để các bạn tiếp tục phát triển các kỹ năng lập trình Flutter ở mức độ nâng cao.

👉 Xem video hướng dẫn chi tiết

Flutter: Button, TextField và Form widgets

· 4 min read

Button, TextField và Form là những widget quan trọng để tạo giao diện tương tác trong ứng dụng Flutter. Bài viết này sẽ hướng dẫn bạn cách sử dụng chúng một cách hiệu quả.

1. Button Widgets

Flutter cung cấp nhiều loại button khác nhau để phù hợp với các nhu cầu khác nhau.

Button Widget Examples

1.1. ElevatedButton

ElevatedButton(
onPressed: () {
// Xử lý sự kiện khi button được nhấn
},
child: const Text('Elevated Button'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
)

1.2. TextButton

TextButton(
onPressed: () {
// Xử lý sự kiện khi button được nhấn
},
child: const Text('Text Button'),
style: TextButton.styleFrom(
foregroundColor: Colors.blue,
),
)

1.3. IconButton

IconButton(
onPressed: () {
// Xử lý sự kiện khi button được nhấn
},
icon: const Icon(Icons.favorite),
color: Colors.red,
)

1.4. OutlinedButton

OutlinedButton(
onPressed: () {
// Xử lý sự kiện khi button được nhấn
},
child: const Text('Outlined Button'),
style: OutlinedButton.styleFrom(
side: const BorderSide(color: Colors.blue),
),
)

2. TextField Widget

TextField widget được sử dụng để nhận input từ người dùng.

TextField Widget Examples

2.1. Basic TextField

TextField(
decoration: InputDecoration(
labelText: 'Username',
hintText: 'Enter your username',
prefixIcon: const Icon(Icons.person),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
)

2.2. Password TextField

TextField(
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
hintText: 'Enter your password',
prefixIcon: const Icon(Icons.lock),
suffixIcon: IconButton(
icon: const Icon(Icons.visibility),
onPressed: () {
// Toggle password visibility
},
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
)

2.3. Number TextField

TextField(
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: 'Age',
hintText: 'Enter your age',
prefixIcon: const Icon(Icons.numbers),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
)

3. Form Widget

Form widget được sử dụng để quản lý và validate nhiều TextField cùng lúc.

Form Widget Examples

3.1. Basic Form

final _formKey = GlobalKey<FormState>();

Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: const InputDecoration(
labelText: 'Email',
hintText: 'Enter your email',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
return null;
},
),
TextFormField(
decoration: const InputDecoration(
labelText: 'Password',
hintText: 'Enter your password',
),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Process form data
}
},
child: const Text('Submit'),
),
],
),
)

3.2. Form với Custom Validation

TextFormField(
decoration: const InputDecoration(
labelText: 'Phone Number',
hintText: 'Enter your phone number',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your phone number';
}
if (!RegExp(r'^\d{10}$').hasMatch(value)) {
return 'Please enter a valid 10-digit phone number';
}
return null;
},
)

4. Best Practices

4.1. Button

  • Sử dụng đúng loại button cho từng trường hợp
  • Thêm loading state cho button khi cần
  • Xử lý disable state khi cần thiết
  • Sử dụng const constructor khi có thể

4.2. TextField

  • Luôn có label hoặc hint text
  • Sử dụng prefix/suffix icon khi cần
  • Xử lý keyboard type phù hợp
  • Validate input khi cần thiết

4.3. Form

  • Sử dụng Form widget để quản lý nhiều field
  • Implement validation logic rõ ràng
  • Hiển thị error message rõ ràng
  • Xử lý form submission một cách an toàn

Kết luận

Button, TextField và Form là những widget cơ bản nhưng rất quan trọng trong Flutter. Việc hiểu rõ cách sử dụng chúng sẽ giúp bạn tạo ra giao diện người dùng tương tác hiệu quả.


Tài liệu tham khảo:

Cấu trúc dự án Flutter và Hello World app

· 4 min read

Flutter Project Structure

Flutter là một framework phát triển ứng dụng di động đa nền tảng của Google. Bài viết này sẽ giúp bạn hiểu rõ về cấu trúc thư mục của một dự án Flutter và cách tạo ứng dụng Hello World đầu tiên.

1. Cấu trúc thư mục dự án Flutter

Khi tạo một dự án Flutter mới, bạn sẽ thấy cấu trúc thư mục như sau:

my_flutter_app/
├── android/ # Mã nguồn Android
├── ios/ # Mã nguồn iOS
├── lib/ # Mã nguồn Dart chính
├── test/ # Unit tests và widget tests
├── pubspec.yaml # File cấu hình dự án và dependencies
└── README.md # Tài liệu dự án

Giải thích các thư mục chính:

  1. lib/

    • Chứa mã nguồn Dart chính của ứng dụng
    • File main.dart là điểm khởi đầu của ứng dụng
    • Thường được tổ chức theo mô hình:
      lib/
      ├── main.dart
      ├── screens/ # Các màn hình
      ├── widgets/ # Các widget tái sử dụng
      ├── models/ # Các model dữ liệu
      ├── services/ # Các service (API, database)
      └── utils/ # Các tiện ích
  2. android/ios/

    • Chứa mã nguồn native cho từng nền tảng
    • Thường không cần chỉnh sửa trừ khi cần tích hợp native code
  3. test/

    • Chứa các file test
    • Bao gồm unit tests và widget tests
  4. pubspec.yaml

    • File cấu hình quan trọng nhất
    • Định nghĩa:
      • Tên và phiên bản ứng dụng
      • Dependencies
      • Assets (hình ảnh, fonts)
      • Cấu hình build

2. Tạo ứng dụng Hello World

Bước 1: Tạo dự án mới

flutter create hello_world
cd hello_world

Bước 2: Cấu trúc thư mục

lib/
├── main.dart
├── screens/
│ └── home_screen.dart
└── widgets/
└── greeting_widget.dart

Bước 3: Tạo các file

main.dart

import 'package:flutter/material.dart';
import 'screens/home_screen.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Hello World App',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: const HomeScreen(),
);
}
}

screens/home_screen.dart

import 'package:flutter/material.dart';
import '../widgets/greeting_widget.dart';

class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Hello World'),
),
body: const Center(
child: GreetingWidget(),
),
);
}
}

widgets/greeting_widget.dart

import 'package:flutter/material.dart';

class GreetingWidget extends StatelessWidget {
const GreetingWidget({super.key});

@override
Widget build(BuildContext context) {
return const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Hello, World!',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 16),
Text(
'Welcome to Flutter',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
],
);
}
}

Bước 4: Chạy ứng dụng

flutter run

3. Giải thích code

MaterialApp

  • Widget gốc của ứng dụng
  • Cung cấp các thành phần cơ bản như theme, navigation
  • useMaterial3: true để sử dụng Material Design 3

Scaffold

  • Widget cung cấp cấu trúc cơ bản cho màn hình
  • Bao gồm AppBar, body, bottom navigation, drawer

StatelessWidget vs StatefulWidget

  • StatelessWidget: Widget không có state
  • StatefulWidget: Widget có state có thể thay đổi

4. Best Practices

  1. Tổ chức code

    • Tách biệt logic và UI
    • Sử dụng các widget có thể tái sử dụng
    • Đặt tên file và class rõ ràng
  2. Quản lý state

    • Sử dụng setState cho state đơn giản
    • Sử dụng state management (Provider, Bloc) cho ứng dụng lớn
  3. Performance

    • Tránh rebuild không cần thiết
    • Sử dụng const constructor khi có thể
    • Tối ưu hóa hình ảnh và assets

Kết luận

Hiểu rõ cấu trúc dự án Flutter và cách tổ chức code là bước đầu tiên quan trọng trong việc phát triển ứng dụng Flutter. Với kiến thức này, bạn có thể bắt đầu xây dựng các ứng dụng phức tạp hơn.


Tài liệu tham khảo:

Cơ bản về ngôn ngữ Dart - Lập trình hướng đối tượng

· 4 min read

Dart OOP Hero

Dart là một ngôn ngữ lập trình hướng đối tượng mạnh mẽ, hỗ trợ đầy đủ các tính năng của OOP. Bài viết này sẽ giúp bạn hiểu rõ về các khái niệm cơ bản của lập trình hướng đối tượng trong Dart.

1. Class và Object

Định nghĩa Class

Class là một khuôn mẫu để tạo ra các đối tượng. Trong Dart, chúng ta định nghĩa class như sau:

class Person {
// Thuộc tính (Properties)
String name;
int age;

// Constructor
Person(this.name, this.age);

// Phương thức (Methods)
void introduce() {
print('Xin chào, tôi là $name, $age tuổi.');
}
}

Tạo và sử dụng Object

void main() {
// Tạo đối tượng từ class
var person = Person('John', 25);

// Gọi phương thức
person.introduce();

// Truy cập thuộc tính
print(person.name); // John
print(person.age); // 25
}

2. Tính đóng gói (Encapsulation)

Dart hỗ trợ tính đóng gói thông qua các access modifier:

class BankAccount {
// Private field (bắt đầu bằng dấu _)
double _balance = 0;

// Getter
double get balance => _balance;

// Setter
set balance(double value) {
if (value >= 0) {
_balance = value;
}
}

// Public method
void deposit(double amount) {
if (amount > 0) {
_balance += amount;
}
}

// Private method
void _updateBalance(double amount) {
_balance = amount;
}
}

3. Kế thừa (Inheritance)

Dart hỗ trợ kế thừa đơn thông qua từ khóa extends:

// Class cha
class Animal {
String name;

Animal(this.name);

void makeSound() {
print('Some sound');
}
}

// Class con
class Dog extends Animal {
Dog(String name) : super(name);

@override
void makeSound() {
print('Woof!');
}

void fetch() {
print('$name is fetching the ball');
}
}

4. Đa hình (Polymorphism)

Dart hỗ trợ đa hình thông qua việc ghi đè phương thức:

class Shape {
double area() {
return 0;
}
}

class Circle extends Shape {
double radius;

Circle(this.radius);

@override
double area() {
return 3.14 * radius * radius;
}
}

class Rectangle extends Shape {
double width;
double height;

Rectangle(this.width, this.height);

@override
double area() {
return width * height;
}
}

5. Interface và Abstract Class

Abstract Class

abstract class Vehicle {
void start();
void stop();

// Có thể có phương thức có sẵn
void honk() {
print('Beep beep!');
}
}

class Car extends Vehicle {
@override
void start() {
print('Car starting...');
}

@override
void stop() {
print('Car stopping...');
}
}

Interface

Trong Dart, mọi class đều là một interface. Chúng ta có thể sử dụng từ khóa implements:

class Flyable {
void fly() {
print('Flying...');
}
}

class Bird implements Flyable {
@override
void fly() {
print('Bird is flying');
}
}

6. Mixins

Mixins cho phép tái sử dụng code giữa các class:

mixin Swimming {
void swim() {
print('Swimming...');
}
}

mixin Flying {
void fly() {
print('Flying...');
}
}

class Duck with Swimming, Flying {
void quack() {
print('Quack!');
}
}

7. Ví dụ thực tế

// Định nghĩa class Product
class Product {
final String id;
final String name;
final double price;

Product(this.id, this.name, this.price);
}

// Định nghĩa class Cart
class Cart {
final List<Product> _items = [];

void addItem(Product product) {
_items.add(product);
}

void removeItem(String productId) {
_items.removeWhere((item) => item.id == productId);
}

double get total => _items.fold(0, (sum, item) => sum + item.price);

void displayItems() {
for (var item in _items) {
print('${item.name}: \$${item.price}');
}
print('Total: \$${total}');
}
}

// Sử dụng
void main() {
var cart = Cart();

cart.addItem(Product('1', 'Laptop', 999.99));
cart.addItem(Product('2', 'Mouse', 29.99));

cart.displayItems();
}

Kết luận

Lập trình hướng đối tượng trong Dart cung cấp một cách tiếp cận mạnh mẽ và linh hoạt để tổ chức và quản lý code. Hiểu rõ các khái niệm cơ bản như class, object, inheritance, polymorphism và encapsulation sẽ giúp bạn viết code Dart hiệu quả và dễ bảo trì hơn.


Tài liệu tham khảo:

Flutter: Quản lý form và validation

· 8 min read

Form là một phần quan trọng trong hầu hết các ứng dụng. Bài viết này sẽ hướng dẫn cách quản lý form và thực hiện validation trong Flutter.

Form Validation

1. Form Widget

1.1. Cấu trúc cơ bản

class MyForm extends StatefulWidget {
@override
_MyFormState createState() => _MyFormState();
}

class _MyFormState extends State<MyForm> {
final _formKey = GlobalKey<FormState>();

@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(
labelText: 'Username',
hintText: 'Enter your username',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your username';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Xử lý form
}
},
child: Text('Submit'),
),
],
),
);
}
}

1.2. FormField Widget

class CustomFormField extends FormField<String> {
CustomFormField({
Key? key,
required String label,
required String? Function(String?) validator,
void Function(String?)? onSaved,
}) : super(
key: key,
validator: validator,
onSaved: onSaved,
builder: (FormFieldState<String> state) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label),
TextField(
onChanged: (value) {
state.didChange(value);
},
decoration: InputDecoration(
errorText: state.errorText,
),
),
],
);
},
);
}

2. Validation

2.1. Validation cơ bản

class ValidationForm extends StatefulWidget {
@override
_ValidationFormState createState() => _ValidationFormState();
}

class _ValidationFormState extends State<ValidationForm> {
final _formKey = GlobalKey<FormState>();
String _email = '';
String _password = '';

String? _validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Email is required';
}
if (!value.contains('@')) {
return 'Please enter a valid email';
}
return null;
}

String? _validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Password is required';
}
if (value.length < 6) {
return 'Password must be at least 6 characters';
}
return null;
}

@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
validator: _validateEmail,
onSaved: (value) => _email = value ?? '',
),
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
validator: _validatePassword,
onSaved: (value) => _password = value ?? '',
),
ElevatedButton(
onPressed: _submitForm,
child: Text('Submit'),
),
],
),
);
}

void _submitForm() {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
// Xử lý form
}
}
}

2.2. Validation nâng cao

class AdvancedValidationForm extends StatefulWidget {
@override
_AdvancedValidationFormState createState() => _AdvancedValidationFormState();
}

class _AdvancedValidationFormState extends State<AdvancedValidationForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _confirmPasswordController = TextEditingController();

@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
_confirmPasswordController.dispose();
super.dispose();
}

String? _validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Email is required';
}
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegex.hasMatch(value)) {
return 'Please enter a valid email';
}
return null;
}

String? _validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Password is required';
}
if (value.length < 8) {
return 'Password must be at least 8 characters';
}
if (!value.contains(RegExp(r'[A-Z]'))) {
return 'Password must contain at least one uppercase letter';
}
if (!value.contains(RegExp(r'[0-9]'))) {
return 'Password must contain at least one number';
}
return null;
}

String? _validateConfirmPassword(String? value) {
if (value != _passwordController.text) {
return 'Passwords do not match';
}
return null;
}

@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _emailController,
decoration: InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
),
keyboardType: TextInputType.emailAddress,
validator: _validateEmail,
),
TextFormField(
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock),
),
obscureText: true,
validator: _validatePassword,
),
TextFormField(
controller: _confirmPasswordController,
decoration: InputDecoration(
labelText: 'Confirm Password',
prefixIcon: Icon(Icons.lock_outline),
),
obscureText: true,
validator: _validateConfirmPassword,
),
ElevatedButton(
onPressed: _submitForm,
child: Text('Register'),
),
],
),
);
}

void _submitForm() {
if (_formKey.currentState!.validate()) {
// Xử lý form
}
}
}

3. Form với Provider

3.1. Form Provider

class FormProvider extends ChangeNotifier {
String _email = '';
String _password = '';
bool _isLoading = false;
String? _error;

String get email => _email;
String get password => _password;
bool get isLoading => _isLoading;
String? get error => _error;

void updateEmail(String value) {
_email = value;
notifyListeners();
}

void updatePassword(String value) {
_password = value;
notifyListeners();
}

Future<void> submitForm() async {
_isLoading = true;
_error = null;
notifyListeners();

try {
// Xử lý form
await Future.delayed(Duration(seconds: 1)); // Giả lập API call
} catch (e) {
_error = e.toString();
} finally {
_isLoading = false;
notifyListeners();
}
}
}

3.2. Form với Provider

class ProviderForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => FormProvider(),
child: Consumer<FormProvider>(
builder: (context, formProvider, child) {
return Form(
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
onChanged: formProvider.updateEmail,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Email is required';
}
return null;
},
),
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
onChanged: formProvider.updatePassword,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Password is required';
}
return null;
},
),
if (formProvider.error != null)
Text(
formProvider.error!,
style: TextStyle(color: Colors.red),
),
ElevatedButton(
onPressed: formProvider.isLoading
? null
: formProvider.submitForm,
child: formProvider.isLoading
? CircularProgressIndicator()
: Text('Submit'),
),
],
),
);
},
),
);
}
}

4. Best Practices

4.1. Tách biệt logic validation

class ValidationRules {
static String? validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Email is required';
}
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegex.hasMatch(value)) {
return 'Please enter a valid email';
}
return null;
}

static String? validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Password is required';
}
if (value.length < 8) {
return 'Password must be at least 8 characters';
}
return null;
}
}

4.2. Xử lý lỗi và loading state

class FormState {
final bool isLoading;
final String? error;
final Map<String, String> fieldErrors;

FormState({
this.isLoading = false,
this.error,
this.fieldErrors = const {},
});

FormState copyWith({
bool? isLoading,
String? error,
Map<String, String>? fieldErrors,
}) {
return FormState(
isLoading: isLoading ?? this.isLoading,
error: error ?? this.error,
fieldErrors: fieldErrors ?? this.fieldErrors,
);
}
}

5. Ví dụ thực tế

5.1. Form đăng ký

class RegistrationForm extends StatefulWidget {
@override
_RegistrationFormState createState() => _RegistrationFormState();
}

class _RegistrationFormState extends State<RegistrationForm> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _isLoading = false;

@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}

Future<void> _submitForm() async {
if (_formKey.currentState!.validate()) {
setState(() {
_isLoading = true;
});

try {
// Xử lý đăng ký
await Future.delayed(Duration(seconds: 1));
// Chuyển hướng sau khi đăng ký thành công
} catch (e) {
// Hiển thị lỗi
} finally {
setState(() {
_isLoading = false;
});
}
}
}

@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _nameController,
decoration: InputDecoration(
labelText: 'Full Name',
prefixIcon: Icon(Icons.person),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Name is required';
}
return null;
},
),
TextFormField(
controller: _emailController,
decoration: InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
),
keyboardType: TextInputType.emailAddress,
validator: ValidationRules.validateEmail,
),
TextFormField(
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock),
),
obscureText: true,
validator: ValidationRules.validatePassword,
),
ElevatedButton(
onPressed: _isLoading ? null : _submitForm,
child: _isLoading
? CircularProgressIndicator()
: Text('Register'),
),
],
),
);
}
}

5.2. Form đăng nhập

class LoginForm extends StatefulWidget {
@override
_LoginFormState createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _isLoading = false;
bool _obscurePassword = true;

@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}

Future<void> _submitForm() async {
if (_formKey.currentState!.validate()) {
setState(() {
_isLoading = true;
});

try {
// Xử lý đăng nhập
await Future.delayed(Duration(seconds: 1));
// Chuyển hướng sau khi đăng nhập thành công
} catch (e) {
// Hiển thị lỗi
} finally {
setState(() {
_isLoading = false;
});
}
}
}

@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _emailController,
decoration: InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
),
keyboardType: TextInputType.emailAddress,
validator: ValidationRules.validateEmail,
),
TextFormField(
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
),
obscureText: _obscurePassword,
validator: ValidationRules.validatePassword,
),
ElevatedButton(
onPressed: _isLoading ? null : _submitForm,
child: _isLoading
? CircularProgressIndicator()
: Text('Login'),
),
],
),
);
}
}

Kết luận

Quản lý form và validation là một phần quan trọng trong phát triển ứng dụng Flutter. Việc hiểu và áp dụng đúng cách các kỹ thuật quản lý form và validation sẽ giúp bạn tạo ra trải nghiệm người dùng tốt hơn và giảm thiểu lỗi trong ứng dụng.


Tài liệu tham khảo:

Flutter: Navigation và Routing cơ bản

· 4 min read

Navigation và Routing là những khái niệm quan trọng trong phát triển ứng dụng Flutter. Bài viết này sẽ hướng dẫn bạn cách thực hiện điều hướng giữa các màn hình và quản lý route trong ứng dụng Flutter.

Navigation &amp; Routing

1. Navigation cơ bản

1.1. Điều hướng đơn giản

// Điều hướng đến màn hình mới
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);

// Quay lại màn hình trước
Navigator.pop(context);

1.2. Điều hướng với tham số

// Điều hướng và truyền tham số
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(item: item),
),
);

// Nhận tham số trong màn hình đích
class DetailScreen extends StatelessWidget {
final Item item;

const DetailScreen({Key? key, required this.item}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(item.title)),
body: Center(child: Text(item.description)),
);
}
}

2. Named Routes

2.1. Định nghĩa routes

MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/details': (context) => DetailScreen(),
'/settings': (context) => SettingsScreen(),
},
);

2.2. Điều hướng với named routes

// Điều hướng đến route đã đặt tên
Navigator.pushNamed(context, '/details');

// Điều hướng và truyền tham số
Navigator.pushNamed(
context,
'/details',
arguments: item,
);

// Nhận tham số trong màn hình đích
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final item = ModalRoute.of(context)!.settings.arguments as Item;
return Scaffold(
appBar: AppBar(title: Text(item.title)),
body: Center(child: Text(item.description)),
);
}
}

3. Nested Navigation

3.1. Bottom Navigation Bar

class MainScreen extends StatefulWidget {
@override
_MainScreenState createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
int _selectedIndex = 0;

final List<Widget> _screens = [
HomeScreen(),
SearchScreen(),
ProfileScreen(),
];

@override
Widget build(BuildContext context) {
return Scaffold(
body: _screens[_selectedIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: (index) {
setState(() {
_selectedIndex = index;
});
},
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
),
);
}
}

3.2. TabBar

class TabScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: [
Tab(icon: Icon(Icons.home), text: 'Home'),
Tab(icon: Icon(Icons.search), text: 'Search'),
Tab(icon: Icon(Icons.person), text: 'Profile'),
],
),
),
body: TabBarView(
children: [
HomeScreen(),
SearchScreen(),
ProfileScreen(),
],
),
),
);
}
}

4. Best Practices

4.1. Quản lý Route

  • Sử dụng named routes cho các màn hình chính
  • Tổ chức routes theo cấu trúc rõ ràng
  • Xử lý các trường hợp route không tồn tại
MaterialApp(
onUnknownRoute: (settings) {
return MaterialPageRoute(
builder: (context) => NotFoundScreen(),
);
},
);

4.2. Xử lý Back Button

WillPopScope(
onWillPop: () async {
// Xử lý khi người dùng nhấn nút back
return await showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Thoát?'),
content: Text('Bạn có muốn thoát không?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: Text('Không'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: Text('Có'),
),
],
),
);
},
child: Scaffold(
// ...
),
);

4.3. Deep Linking

MaterialApp(
onGenerateRoute: (settings) {
// Xử lý deep link
if (settings.name?.startsWith('/product/') ?? false) {
final productId = settings.name!.split('/').last;
return MaterialPageRoute(
builder: (context) => ProductScreen(id: productId),
);
}
return null;
},
);

5. Ví dụ thực tế

5.1. Ứng dụng E-commerce

class EcommerceApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/categories': (context) => CategoriesScreen(),
'/product': (context) => ProductScreen(),
'/cart': (context) => CartScreen(),
'/checkout': (context) => CheckoutScreen(),
},
);
}
}

5.2. Ứng dụng Social Media

class SocialApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => FeedScreen(),
'/profile': (context) => ProfileScreen(),
'/messages': (context) => MessagesScreen(),
'/notifications': (context) => NotificationsScreen(),
},
);
}
}

Kết luận

Navigation và Routing là những khái niệm cơ bản nhưng quan trọng trong phát triển ứng dụng Flutter. Việc hiểu rõ và áp dụng đúng cách sẽ giúp bạn tạo ra trải nghiệm người dùng tốt hơn.


Tài liệu tham khảo:

Flutter: Hoàn thiện và tối ưu ứng dụng

· 4 min read

Hoàn thiện và tối ưu ứng dụng là bước quan trọng để sản phẩm Flutter của bạn đạt chất lượng cao, hiệu suất tốt và sẵn sàng phát hành. Bài viết này tổng hợp các bước và lưu ý quan trọng giúp bạn hoàn thiện app chuyên nghiệp.

1. Kiểm tra UI/UX

  • Đảm bảo giao diện nhất quán:
    Sử dụng một hệ thống theme (màu sắc, font chữ, icon) xuyên suốt toàn bộ app. Định nghĩa theme trong ThemeData và sử dụng lại các style đã định nghĩa.
  • Kiểm tra trải nghiệm người dùng trên nhiều thiết bị:
    Test app trên nhiều kích thước màn hình, cả Android và iOS. Sử dụng widget như LayoutBuilder, MediaQuery để responsive.
  • Sử dụng Material Design/Cupertino đúng chuẩn:
    Dùng các widget Material cho Android (Scaffold, AppBar, FloatingActionButton...) và Cupertino cho iOS (CupertinoPageScaffold, CupertinoButton...).

2. Tối ưu hiệu suất (Performance)

  • Sử dụng const constructor:
    Đánh dấu các widget không đổi bằng const để Flutter không rebuild lại không cần thiết.
    const Text('Hello');
  • Tách nhỏ widget:
    Chia nhỏ UI thành các widget con, tránh các widget lớn bị rebuild toàn bộ.
  • Dùng ListView.builder, GridView.builder:
    Đối với danh sách lớn, luôn dùng builder để chỉ render những item hiển thị.
  • Tối ưu hình ảnh:
    • Resize ảnh trước khi đưa vào app.
    • Dùng package như cached_network_image để cache ảnh.
    • Sử dụng định dạng ảnh phù hợp (WebP, JPEG).
  • Lazy loading:
    Chỉ tải dữ liệu khi cần thiết, ví dụ dùng ListView.builder với itemCount động.

3. Quản lý state hiệu quả

  • Chọn giải pháp phù hợp:
    • App nhỏ: Provider, Riverpod.
    • App lớn: Bloc, GetX, Redux.
  • Tách biệt logic và UI:
    • Đặt logic vào Provider/Bloc, UI chỉ nhận state và gọi action.
    • Áp dụng pattern MVVM, Clean Architecture nếu app lớn.
  • Tránh truyền state không cần thiết:
    • Chỉ rebuild widget thực sự cần thiết bằng cách dùng Consumer, Selector (Provider) hoặc BlocBuilder (Bloc).

4. Kiểm thử (Testing)

  • Unit test:
    Kiểm thử các hàm logic, validation, xử lý dữ liệu.
    test('Cộng hai số', () {
    expect(add(2, 3), 5);
    });
  • Widget test:
    Kiểm thử UI, tương tác widget.
    testWidgets('Kiểm tra nút bấm', (tester) async {
    await tester.pumpWidget(MyApp());
    expect(find.text('Bấm tôi'), findsOneWidget);
    });
  • Integration test:
    Kiểm thử luồng chức năng chính, mô phỏng hành vi người dùng.

5. Build, release & phân phối ứng dụng

  • Build release:
    • Android: flutter build apk --release
    • iOS: flutter build ios --release
  • Ký app, cấu hình icon, splash screen:
    • Sử dụng package flutter_launcher_icons, flutter_native_splash.
  • Obfuscation, minify code:
    • Android: flutter build apk --obfuscate --split-debug-info=build/debug-info
  • Tối ưu kích thước file APK/IPA:
    • Xóa asset không dùng, dùng --split-per-abi cho Android.

6. Theo dõi & phân tích (Monitoring & Analytics)

  • Tích hợp Firebase Crashlytics:
    Theo dõi crash, lỗi runtime.
  • Google Analytics:
    Theo dõi hành vi người dùng, sự kiện quan trọng.
  • Log lỗi:
    Ghi log các lỗi không nghiêm trọng để phân tích sau.

7. Bảo mật (Security)

  • Bảo vệ API key, thông tin nhạy cảm:
    • Không commit file chứa key lên git.
    • Sử dụng file .env và package như flutter_dotenv.
  • Sử dụng HTTPS, xác thực người dùng:
    • Luôn dùng HTTPS cho API.
    • Áp dụng xác thực JWT, OAuth2 nếu cần.
  • Kiểm tra lỗ hổng bảo mật:
    • Sử dụng tool như MobSF để scan app trước khi phát hành.

8. Hình minh họa quy trình tối ưu & hoàn thiện app

Optimize App Architecture

9. Tài liệu tham khảo

Flutter: Giới thiệu về Provider package

· 5 min read

Provider là một trong những package phổ biến nhất để quản lý state trong Flutter. Bài viết này sẽ giới thiệu về Provider và cách sử dụng nó hiệu quả.

Provider Package

1. Giới thiệu về Provider

Provider là một giải pháp quản lý state được phát triển bởi Remi Rousselet, một trong những core team member của Flutter. Provider được thiết kế để đơn giản hóa việc quản lý state và dependency injection trong ứng dụng Flutter.

1.1. Cài đặt Provider

Thêm Provider vào file pubspec.yaml:

dependencies:
provider: ^6.0.0

1.2. Các loại Provider

Provider cung cấp nhiều loại provider khác nhau:

  • Provider: Cung cấp một giá trị
  • ChangeNotifierProvider: Cung cấp một ChangeNotifier
  • ListenableProvider: Cung cấp một Listenable
  • ValueListenableProvider: Cung cấp một ValueListenable
  • StreamProvider: Cung cấp một Stream
  • FutureProvider: Cung cấp một Future

2. Sử dụng Provider

2.1. Tạo Provider

class CounterProvider extends ChangeNotifier {
int _count = 0;

int get count => _count;

void increment() {
_count++;
notifyListeners();
}

void decrement() {
_count--;
notifyListeners();
}
}

2.2. Cung cấp Provider

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => CounterProvider(),
child: MaterialApp(
home: HomeScreen(),
),
);
}
}

2.3. Sử dụng Provider

class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Provider Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Consumer<CounterProvider>(
builder: (context, counter, child) {
return Text(
'Count: ${counter.count}',
style: Theme.of(context).textTheme.headline4,
);
},
),
ElevatedButton(
onPressed: () {
context.read<CounterProvider>().increment();
},
child: Text('Increment'),
),
],
),
),
);
}
}

3. Các Pattern phổ biến

3.1. MultiProvider

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CounterProvider()),
ChangeNotifierProvider(create: (_) => ThemeProvider()),
ChangeNotifierProvider(create: (_) => UserProvider()),
],
child: MaterialApp(
home: HomeScreen(),
),
);
}
}

3.2. ProxyProvider

class UserProvider extends ChangeNotifier {
String _name = '';
String get name => _name;

void updateName(String name) {
_name = name;
notifyListeners();
}
}

class UserProfileProvider extends ChangeNotifier {
final UserProvider userProvider;
String _greeting = '';

UserProfileProvider(this.userProvider) {
userProvider.addListener(_updateGreeting);
_updateGreeting();
}

String get greeting => _greeting;

void _updateGreeting() {
_greeting = 'Hello, ${userProvider.name}!';
notifyListeners();
}

@override
void dispose() {
userProvider.removeListener(_updateGreeting);
super.dispose();
}
}

// Sử dụng
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => UserProvider()),
ChangeNotifierProxyProvider<UserProvider, UserProfileProvider>(
create: (context) => UserProfileProvider(
Provider.of<UserProvider>(context, listen: false),
),
update: (context, userProvider, previous) =>
UserProfileProvider(userProvider),
),
],
child: MyApp(),
)

4. Best Practices

4.1. Tối ưu hóa hiệu suất

// Sử dụng Consumer thay vì Provider.of
Consumer<CounterProvider>(
builder: (context, counter, child) {
return Column(
children: [
Text('Count: ${counter.count}'),
child!, // Widget không thay đổi
],
);
},
child: const StaticWidget(), // Widget được tạo một lần
)

// Sử dụng Selector để chỉ rebuild khi cần
Selector<CounterProvider, int>(
selector: (_, provider) => provider.count,
builder: (context, count, child) {
return Text('Count: $count');
},
)

4.2. Xử lý lỗi

class DataProvider extends ChangeNotifier {
String? _error;
bool _isLoading = false;
List<Data> _data = [];

String? get error => _error;
bool get isLoading => _isLoading;
List<Data> get data => _data;

Future<void> fetchData() async {
try {
_isLoading = true;
_error = null;
notifyListeners();

_data = await api.fetchData();
} catch (e) {
_error = e.toString();
} finally {
_isLoading = false;
notifyListeners();
}
}
}

5. Ví dụ thực tế

5.1. Ứng dụng Todo

class TodoProvider extends ChangeNotifier {
List<Todo> _todos = [];
bool _isLoading = false;

List<Todo> get todos => _todos;
bool get isLoading => _isLoading;

Future<void> addTodo(Todo todo) async {
_isLoading = true;
notifyListeners();

try {
final savedTodo = await api.saveTodo(todo);
_todos.add(savedTodo);
} finally {
_isLoading = false;
notifyListeners();
}
}

Future<void> toggleTodo(String id) async {
final todo = _todos.firstWhere((t) => t.id == id);
final updatedTodo = todo.copyWith(completed: !todo.completed);

try {
await api.updateTodo(updatedTodo);
final index = _todos.indexWhere((t) => t.id == id);
_todos[index] = updatedTodo;
notifyListeners();
} catch (e) {
// Xử lý lỗi
}
}
}

class TodoScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Todo List')),
body: Consumer<TodoProvider>(
builder: (context, provider, child) {
if (provider.isLoading) {
return Center(child: CircularProgressIndicator());
}

return ListView.builder(
itemCount: provider.todos.length,
itemBuilder: (context, index) {
final todo = provider.todos[index];
return TodoItem(
todo: todo,
onToggle: () => provider.toggleTodo(todo.id),
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Hiển thị dialog thêm todo mới
},
child: Icon(Icons.add),
),
);
}
}

5.2. Ứng dụng Authentication

class AuthProvider extends ChangeNotifier {
User? _user;
bool _isLoading = false;

User? get user => _user;
bool get isLoading => _isLoading;
bool get isAuthenticated => _user != null;

Future<void> login(String email, String password) async {
_isLoading = true;
notifyListeners();

try {
_user = await authService.login(email, password);
} finally {
_isLoading = false;
notifyListeners();
}
}

Future<void> logout() async {
_isLoading = true;
notifyListeners();

try {
await authService.logout();
_user = null;
} finally {
_isLoading = false;
notifyListeners();
}
}
}

class LoginScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Login')),
body: Consumer<AuthProvider>(
builder: (context, auth, child) {
if (auth.isLoading) {
return Center(child: CircularProgressIndicator());
}

return LoginForm(
onLogin: (email, password) {
auth.login(email, password);
},
);
},
),
);
}
}

Kết luận

Provider là một giải pháp quản lý state đơn giản nhưng mạnh mẽ trong Flutter. Nó giúp bạn tổ chức code tốt hơn, dễ bảo trì và mở rộng. Việc hiểu và sử dụng Provider đúng cách sẽ giúp bạn xây dựng ứng dụng Flutter hiệu quả hơn.


Tài liệu tham khảo:

Flutter: State trong StatefulWidget

· 5 min read

State là một khái niệm quan trọng trong Flutter, đặc biệt khi làm việc với StatefulWidget. Bài viết này sẽ giúp bạn hiểu rõ về cách quản lý state và các phương pháp hiệu quả.

StatefulWidget &amp; State

1. StatefulWidget và State

1.1. Cấu trúc cơ bản

class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;

void _incrementCounter() {
setState(() {
_counter++;
});
}

@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Counter: $_counter'),
ElevatedButton(
onPressed: _incrementCounter,
child: Text('Increment'),
),
],
);
}
}

1.2. Vòng đời của State

class _MyWidgetState extends State<MyWidget> {
@override
void initState() {
super.initState();
// Khởi tạo state
}

@override
void didChangeDependencies() {
super.didChangeDependencies();
// Xử lý khi dependencies thay đổi
}

@override
void didUpdateWidget(MyWidget oldWidget) {
super.didUpdateWidget(oldWidget);
// Xử lý khi widget được cập nhật
}

@override
void dispose() {
// Dọn dẹp tài nguyên
super.dispose();
}
}

2. Quản lý State

2.1. Local State

class _FormState extends State<FormWidget> {
final _formKey = GlobalKey<FormState>();
String _name = '';
String _email = '';

void _submitForm() {
if (_formKey.currentState!.validate()) {
setState(() {
// Cập nhật state
});
}
}

@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
onChanged: (value) {
setState(() {
_name = value;
});
},
),
TextFormField(
onChanged: (value) {
setState(() {
_email = value;
});
},
),
],
),
);
}
}

2.2. State với Animation

class _AnimatedWidgetState extends State<AnimatedWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;

@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _animation,
child: YourWidget(),
);
}
}

3. Best Practices

3.1. Tối ưu hóa setState

class _OptimizedWidgetState extends State<OptimizedWidget> {
int _counter = 0;
bool _isLoading = false;

void _updateCounter() {
// Chỉ cập nhật các widget cần thiết
setState(() {
_counter++;
});
}

void _loadData() async {
setState(() {
_isLoading = true;
});

// Thực hiện tác vụ bất đồng bộ
await Future.delayed(Duration(seconds: 1));

setState(() {
_isLoading = false;
});
}
}

3.2. Xử lý State phức tạp

class _ComplexWidgetState extends State<ComplexWidget> {
// Sử dụng các biến riêng biệt cho từng loại state
int _counter = 0;
String _selectedItem = '';
List<String> _items = [];
bool _isLoading = false;

// Tách logic xử lý state
void _handleCounterChange() {
setState(() {
_counter++;
});
}

void _handleItemSelection(String item) {
setState(() {
_selectedItem = item;
});
}

Future<void> _loadItems() async {
setState(() {
_isLoading = true;
});

try {
final items = await fetchItems();
setState(() {
_items = items;
_isLoading = false;
});
} catch (e) {
setState(() {
_isLoading = false;
});
// Xử lý lỗi
}
}
}

4. Ví dụ thực tế

4.1. Form với Validation

class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
String _email = '';
String _password = '';
bool _isLoading = false;

Future<void> _submitForm() async {
if (_formKey.currentState!.validate()) {
setState(() {
_isLoading = true;
});

try {
await login(_email, _password);
// Xử lý đăng nhập thành công
} catch (e) {
// Xử lý lỗi
} finally {
setState(() {
_isLoading = false;
});
}
}
}

@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
onChanged: (value) => _email = value,
validator: (value) {
if (value?.isEmpty ?? true) {
return 'Please enter your email';
}
return null;
},
),
TextFormField(
onChanged: (value) => _password = value,
obscureText: true,
validator: (value) {
if (value?.isEmpty ?? true) {
return 'Please enter your password';
}
return null;
},
),
ElevatedButton(
onPressed: _isLoading ? null : _submitForm,
child: _isLoading
? CircularProgressIndicator()
: Text('Login'),
),
],
),
);
}
}

4.2. List với CRUD Operations

class _TodoListState extends State<TodoList> {
List<Todo> _todos = [];
bool _isLoading = false;

Future<void> _loadTodos() async {
setState(() {
_isLoading = true;
});

try {
final todos = await fetchTodos();
setState(() {
_todos = todos;
});
} finally {
setState(() {
_isLoading = false;
});
}
}

Future<void> _addTodo(Todo todo) async {
setState(() {
_todos.add(todo);
});
await saveTodo(todo);
}

Future<void> _updateTodo(Todo todo) async {
setState(() {
final index = _todos.indexWhere((t) => t.id == todo.id);
if (index != -1) {
_todos[index] = todo;
}
});
await updateTodo(todo);
}

Future<void> _deleteTodo(String id) async {
setState(() {
_todos.removeWhere((todo) => todo.id == id);
});
await deleteTodo(id);
}
}

Kết luận

Quản lý state trong StatefulWidget là một kỹ năng quan trọng trong phát triển ứng dụng Flutter. Việc hiểu rõ và áp dụng đúng cách sẽ giúp bạn tạo ra ứng dụng có hiệu suất tốt và dễ bảo trì.


Tài liệu tham khảo:

Flutter: Xây dựng chức năng quản lý Tasks

· 3 min read

Quản lý tasks là một chức năng phổ biến trong các ứng dụng quản lý công việc, Todo List, dự án... Bài viết này hướng dẫn bạn xây dựng chức năng quản lý tasks hoàn chỉnh trong Flutter, sử dụng Provider để quản lý trạng thái và lưu trữ dữ liệu.

1. Mô hình dữ liệu Task

class Task {
final String id;
String title;
String description;
DateTime? deadline;
bool isCompleted;

Task({
required this.id,
required this.title,
this.description = '',
this.deadline,
this.isCompleted = false,
});
}

2. Provider quản lý danh sách tasks

import 'package:flutter/material.dart';

class TaskProvider with ChangeNotifier {
List<Task> _tasks = [];

List<Task> get tasks => _tasks;

void addTask(Task task) {
_tasks.add(task);
notifyListeners();
}

void updateTask(Task task) {
final index = _tasks.indexWhere((t) => t.id == task.id);
if (index != -1) {
_tasks[index] = task;
notifyListeners();
}
}

void deleteTask(String id) {
_tasks.removeWhere((t) => t.id == id);
notifyListeners();
}

void toggleComplete(String id) {
final index = _tasks.indexWhere((t) => t.id == id);
if (index != -1) {
_tasks[index].isCompleted = !_tasks[index].isCompleted;
notifyListeners();
}
}
}

3. Giao diện quản lý tasks

Hiển thị danh sách tasks

Consumer<TaskProvider>(
builder: (context, taskProvider, child) {
final tasks = taskProvider.tasks;
return ListView.builder(
itemCount: tasks.length,
itemBuilder: (context, index) {
final task = tasks[index];
return ListTile(
title: Text(task.title),
subtitle: Text(task.description),
trailing: Checkbox(
value: task.isCompleted,
onChanged: (_) => taskProvider.toggleComplete(task.id),
),
onTap: () {
// Chỉnh sửa task
},
onLongPress: () {
// Xóa task
taskProvider.deleteTask(task.id);
},
);
},
);
},
)

Thêm task mới

void _addTask(BuildContext context) {
final provider = Provider.of<TaskProvider>(context, listen: false);
final newTask = Task(
id: UniqueKey().toString(),
title: 'Task mới',
description: 'Mô tả...',
deadline: DateTime.now().add(Duration(days: 1)),
);
provider.addTask(newTask);
}

4. Lưu trữ tasks (local storage)

Bạn có thể sử dụng shared_preferences, hive hoặc sqflite để lưu trữ tasks. Ví dụ với shared_preferences:

import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';

Future<void> saveTasks(List<Task> tasks) async {
final prefs = await SharedPreferences.getInstance();
final tasksJson = jsonEncode(tasks.map((t) => {
'id': t.id,
'title': t.title,
'description': t.description,
'deadline': t.deadline?.toIso8601String(),
'isCompleted': t.isCompleted,
}).toList());
await prefs.setString('tasks', tasksJson);
}

5. Hình minh họa kiến trúc quản lý tasks

Task Manager Architecture

6. Best Practices

  • Sử dụng Provider hoặc Riverpod để quản lý state.
  • Tách biệt logic và UI.
  • Lưu trữ dữ liệu local hoặc cloud.
  • Sử dụng UUID cho id task.
  • Thêm xác nhận khi xóa task.

7. Tài liệu tham khảo