Skip to main content

24 posts tagged with "Flutter"

View All Tags

Vì Sao Chọn Flutter

· 2 min read

1. Cross-platform Development

  • Một codebase duy nhất cho nhiều nền tảng (iOS, Android, Web, Desktop)
  • Tiết kiệm thời gian và chi phí phát triển
  • Dễ dàng bảo trì và cập nhật
  • Hot Reload giúp phát triển nhanh chóng

2. Hiệu Năng Cao

  • Biên dịch thành native code
  • Rendering engine riêng (Skia)
  • Không cần bridge như React Native
  • Hiệu năng gần như native

3. UI Đẹp và Nhất Quán

  • Widget phong phú và tùy biến cao
  • Material Design và Cupertino có sẵn
  • Custom painting và animation mạnh mẽ
  • Pixel-perfect rendering trên mọi thiết bị

4. Developer Experience Tốt

  • Hot Reload/Hot Restart
  • DevTools mạnh mẽ
  • Documentation đầy đủ
  • IDE support tốt (VS Code, Android Studio)

5. Cộng Đồng Lớn Mạnh

  • Hỗ trợ từ Google
  • Nhiều package chất lượng cao
  • Stack Overflow active
  • Cộng đồng đông đảo và nhiệt tình

6. Dart - Ngôn Ngữ Hiện Đại

  • OOP và Functional Programming
  • Strong typing
  • Null safety
  • Async/await support
  • JIT và AOT compilation

7. Tính Năng Enterprise

  • Internationalization
  • Accessibility
  • Testing framework đầy đủ
  • CI/CD support

8. Use Cases Phù Hợp

Nên Dùng Flutter Khi:

  • Cần phát triển app đa nền tảng
  • UI/UX phức tạp và đẹp
  • Time-to-market ngắn
  • Team size vừa và nhỏ

Không Nên Dùng Flutter Khi:

  • Cần tính năng platform-specific phức tạp
  • App đơn giản chỉ chạy trên một nền tảng
  • Yêu cầu về kích thước app rất nhỏ

9. Chi Phí Phát Triển

Tiết Kiệm:

  • Một team phát triển thay vì hai
  • Thời gian phát triển ngắn hơn
  • Tái sử dụng code cao
  • Bảo trì dễ dàng

10. Tương Lai của Flutter

  • Được Google đầu tư mạnh
  • Roadmap rõ ràng
  • Cập nhật thường xuyên
  • Adoption rate cao trong industry

Kết Luận

Flutter là lựa chọn tuyệt vời cho phát triển ứng dụng đa nền tảng với:

  • Hiệu năng cao
  • UI đẹp và nhất quán
  • Developer experience tốt
  • Chi phí phát triển hợp lý
  • Cộng đồng lớn mạnh

Tài Liệu Tham Khảo

Giới thiệu Lập trình Flutter đa nền tảng Full Stack

· 3 min read

Giới thiệu Lập trình Flutter đa nền tảng Full Stack

Video


Giới thiệu

Trong video này, Thanh sẽ giới thiệu về khóa học Lập trình Flutter đa nền tảng Full Stack. Khóa học này sẽ giúp các bạn nắm vững kiến thức từ frontend đến backend, bao gồm cả việc tích hợp AI vào ứng dụng. Đây là cơ hội để các bạn trở thành một lập trình viên Full Stack chuyên nghiệp.


Nội dung khóa học

1. Level 1: UI/UX cơ bản

  • Thời lượng: 15 buổi.
  • Nội dung:
    • Học cách xây dựng giao diện người dùng (UI) cơ bản với Flutter.
    • Làm quen với ngôn ngữ Dart.
    • Tạo các giao diện hấp dẫn và thân thiện với người dùng.

2. Level 2: UI/UX nâng cao và Database

  • Thời lượng: 15 buổi.
  • Nội dung:
    • Xử lý toàn diện UI trên Flutter.
    • Tích hợp các loại cơ sở dữ liệu như SQL Server, MySQL, Firebase, và MongoDB.
    • Quản lý và lưu trữ dữ liệu trong ứng dụng.

3. Level 3: Backend, API và AI

  • Thời lượng: 25 buổi.
  • Nội dung:
    • Xây dựng backend bằng Python.
    • Tích hợp AI vào ứng dụng, bao gồm chatbot và các mô hình dự đoán (ví dụ: dự đoán doanh thu, giá cổ phiếu).
    • Học cách kết hợp kiến thức từ Level 1 và Level 2 để tạo ứng dụng hoàn chỉnh.

Demo tạo API cơ bản trên Python

1. Cài đặt công cụ

  • SQL Server: Cài đặt và cấu hình cơ sở dữ liệu.
  • Visual Studio Code: Công cụ lập trình chính.
  • Thư viện Flask: Cài đặt thư viện Flask để tạo API.

2. Tạo API đơn giản

  • Kết nối cơ sở dữ liệu: Sử dụng Flask để kết nối với SQL Server.
  • Tạo bảng và chèn dữ liệu: Tạo bảng user và chèn dữ liệu mẫu.
  • Chạy API: Sử dụng Postman để kiểm tra API.

3. Kết quả

  • API được tạo thành công và có thể thêm, lấy dữ liệu từ cơ sở dữ liệu.

Lời kết

Khóa học Lập trình Flutter đa nền tảng Full Stack sẽ giúp các bạn nắm vững kiến thức từ cơ bản đến nâng cao, từ frontend đến backend, và cả việc tích hợp AI vào ứng dụng. Hãy đăng ký khóa học để bắt đầu hành trình trở thành một lập trình viên Full Stack chuyên nghiệp.

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

Tạo dự án Flutter đầu tiên và chạy trên iOS

· One min read

Tạo dự án Flutter đầu tiên và chạy trên iOS

Video


Giới thiệu

Trong video này, mình sẽ hướng dẫn các bạn cách tạo một dự án Flutter mới với tên Hello m và chạy thử ứng dụng đầu tiên của bạn chỉ trong vài bước đơn giản. Đây là bước khởi đầu để bạn xây dựng những ứng dụng tuyệt vời với Flutter.


Các bước thực hiện

1. Tạo dự án Flutter mới

  • Mở Terminal hoặc Command Prompt.
  • Gõ lệnh sau để tạo dự án Flutter mới:
    flutter create Hello_m

Webinar 16.2: Phân tích định lượng, Backend API, xây dựng Bot cho giao dịch đầu tư tài chính

· 4 min read

Webinar 16.2: Phân tích định lượng, Backend API, xây dựng Bot cho giao dịch đầu tư tài chính

Video


Giới thiệu

Buổi webinar với chủ đề "Phân tích định lượng, Backend API, xây dựng Bot cho giao dịch đầu tư tài chính" đã diễn ra với nhiều nội dung hấp dẫn và thực tế. Tiến sĩ Đặng Anh Tuấn đã chia sẻ những kiến thức quý báu và kinh nghiệm thực tiễn trong lĩnh vực phân tích giao dịch định lượng.


Nội dung chính

1. Phân tích định lượng trong giao dịch tài chính

  • Quy trình phân tích:

    • Bước 1: Phân tích kỹ thuật.
    • Bước 2: Phân tích cơ bản.
    • Bước 3: Áp dụng các thuật toán học máy.
    • Bước 4: Giao dịch tự động (Bot trading).
  • Phân tích kỹ thuật:

    • Sử dụng các chỉ báo kỹ thuật như đường trung bình động (MA), RSI, MACD.
    • Dự báo giá dựa trên biểu đồ nến và các mô hình giá.
  • Phân tích cơ bản:

    • Đánh giá các yếu tố kinh tế vĩ mô, tình hình doanh nghiệp, và các yếu tố ảnh hưởng đến thị trường.
  • Thuật toán học máy:

    • Sử dụng các mô hình như mạng neuron nhân tạo (ANN), học sâu (Deep Learning), và học tăng cường (Reinforcement Learning).
    • Huấn luyện mô hình trên dữ liệu lịch sử để dự báo giá và tối ưu hóa chiến lược giao dịch.

2. Tích hợp Backend và API

  • Backend với Python:

    • Sử dụng Python để xây dựng backend, xử lý dữ liệu và cung cấp API.
    • Các thư viện phổ biến: Flask, FastAPI, Pandas, NumPy, Scikit-learn.
  • API trong giao dịch tự động:

    • API cho phép kết nối giữa ứng dụng giao dịch và sàn giao dịch.
    • Lấy dữ liệu thời gian thực (Real-time data) và thực hiện giao dịch tự động.
  • Demo tạo API đơn giản:

    • Tạo API bằng Flask để kết nối với cơ sở dữ liệu và trả về dữ liệu dạng JSON.
    • Sử dụng Postman để kiểm tra API.

3. Xây dựng Bot giao dịch tự động

  • Bot giao dịch:

    • Bot tự động thực hiện giao dịch dựa trên các tín hiệu từ phân tích kỹ thuật và học máy.
    • Tích hợp API để lấy dữ liệu thời gian thực và thực hiện lệnh giao dịch.
  • Chiến lược giao dịch:

    • Quét thị trường để tìm các mã cổ phiếu tiềm năng.
    • Đặt lệnh mua/bán dựa trên các điều kiện đã được lập trình sẵn.
  • Xử lý độ trễ:

    • Độ trễ trong giao dịch tự động có thể ảnh hưởng đến hiệu suất.
    • Giải pháp: Sử dụng các kỹ thuật như Slip (đợi vài giây giữa các lệnh) để tránh bị chặn bởi sàn giao dịch.

4. Thách thức và giải pháp

  • Thách thức:

    • Dữ liệu thời gian thực không phải lúc nào cũng chính xác.
    • Rủi ro từ các yếu tố bên ngoài như biến động chính trị, kinh tế.
    • Độ trễ trong giao dịch tự động.
  • Giải pháp:

    • Sử dụng các thuật toán học máy để cải thiện độ chính xác của dự báo.
    • Tối ưu hóa chiến lược giao dịch để giảm thiểu rủi ro.
    • Sử dụng các nguồn dữ liệu đáng tin cậy và cập nhật thường xuyên.

Kết luận

  • Phân tích định lượnggiao dịch tự động là những công cụ mạnh mẽ giúp nhà đầu tư tối ưu hóa chiến lược và giảm thiểu rủi ro.
  • Backend và API đóng vai trò quan trọng trong việc kết nối và xử lý dữ liệu thời gian thực.
  • Bot giao dịch tự động giúp tăng hiệu suất và giảm thiểu sai sót trong quá trình giao dịch.

Tài liệu tham khảo

Webinar: Lập trình Flutter tích hợp Backend và API

· 3 min read

Webinar: Lập trình Flutter tích hợp Backend và API

Video


Giới thiệu

Buổi webinar với chủ đề "Lập trình Flutter tích hợp Backend và API" đã diễn ra với nhiều nội dung hấp dẫn và thực tế. Flutter là một framework phát triển ứng dụng di động đa nền tảng, được đánh giá cao về khả năng tạo giao diện đẹp và mượt mà. Backend và API là hai thành phần quan trọng trong việc xử lý dữ liệu và logic nghiệp vụ phía server.


Nội dung chính

1. Khái niệm Backend và API

  • Backend: Phần phía server của ứng dụng, bao gồm máy chủ, cơ sở dữ liệu và các ứng dụng xử lý logic nghiệp vụ.
    • Chức năng chính:
      • Quản lý dữ liệu.
      • Xử lý logic nghiệp vụ.
      • Xác thực và phân quyền.
      • Giao tiếp với frontend thông qua API.
  • API: Giao diện lập trình ứng dụng (Application Programming Interface), là tập hợp các quy tắc và giao thức cho phép các ứng dụng giao tiếp với nhau.
    • Vai trò:
      • Kết nối hệ thống.
      • Truy cập dịch vụ.
      • Trực quan hóa dữ liệu.

2. Tích hợp Backend và API với Flutter

  • Python làm Backend: Python được sử dụng để xây dựng backend, xử lý dữ liệu và cung cấp API.
  • Flutter làm Frontend: Flutter được sử dụng để xây dựng giao diện người dùng và kết nối với backend thông qua API.
  • Demo thực tế:
    • Tạo API đơn giản bằng Python.
    • Kết nối API với ứng dụng Flutter.
    • Hiển thị dữ liệu từ API lên giao diện Flutter.

3. Thực hành và Demo

  • Tạo API đơn giản:
    • Sử dụng Python và thư viện Flask để tạo API.
    • API trả về dữ liệu dạng JSON.
  • Kết nối API với Flutter:
    • Sử dụng package http trong Flutter để gọi API.
    • Hiển thị dữ liệu từ API lên giao diện Flutter.
  • Demo ứng dụng thực tế:
    • Ứng dụng hiển thị dữ liệu chứng khoán từ API.
    • Ứng dụng dự báo giá cổ phiếu sử dụng mô hình học máy.

4. Lợi ích của việc tích hợp Backend và API với Flutter

  • Hiệu suất cao: Flutter có khả năng hiển thị giao diện mượt mà và nhanh chóng.
  • Dễ dàng tích hợp: API giúp kết nối dễ dàng giữa frontend và backend.
  • Linh hoạt: Có thể sử dụng nhiều ngôn ngữ lập trình để xây dựng backend (Python, Node.js, Java, ...).

Kết luận

  • Flutter là một công cụ mạnh mẽ để phát triển ứng dụng di động đa nền tảng.
  • Backend và API đóng vai trò quan trọng trong việc xử lý dữ liệu và logic nghiệp vụ.
  • Python là một lựa chọn tốt để xây dựng backend và cung cấp API cho ứng dụng Flutter.

Tài liệu tham khảo

Các Phần Quan Trọng Nhất Khi Học Flutter

· 6 min read

Flutter là một framework phát triển ứng dụng di động đa nền tảng, được Google phát triển và ngày càng phổ biến nhờ hiệu suất cao, giao diện đẹp và khả năng tái sử dụng code. Để học Flutter hiệu quả, bạn cần tập trung vào các phần quan trọng sau:


1. Ngôn Ngữ Dart

  • Tại sao quan trọng?: Dart là ngôn ngữ lập trình chính của Flutter. Hiểu rõ Dart giúp bạn viết code Flutter một cách trơn tru và hiệu quả.
  • Nội dung cần học:
    • Cú pháp cơ bản: Biến, hàm, vòng lặp, điều kiện.
    • Hướng đối tượng: Class, kế thừa, mixins.
    • Xử lý bất đồng bộ: async/await, Future, Stream.
    • Collections: List, Map, Set.
  • Lưu ý: Nếu bạn đã biết các ngôn ngữ như Java, JavaScript, hoặc C#, việc học Dart sẽ dễ dàng hơn.

2. Widgets

  • Tại sao quan trọng?: Flutter sử dụng widget để xây dựng giao diện người dùng. Mọi thứ trong Flutter đều là widget, từ button đến layout.
  • Nội dung cần học:
    • StatelessWidget: Widget không thay đổi trạng thái.
    • StatefulWidget: Widget có thể thay đổi trạng thái.
    • Các widget cơ bản: Container, Row, Column, ListView, GridView, Stack, Padding, Text, Image, v.v.
    • Custom Widgets: Tạo widget tùy chỉnh để tái sử dụng code.
  • Lưu ý: Hãy thực hành nhiều với các widget để hiểu rõ cách chúng hoạt động và kết hợp với nhau.

3. State Management (Quản Lý Trạng Thái)

  • Tại sao quan trọng?: Quản lý trạng thái là một trong những phần phức tạp nhất nhưng quan trọng nhất trong Flutter. Nó quyết định cách dữ liệu được cập nhật và hiển thị trên giao diện.
  • Nội dung cần học:
    • setState: Cách cơ bản để quản lý trạng thái trong StatefulWidget.
    • Các phương pháp quản lý trạng thái phổ biến:
      • Provider: Đơn giản và dễ sử dụng.
      • Bloc/Cubit: Phù hợp cho ứng dụng lớn và phức tạp.
      • Riverpod: Phiên bản nâng cao của Provider.
      • GetX: Nhẹ và mạnh mẽ.
  • Lưu ý: Chọn một phương pháp phù hợp với dự án của bạn và học sâu về nó.

4. Navigation và Routing

  • Tại sao quan trọng?: Điều hướng giữa các màn hình là yếu tố cốt lõi trong phát triển ứng dụng di động.
  • Nội dung cần học:
    • Sử dụng Navigator để chuyển đổi giữa các màn hình.
    • Named Routes: Quản lý điều hướng dễ dàng hơn.
    • Truyền dữ liệu giữa các màn hình.
    • Deep Linking: Cho phép mở ứng dụng từ một liên kết cụ thể.
  • Lưu ý: Hãy thực hành nhiều với các loại điều hướng để hiểu rõ cách chúng hoạt động.

5. Làm Việc Với API

  • Tại sao quan trọng?: Hầu hết ứng dụng đều cần kết nối với backend để lấy dữ liệu.
  • Nội dung cần học:
    • Sử dụng package http hoặc Dio để gọi API.
    • Xử lý dữ liệu JSON: Sử dụng json_serializable hoặc convert.
    • Xử lý lỗi và hiển thị thông báo phù hợp cho người dùng.
    • Caching dữ liệu để tăng hiệu suất ứng dụng.
  • Lưu ý: Hãy thực hành với các API thực tế để hiểu rõ cách làm việc với dữ liệu từ server.

6. Animation và UI/UX

  • Tại sao quan trọng?: Animation và giao diện đẹp mắt giúp ứng dụng của bạn trở nên chuyên nghiệp và thu hút người dùng.
  • Nội dung cần học:
    • Sử dụng AnimationController, Tween, AnimatedBuilder để tạo hiệu ứng.
    • Các package hỗ trợ animation như Flutter Animation hoặc Rive.
    • Nguyên tắc thiết kế Material Design (Android) và Cupertino (iOS).
    • Tối ưu hóa hiệu suất UI để ứng dụng chạy mượt mà.
  • Lưu ý: Hãy thử nghiệm với các hiệu ứng và giao diện để tạo ra trải nghiệm người dùng tốt nhất.

7. Testing và Debugging

  • Tại sao quan trọng?: Đảm bảo ứng dụng hoạt động ổn định và không có lỗi.
  • Nội dung cần học:
    • Viết unit test, widget test, và integration test.
    • Sử dụng Flutter DevTools để debug ứng dụng.
    • Sử dụng Breakpoints, Logs, và Error Handling để phát hiện và sửa lỗi.
  • Lưu ý: Hãy viết test ngay từ đầu để tiết kiệm thời gian và công sức khi phát triển ứng dụng.

8. Firebase Integration

  • Tại sao quan trọng?: Firebase cung cấp nhiều dịch vụ backend như xác thực người dùng, cơ sở dữ liệu real-time, và lưu trữ đám mây.
  • Nội dung cần học:
    • Tích hợp Firebase vào Flutter.
    • Sử dụng Firebase Auth để xác thực người dùng.
    • Sử dụng Firestore hoặc Realtime Database để lưu trữ dữ liệu.
    • Sử dụng Cloud Storage để lưu trữ file.
    • Sử dụng Cloud Messaging để gửi thông báo đẩy.
  • Lưu ý: Firebase là một công cụ mạnh mẽ giúp bạn xây dự ứng dụng nhanh chóng mà không cần tự xây dựng backend.

9. Deploy Ứng Dụng

  • Tại sao quan trọng?: Bạn cần biết cách đưa ứng dụng lên các cửa hàng ứng dụng để người dùng có thể tải về.
  • Nội dung cần học:
    • Build ứng dụng cho Android (APK/AAB) và iOS (IPA).
    • Đăng ký tài khoản developer trên Google Play Store và Apple App Store.
    • Upload ứng dụng và quản lý phiên bản.
  • Lưu ý: Hãy đọc kỹ các hướng dẫn của Google và Apple để tránh gặp phải các vấn đề khi deploy.

Kết Luận

Các phần quan trọng nhất khi học Flutter bao gồm:

  1. Dart (nền tảng ngôn ngữ).
  2. Widgets (xây dựng giao diện).
  3. State Management (quản lý trạng thái).
  4. API Integration (kết nối dữ liệu).
  5. Navigation (điều hướng giữa các màn hình).

Hãy dành thời gian để thực hành nhiều với các phần này, vì chúng là nền tảng để bạn trở thành một Flutter developer giỏi. Chúc bạn học tập hiệu quả và thành công! 🚀

Giới Thiệu Thư Viện Dio và Lý Do Nên Dùng Thay HTTP trong Flutter

· 8 min read

Khi phát triển ứng dụng Flutter, việc gọi API là một phần không thể thiếu. Trong khi package http là lựa chọn cơ bản, Dio là một giải pháp mạnh mẽ và linh hoạt hơn nhiều. Bài viết này sẽ giới thiệu về Dio và giải thích tại sao bạn nên sử dụng nó thay vì http package.


1️⃣ Dio Là Gì?

Dio là một HTTP client mạnh mẽ cho Dart/Flutter, được phát triển bởi Flutter China. Dio cung cấp nhiều tính năng nâng cao mà package http cơ bản không có.

1.1 Cài Đặt Dio

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

dependencies:
dio: ^5.4.0

Sau đó chạy:

flutter pub get

1.2 Import Dio

import 'package:dio/dio.dart';

2️⃣ So Sánh Dio vs HTTP Package

2.1 HTTP Package - Giải Pháp Cơ Bản

import 'package:http/http.dart' as http;
import 'dart:convert';

// Gọi API với http package
Future<void> fetchData() async {
final response = await http.get(
Uri.parse('https://api.example.com/data'),
headers: {'Content-Type': 'application/json'},
);

if (response.statusCode == 200) {
final data = json.decode(response.body);
print(data);
} else {
print('Error: ${response.statusCode}');
}
}

Hạn chế của HTTP package:

  • ❌ Không có interceptors
  • ❌ Không hỗ trợ retry tự động
  • ❌ Không có request/response transformers
  • ❌ Xử lý lỗi thủ công
  • ❌ Không có timeout configuration dễ dàng
  • ❌ Không hỗ trợ cancel requests

2.2 Dio - Giải Pháp Mạnh Mẽ

import 'package:dio/dio.dart';

// Gọi API với Dio
Future<void> fetchData() async {
final dio = Dio();

try {
final response = await dio.get('https://api.example.com/data');
print(response.data);
} catch (e) {
print('Error: $e');
}
}

Ưu điểm của Dio:

  • ✅ Interceptors mạnh mẽ
  • ✅ Retry logic tự động
  • ✅ Request/Response transformers
  • ✅ Xử lý lỗi tốt hơn
  • ✅ Timeout configuration dễ dàng
  • ✅ Hỗ trợ cancel requests
  • ✅ Hỗ trợ file upload/download
  • ✅ Progress callbacks

3️⃣ Các Tính Năng Nổi Bật Của Dio

3.1 Interceptors

Dio cho phép bạn thêm interceptors để xử lý requests và responses trước/sau khi gửi/nhận:

final dio = Dio();

// Thêm interceptor để log requests
dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
));

// Thêm interceptor tùy chỉnh
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
// Thêm token vào header
options.headers['Authorization'] = 'Bearer your_token';
return handler.next(options);
},
onResponse: (response, handler) {
// Xử lý response
return handler.next(response);
},
onError: (error, handler) {
// Xử lý lỗi
return handler.next(error);
},
));

3.2 Retry Logic

Dio hỗ trợ retry tự động khi request thất bại:

import 'package:dio/retry.dart';

final dio = Dio();

dio.interceptors.add(
RetryInterceptor(
dio: dio,
options: const RetryOptions(
retries: 3,
retryInterval: Duration(seconds: 2),
),
),
);

3.3 Request/Response Transformers

Transform dữ liệu trước khi gửi/nhận:

final dio = Dio();

dio.transformer = BackgroundTransformer()..jsonDecodeCallback = parseJson;

// Custom JSON parser
Map<String, dynamic> parseJson(String text) {
// Custom parsing logic
return jsonDecode(text);
}

3.4 Timeout Configuration

Cấu hình timeout dễ dàng:

final dio = Dio(BaseOptions(
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
sendTimeout: Duration(seconds: 3),
));

3.5 Cancel Requests

Hủy request khi không cần thiết:

final cancelToken = CancelToken();

// Gọi API với cancel token
dio.get(
'https://api.example.com/data',
cancelToken: cancelToken,
);

// Hủy request
cancelToken.cancel('Request cancelled');

3.6 File Upload/Download

Dio hỗ trợ upload và download file với progress callback:

// Upload file
FormData formData = FormData.fromMap({
'file': await MultipartFile.fromFile('/path/to/file'),
});

await dio.post(
'https://api.example.com/upload',
data: formData,
onSendProgress: (sent, total) {
print('Progress: ${(sent / total * 100).toStringAsFixed(0)}%');
},
);

// Download file
await dio.download(
'https://api.example.com/file',
'/path/to/save/file',
onReceiveProgress: (received, total) {
print('Progress: ${(received / total * 100).toStringAsFixed(0)}%');
},
);

4️⃣ Lý Do Nên Dùng Dio Thay HTTP

4.1 Code Sạch Hơn và Dễ Bảo Trì

Với HTTP package:

Future<Map<String, dynamic>> fetchUser(int userId) async {
final uri = Uri.parse('https://api.example.com/users/$userId');
final response = await http.get(
uri,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
);

if (response.statusCode == 200) {
return json.decode(response.body);
} else if (response.statusCode == 401) {
throw UnauthorizedException();
} else if (response.statusCode == 404) {
throw NotFoundException();
} else {
throw ServerException();
}
}

Với Dio:

Future<Map<String, dynamic>> fetchUser(int userId) async {
try {
final response = await dio.get('/users/$userId');
return response.data;
} on DioException catch (e) {
if (e.response?.statusCode == 401) {
throw UnauthorizedException();
} else if (e.response?.statusCode == 404) {
throw NotFoundException();
} else {
throw ServerException();
}
}
}

4.2 Xử Lý Lỗi Tốt Hơn

Dio cung cấp DioException với nhiều thông tin hữu ích:

try {
await dio.get('/api/data');
} on DioException catch (e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
print('Connection timeout');
break;
case DioExceptionType.sendTimeout:
print('Send timeout');
break;
case DioExceptionType.receiveTimeout:
print('Receive timeout');
break;
case DioExceptionType.badResponse:
print('Bad response: ${e.response?.statusCode}');
break;
case DioExceptionType.cancel:
print('Request cancelled');
break;
default:
print('Other error: ${e.message}');
}
}

4.3 Interceptors Cho Toàn Bộ App

Với Dio, bạn có thể cấu hình interceptors một lần và áp dụng cho tất cả requests:

class ApiClient {
static final Dio _dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
headers: {'Content-Type': 'application/json'},
));

static void init() {
// Thêm token vào mọi request
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
final token = TokenManager.getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
return handler.next(options);
},
));

// Log requests
_dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
));

// Retry logic
_dio.interceptors.add(
RetryInterceptor(
dio: _dio,
options: const RetryOptions(retries: 3),
),
);
}

static Dio get dio => _dio;
}

4.4 Type Safety Tốt Hơn

Dio hỗ trợ generic types cho responses:

// Định nghĩa model
class User {
final int id;
final String name;
final String email;

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

factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
}

// Sử dụng với Dio
Future<User> fetchUser(int id) async {
final response = await dio.get<Map<String, dynamic>>('/users/$id');
return User.fromJson(response.data!);
}

4.5 Hỗ Trợ Nhiều Format Dữ Liệu

Dio tự động xử lý JSON, FormData, và các format khác:

// JSON (mặc định)
await dio.post('/api/data', data: {'key': 'value'});

// FormData
await dio.post('/api/upload', data: FormData.fromMap({
'file': multipartFile,
'name': 'filename',
}));

// URL encoded
await dio.post(
'/api/data',
data: {'key': 'value'},
options: Options(contentType: Headers.formUrlEncodedContentType),
);

5️⃣ Ví Dụ Thực Tế: Tạo API Client Với Dio

import 'package:dio/dio.dart';

class ApiService {
late Dio _dio;

ApiService() {
_dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
headers: {
'Content-Type': 'application/json',
},
));

_setupInterceptors();
}

void _setupInterceptors() {
// Log interceptor
_dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
error: true,
));

// Auth interceptor
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
final token = _getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
return handler.next(options);
},
onError: (error, handler) {
if (error.response?.statusCode == 401) {
// Refresh token hoặc đăng nhập lại
_handleUnauthorized();
}
return handler.next(error);
},
));
}

String? _getToken() {
// Lấy token từ storage
return 'your_token_here';
}

void _handleUnauthorized() {
// Xử lý khi unauthorized
}

// GET request
Future<Response> get(String path, {Map<String, dynamic>? queryParameters}) async {
try {
return await _dio.get(path, queryParameters: queryParameters);
} on DioException catch (e) {
throw _handleError(e);
}
}

// POST request
Future<Response> post(String path, {dynamic data}) async {
try {
return await _dio.post(path, data: data);
} on DioException catch (e) {
throw _handleError(e);
}
}

// PUT request
Future<Response> put(String path, {dynamic data}) async {
try {
return await _dio.put(path, data: data);
} on DioException catch (e) {
throw _handleError(e);
}
}

// DELETE request
Future<Response> delete(String path) async {
try {
return await _dio.delete(path);
} on DioException catch (e) {
throw _handleError(e);
}
}

Exception _handleError(DioException error) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return TimeoutException('Request timeout');
case DioExceptionType.badResponse:
return ServerException('Server error: ${error.response?.statusCode}');
case DioExceptionType.cancel:
return CancelException('Request cancelled');
default:
return NetworkException('Network error: ${error.message}');
}
}
}

// Custom exceptions
class TimeoutException implements Exception {
final String message;
TimeoutException(this.message);
}

class ServerException implements Exception {
final String message;
ServerException(this.message);
}

class CancelException implements Exception {
final String message;
CancelException(this.message);
}

class NetworkException implements Exception {
final String message;
NetworkException(this.message);
}

6️⃣ Kết Luận

Dio là lựa chọn tốt hơn so với HTTP package vì:

Tính năng phong phú: Interceptors, retry, transformers
Code sạch hơn: Dễ đọc, dễ bảo trì
Xử lý lỗi tốt hơn: DioException với nhiều thông tin
Linh hoạt: Hỗ trợ nhiều use cases
Cộng đồng lớn: Nhiều tài liệu và ví dụ
Được maintain tích cực: Cập nhật thường xuyên

💡 Lời khuyên: Nếu bạn đang bắt đầu dự án Flutter mới, hãy sử dụng Dio ngay từ đầu. Nếu đang dùng HTTP package, việc migrate sang Dio cũng không quá khó khăn và sẽ mang lại nhiều lợi ích.


🎓 Học Sâu Hơn Về Flutter

Muốn master Flutter, API Integration, và các best practices? Tham gia các khóa học tại Hướng Nghiệp Dữ Liệu:

📚 Khóa Học Liên Quan:


📝 Bài viết này được biên soạn bởi đội ngũ Hướng Nghiệp Dữ Liệu. Để cập nhật thêm về Flutter, Dio và các best practices trong phát triển ứng dụng di động, hãy theo dõi blog của chúng tôi.

Hướng Dẫn Call API Bằng Dio Có Kèm Interceptor và Retry Logic

· 9 min read

Trong bài viết này, chúng ta sẽ học cách sử dụng Dio để gọi API trong Flutter với các tính năng nâng cao như interceptors và retry logic. Đây là những kỹ thuật quan trọng để xây dựng ứng dụng Flutter chuyên nghiệp.


1️⃣ Cài Đặt và Cấu Hình Cơ Bản

1.1 Cài Đặt Dio

Thêm vào pubspec.yaml:

dependencies:
dio: ^5.4.0
dio_retry: ^2.0.0 # Cho retry logic

1.2 Tạo Dio Instance

import 'package:dio/dio.dart';

class ApiClient {
late Dio _dio;

ApiClient() {
_dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
sendTimeout: Duration(seconds: 3),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
));
}

Dio get dio => _dio;
}

2️⃣ Interceptors - Xử Lý Requests và Responses

2.1 Log Interceptor

Ghi log tất cả requests và responses:

import 'package:dio/dio.dart';

void setupLogInterceptor(Dio dio) {
dio.interceptors.add(LogInterceptor(
request: true,
requestHeader: true,
requestBody: true,
responseHeader: true,
responseBody: true,
error: true,
logPrint: (obj) {
// Sử dụng logger của bạn (ví dụ: logger package)
print(obj);
},
));
}

2.2 Authentication Interceptor

Tự động thêm token vào mọi request:

class AuthInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// Lấy token từ storage
final token = TokenStorage.getToken();

if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}

handler.next(options);
}
}

// Sử dụng
dio.interceptors.add(AuthInterceptor());

2.3 Refresh Token Interceptor

Tự động refresh token khi hết hạn:

class RefreshTokenInterceptor extends Interceptor {
final Dio refreshDio;

RefreshTokenInterceptor(this.refreshDio);

@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
// Nếu lỗi 401 (Unauthorized)
if (err.response?.statusCode == 401) {
try {
// Refresh token
final newToken = await _refreshToken();

if (newToken != null) {
// Lưu token mới
TokenStorage.saveToken(newToken);

// Retry request với token mới
final opts = err.requestOptions;
opts.headers['Authorization'] = 'Bearer $newToken';

final response = await refreshDio.request(
opts.path,
options: Options(
method: opts.method,
headers: opts.headers,
),
data: opts.data,
queryParameters: opts.queryParameters,
);

return handler.resolve(response);
}
} catch (e) {
// Nếu refresh token thất bại, đăng xuất
await AuthService.logout();
return handler.reject(err);
}
}

return handler.next(err);
}

Future<String?> _refreshToken() async {
try {
final refreshToken = TokenStorage.getRefreshToken();
final response = await refreshDio.post(
'/auth/refresh',
data: {'refresh_token': refreshToken},
);

return response.data['access_token'];
} catch (e) {
return null;
}
}
}

// Sử dụng
final refreshDio = Dio();
dio.interceptors.add(RefreshTokenInterceptor(refreshDio));

2.4 Error Handling Interceptor

Xử lý lỗi tập trung:

class ErrorInterceptor extends Interceptor {
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
String errorMessage = 'Đã xảy ra lỗi';

switch (err.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
errorMessage = 'Kết nối timeout. Vui lòng thử lại.';
break;
case DioExceptionType.badResponse:
final statusCode = err.response?.statusCode;
switch (statusCode) {
case 400:
errorMessage = 'Yêu cầu không hợp lệ';
break;
case 401:
errorMessage = 'Chưa đăng nhập hoặc token đã hết hạn';
break;
case 403:
errorMessage = 'Không có quyền truy cập';
break;
case 404:
errorMessage = 'Không tìm thấy tài nguyên';
break;
case 500:
errorMessage = 'Lỗi server. Vui lòng thử lại sau.';
break;
default:
errorMessage = 'Lỗi server: $statusCode';
}
break;
case DioExceptionType.cancel:
errorMessage = 'Request đã bị hủy';
break;
case DioExceptionType.unknown:
errorMessage = 'Không có kết nối internet';
break;
default:
errorMessage = err.message ?? 'Đã xảy ra lỗi';
}

// Hiển thị thông báo lỗi (sử dụng snackbar, dialog, etc.)
ErrorHandler.showError(errorMessage);

handler.next(err);
}
}

// Sử dụng
dio.interceptors.add(ErrorInterceptor());

2.5 Request/Response Transformer Interceptor

Transform dữ liệu trước khi gửi/nhận:

class DataTransformerInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// Transform request data
if (options.data is Map) {
// Thêm timestamp, device info, etc.
options.data = {
...options.data as Map,
'timestamp': DateTime.now().toIso8601String(),
'device_id': DeviceInfo.getDeviceId(),
};
}

handler.next(options);
}

@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
// Transform response data
if (response.data is Map) {
final data = response.data as Map;

// Unwrap nested data nếu cần
if (data.containsKey('data')) {
response.data = data['data'];
}
}

handler.next(response);
}
}

// Sử dụng
dio.interceptors.add(DataTransformerInterceptor());

3️⃣ Retry Logic - Tự Động Thử Lại Khi Thất Bại

3.1 Sử dụng dio_retry Package

import 'package:dio/dio.dart';
import 'package:dio_retry/dio_retry.dart';

void setupRetryInterceptor(Dio dio) {
dio.interceptors.add(
RetryInterceptor(
dio: dio,
options: RetryOptions(
retries: 3, // Số lần retry
retryInterval: Duration(seconds: 2), // Khoảng thời gian giữa các lần retry
exponentialBackoff: true, // Tăng dần thời gian chờ
retryableExtraStatuses: [401, 403], // Retry với các status code này
),
),
);
}

3.2 Custom Retry Logic

Tự xây dựng retry logic:

class RetryInterceptor extends Interceptor {
final int maxRetries;
final Duration retryDelay;

RetryInterceptor({
this.maxRetries = 3,
this.retryDelay = const Duration(seconds: 2),
});

@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
if (_shouldRetry(err)) {
final retryCount = err.requestOptions.extra['retryCount'] ?? 0;

if (retryCount < maxRetries) {
// Tăng retry count
err.requestOptions.extra['retryCount'] = retryCount + 1;

// Đợi trước khi retry
await Future.delayed(retryDelay * (retryCount + 1));

try {
// Retry request
final response = await dio.request(
err.requestOptions.path,
options: Options(
method: err.requestOptions.method,
headers: err.requestOptions.headers,
),
data: err.requestOptions.data,
queryParameters: err.requestOptions.queryParameters,
extra: err.requestOptions.extra,
);

return handler.resolve(response);
} catch (e) {
// Nếu vẫn lỗi, tiếp tục retry hoặc reject
if (retryCount + 1 < maxRetries) {
return onError(err, handler);
}
}
}
}

handler.next(err);
}

bool _shouldRetry(DioException err) {
// Chỉ retry với một số loại lỗi nhất định
return err.type == DioExceptionType.connectionTimeout ||
err.type == DioExceptionType.receiveTimeout ||
err.type == DioExceptionType.sendTimeout ||
(err.response?.statusCode != null &&
err.response!.statusCode! >= 500);
}
}

// Sử dụng
dio.interceptors.add(RetryInterceptor(maxRetries: 3));

4️⃣ Ví Dụ Hoàn Chỉnh: API Service Với Interceptors và Retry

import 'package:dio/dio.dart';
import 'package:dio_retry/dio_retry.dart';

class ApiService {
late Dio _dio;
static final ApiService _instance = ApiService._internal();

factory ApiService() => _instance;

ApiService._internal() {
_dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
sendTimeout: Duration(seconds: 3),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
));

_setupInterceptors();
}

void _setupInterceptors() {
// 1. Log Interceptor
_dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
error: true,
));

// 2. Auth Interceptor
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
final token = _getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
return handler.next(options);
},
));

// 3. Retry Interceptor
_dio.interceptors.add(
RetryInterceptor(
dio: _dio,
options: RetryOptions(
retries: 3,
retryInterval: Duration(seconds: 2),
exponentialBackoff: true,
),
),
);

// 4. Error Interceptor
_dio.interceptors.add(InterceptorsWrapper(
onError: (error, handler) {
_handleError(error);
return handler.next(error);
},
));
}

String? _getToken() {
// Lấy token từ secure storage
// return SecureStorage.getToken();
return null;
}

void _handleError(DioException error) {
// Xử lý lỗi tập trung
// Có thể hiển thị snackbar, log, etc.
print('API Error: ${error.message}');
}

// GET request
Future<Response<T>> get<T>(
String path, {
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
}) async {
try {
return await _dio.get<T>(
path,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
);
} on DioException catch (e) {
throw _mapException(e);
}
}

// POST request
Future<Response<T>> post<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
}) async {
try {
return await _dio.post<T>(
path,
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
);
} on DioException catch (e) {
throw _mapException(e);
}
}

// PUT request
Future<Response<T>> put<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
}) async {
try {
return await _dio.put<T>(
path,
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
);
} on DioException catch (e) {
throw _mapException(e);
}
}

// DELETE request
Future<Response<T>> delete<T>(
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
}) async {
try {
return await _dio.delete<T>(
path,
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
);
} on DioException catch (e) {
throw _mapException(e);
}
}

Exception _mapException(DioException error) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return TimeoutException('Request timeout');
case DioExceptionType.badResponse:
return ServerException(
'Server error: ${error.response?.statusCode}',
error.response?.statusCode,
);
case DioExceptionType.cancel:
return CancelException('Request cancelled');
default:
return NetworkException('Network error: ${error.message}');
}
}
}

// Custom exceptions
class TimeoutException implements Exception {
final String message;
TimeoutException(this.message);
}

class ServerException implements Exception {
final String message;
final int? statusCode;
ServerException(this.message, this.statusCode);
}

class CancelException implements Exception {
final String message;
CancelException(this.message);
}

class NetworkException implements Exception {
final String message;
NetworkException(this.message);
}

5️⃣ Sử Dụng API Service

// Trong repository hoặc service
class UserRepository {
final ApiService _apiService = ApiService();

Future<User> getUser(int id) async {
try {
final response = await _apiService.get<Map<String, dynamic>>('/users/$id');
return User.fromJson(response.data!);
} catch (e) {
throw Exception('Failed to get user: $e');
}
}

Future<List<User>> getUsers() async {
try {
final response = await _apiService.get<List<dynamic>>('/users');
return (response.data as List)
.map((json) => User.fromJson(json))
.toList();
} catch (e) {
throw Exception('Failed to get users: $e');
}
}

Future<User> createUser(User user) async {
try {
final response = await _apiService.post<Map<String, dynamic>>(
'/users',
data: user.toJson(),
);
return User.fromJson(response.data!);
} catch (e) {
throw Exception('Failed to create user: $e');
}
}
}

6️⃣ Best Practices

6.1 Thứ Tự Interceptors

Thứ tự interceptors quan trọng:

// 1. Log interceptor (đầu tiên để log tất cả)
dio.interceptors.add(LogInterceptor());

// 2. Auth interceptor (thêm token)
dio.interceptors.add(AuthInterceptor());

// 3. Retry interceptor (retry khi lỗi)
dio.interceptors.add(RetryInterceptor());

// 4. Error interceptor (xử lý lỗi cuối cùng)
dio.interceptors.add(ErrorInterceptor());

6.2 Sử Dụng Singleton Pattern

Tạo một instance Dio duy nhất cho toàn bộ app:

class ApiService {
static final ApiService _instance = ApiService._internal();
factory ApiService() => _instance;
ApiService._internal();
}

6.3 Cancel Token Cho Long-Running Requests

final cancelToken = CancelToken();

// Gọi API
_apiService.get('/data', cancelToken: cancelToken);

// Hủy khi không cần thiết (ví dụ: khi widget dispose)
@override
void dispose() {
cancelToken.cancel('Widget disposed');
super.dispose();
}

7️⃣ Kết Luận

Với Dio, interceptors và retry logic, bạn có thể:

Tự động thêm authentication vào mọi request
Xử lý lỗi tập trung và nhất quán
Tự động retry khi request thất bại
Log requests/responses để debug
Transform data trước khi gửi/nhận

💡 Lời khuyên: Sử dụng interceptors để tách biệt concerns. Mỗi interceptor nên có một nhiệm vụ cụ thể. Điều này giúp code dễ đọc, dễ test và dễ bảo trì.


🎓 Học Sâu Hơn Về Flutter

Muốn master Flutter, API Integration, và các best practices? Tham gia các khóa học tại Hướng Nghiệp Dữ Liệu:

📚 Khóa Học Liên Quan:


📝 Bài viết này được biên soạn bởi đội ngũ Hướng Nghiệp Dữ Liệu. Để cập nhật thêm về Flutter, Dio và các best practices trong phát triển ứng dụng di động, hãy theo dõi blog của chúng tôi.

Hướng Dẫn Cloud Firestore CRUD Đầy Đủ

· 10 min read

Cloud Firestore là một NoSQL database mạnh mẽ của Firebase. Bài viết này sẽ hướng dẫn bạn cách thực hiện CRUD operations (Create, Read, Update, Delete) với Firestore trong Flutter một cách đầy đủ.


1️⃣ Cài Đặt và Setup

1.1 Thêm Dependencies

Thêm vào pubspec.yaml:

dependencies:
firebase_core: ^2.24.2
cloud_firestore: ^4.13.6

Sau đó chạy:

flutter pub get

1.2 Khởi Tạo Firestore

import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'firebase_options.dart';

void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(MyApp());
}

1.3 Cấu Hình Firestore Rules

Trong Firebase Console > Firestore Database > Rules:

rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Cho phép đọc/ghi nếu đã đăng nhập
match /{document=**} {
allow read, write: if request.auth != null;
}

// Hoặc public read, authenticated write
match /posts/{postId} {
allow read: if true;
allow write: if request.auth != null;
}
}
}

2️⃣ Cấu Trúc Dữ Liệu Firestore

2.1 Collections và Documents

Firestore có cấu trúc:

  • Collection: Tương tự như table trong SQL
  • Document: Tương tự như row trong SQL
  • Field: Tương tự như column trong SQL
users (collection)
├── user1 (document)
│ ├── name: "Nguyễn Văn A"
│ ├── email: "a@example.com"
│ └── age: 25
└── user2 (document)
├── name: "Trần Thị B"
├── email: "b@example.com"
└── age: 30

2.2 Model Class

class User {
final String? id;
final String name;
final String email;
final int age;
final DateTime createdAt;

User({
this.id,
required this.name,
required this.email,
required this.age,
DateTime? createdAt,
}) : createdAt = createdAt ?? DateTime.now();

// Convert to Map for Firestore
Map<String, dynamic> toMap() {
return {
'name': name,
'email': email,
'age': age,
'createdAt': Timestamp.fromDate(createdAt),
};
}

// Create from Firestore document
factory User.fromMap(String id, Map<String, dynamic> map) {
return User(
id: id,
name: map['name'] as String,
email: map['email'] as String,
age: map['age'] as int,
createdAt: (map['createdAt'] as Timestamp).toDate(),
);
}

// Create from Firestore document snapshot
factory User.fromDocument(DocumentSnapshot doc) {
return User.fromMap(doc.id, doc.data() as Map<String, dynamic>);
}
}

3️⃣ CREATE - Tạo Dữ Liệu

3.1 Thêm Document với Auto ID

import 'package:cloud_firestore/cloud_firestore.dart';

class FirestoreService {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;

// Thêm document với auto-generated ID
Future<String> addUser(User user) async {
try {
final docRef = await _firestore
.collection('users')
.add(user.toMap());

return docRef.id;
} catch (e) {
throw Exception('Error adding user: $e');
}
}
}

3.2 Thêm Document với Custom ID

// Thêm document với ID tùy chỉnh
Future<void> addUserWithId(String userId, User user) async {
try {
await _firestore
.collection('users')
.doc(userId)
.set(user.toMap());
} catch (e) {
throw Exception('Error adding user: $e');
}
}

3.3 Thêm Document với Merge

// Merge: Nếu document đã tồn tại, chỉ update các field được chỉ định
Future<void> addUserWithMerge(String userId, Map<String, dynamic> data) async {
try {
await _firestore
.collection('users')
.doc(userId)
.set(data, SetOptions(merge: true));
} catch (e) {
throw Exception('Error adding user: $e');
}
}

3.4 Thêm Nested Document

// Thêm document vào subcollection
Future<void> addPostToUser(String userId, Post post) async {
try {
await _firestore
.collection('users')
.doc(userId)
.collection('posts')
.add(post.toMap());
} catch (e) {
throw Exception('Error adding post: $e');
}
}

4️⃣ READ - Đọc Dữ Liệu

4.1 Đọc Single Document

// Đọc một document
Future<User?> getUser(String userId) async {
try {
final doc = await _firestore
.collection('users')
.doc(userId)
.get();

if (doc.exists) {
return User.fromDocument(doc);
}
return null;
} catch (e) {
throw Exception('Error getting user: $e');
}
}

4.2 Đọc Tất Cả Documents

// Đọc tất cả documents trong collection
Future<List<User>> getAllUsers() async {
try {
final snapshot = await _firestore
.collection('users')
.get();

return snapshot.docs
.map((doc) => User.fromDocument(doc))
.toList();
} catch (e) {
throw Exception('Error getting users: $e');
}
}

4.3 Query với Điều Kiện

// Query với where
Future<List<User>> getUsersByAge(int minAge) async {
try {
final snapshot = await _firestore
.collection('users')
.where('age', isGreaterThanOrEqualTo: minAge)
.get();

return snapshot.docs
.map((doc) => User.fromDocument(doc))
.toList();
} catch (e) {
throw Exception('Error getting users: $e');
}
}

// Multiple conditions
Future<List<User>> getUsersByAgeAndName(int age, String name) async {
try {
final snapshot = await _firestore
.collection('users')
.where('age', isEqualTo: age)
.where('name', isEqualTo: name)
.get();

return snapshot.docs
.map((doc) => User.fromDocument(doc))
.toList();
} catch (e) {
throw Exception('Error getting users: $e');
}
}

4.4 Sorting và Limiting

// Sort và limit
Future<List<User>> getUsersSortedByAge({int limit = 10}) async {
try {
final snapshot = await _firestore
.collection('users')
.orderBy('age', descending: false)
.limit(limit)
.get();

return snapshot.docs
.map((doc) => User.fromDocument(doc))
.toList();
} catch (e) {
throw Exception('Error getting users: $e');
}
}

// Pagination
Future<List<User>> getUsersPaginated({
required int limit,
DocumentSnapshot? startAfter,
}) async {
try {
Query query = _firestore
.collection('users')
.orderBy('createdAt', descending: true)
.limit(limit);

if (startAfter != null) {
query = query.startAfterDocument(startAfter);
}

final snapshot = await query.get();

return snapshot.docs
.map((doc) => User.fromDocument(doc))
.toList();
} catch (e) {
throw Exception('Error getting users: $e');
}
}

4.5 Real-time Updates

// Stream để lắng nghe thay đổi real-time
Stream<List<User>> getUsersStream() {
return _firestore
.collection('users')
.snapshots()
.map((snapshot) => snapshot.docs
.map((doc) => User.fromDocument(doc))
.toList());
}

// Stream cho single document
Stream<User?> getUserStream(String userId) {
return _firestore
.collection('users')
.doc(userId)
.snapshots()
.map((doc) => doc.exists ? User.fromDocument(doc) : null);
}

5️⃣ UPDATE - Cập Nhật Dữ Liệu

5.1 Update Toàn Bộ Document

// Update toàn bộ document
Future<void> updateUser(String userId, User user) async {
try {
await _firestore
.collection('users')
.doc(userId)
.set(user.toMap());
} catch (e) {
throw Exception('Error updating user: $e');
}
}

5.2 Update Một Số Fields

// Update chỉ một số fields
Future<void> updateUserFields(
String userId,
Map<String, dynamic> fields,
) async {
try {
await _firestore
.collection('users')
.doc(userId)
.update(fields);
} catch (e) {
throw Exception('Error updating user: $e');
}
}

// Ví dụ sử dụng
await updateUserFields('user123', {
'name': 'Tên mới',
'age': 26,
});

5.3 Increment/Decrement

// Tăng/giảm giá trị số
Future<void> incrementUserAge(String userId) async {
try {
await _firestore
.collection('users')
.doc(userId)
.update({
'age': FieldValue.increment(1),
});
} catch (e) {
throw Exception('Error incrementing age: $e');
}
}

5.4 Array Operations

// Thêm vào array
Future<void> addToArray(String userId, String item) async {
try {
await _firestore
.collection('users')
.doc(userId)
.update({
'tags': FieldValue.arrayUnion([item]),
});
} catch (e) {
throw Exception('Error adding to array: $e');
}
}

// Xóa khỏi array
Future<void> removeFromArray(String userId, String item) async {
try {
await _firestore
.collection('users')
.doc(userId)
.update({
'tags': FieldValue.arrayRemove([item]),
});
} catch (e) {
throw Exception('Error removing from array: $e');
}
}

6️⃣ DELETE - Xóa Dữ Liệu

6.1 Xóa Document

// Xóa document
Future<void> deleteUser(String userId) async {
try {
await _firestore
.collection('users')
.doc(userId)
.delete();
} catch (e) {
throw Exception('Error deleting user: $e');
}
}

6.2 Xóa Field

// Xóa một field
Future<void> deleteUserField(String userId, String fieldName) async {
try {
await _firestore
.collection('users')
.doc(userId)
.update({
fieldName: FieldValue.delete(),
});
} catch (e) {
throw Exception('Error deleting field: $e');
}
}

6.3 Xóa Subcollection

// Xóa tất cả documents trong subcollection
Future<void> deleteUserPosts(String userId) async {
try {
final snapshot = await _firestore
.collection('users')
.doc(userId)
.collection('posts')
.get();

final batch = _firestore.batch();
for (var doc in snapshot.docs) {
batch.delete(doc.reference);
}
await batch.commit();
} catch (e) {
throw Exception('Error deleting posts: $e');
}
}

7️⃣ Transactions và Batch Writes

7.1 Transactions

// Transaction: Đảm bảo atomic operations
Future<void> transferPoints(
String fromUserId,
String toUserId,
int points,
) async {
try {
await _firestore.runTransaction((transaction) async {
// Đọc documents
final fromDoc = await transaction.get(
_firestore.collection('users').doc(fromUserId),
);
final toDoc = await transaction.get(
_firestore.collection('users').doc(toUserId),
);

if (!fromDoc.exists || !toDoc.exists) {
throw Exception('User not found');
}

final fromPoints = fromDoc.data()!['points'] as int;
if (fromPoints < points) {
throw Exception('Insufficient points');
}

// Update
transaction.update(fromDoc.reference, {
'points': FieldValue.increment(-points),
});
transaction.update(toDoc.reference, {
'points': FieldValue.increment(points),
});
});
} catch (e) {
throw Exception('Error transferring points: $e');
}
}

7.2 Batch Writes

// Batch write: Thực hiện nhiều operations cùng lúc
Future<void> batchUpdateUsers(List<String> userIds, Map<String, dynamic> data) async {
try {
final batch = _firestore.batch();

for (var userId in userIds) {
final docRef = _firestore.collection('users').doc(userId);
batch.update(docRef, data);
}

await batch.commit();
} catch (e) {
throw Exception('Error batch updating users: $e');
}
}

8️⃣ Ví Dụ Hoàn Chỉnh: Firestore Service

import 'package:cloud_firestore/cloud_firestore.dart';

class FirestoreService {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;

// CREATE
Future<String> createUser(User user) async {
try {
final docRef = await _firestore
.collection('users')
.add(user.toMap());
return docRef.id;
} catch (e) {
throw Exception('Error creating user: $e');
}
}

// READ - Single
Future<User?> getUser(String userId) async {
try {
final doc = await _firestore
.collection('users')
.doc(userId)
.get();
return doc.exists ? User.fromDocument(doc) : null;
} catch (e) {
throw Exception('Error getting user: $e');
}
}

// READ - All
Future<List<User>> getAllUsers() async {
try {
final snapshot = await _firestore
.collection('users')
.get();
return snapshot.docs
.map((doc) => User.fromDocument(doc))
.toList();
} catch (e) {
throw Exception('Error getting users: $e');
}
}

// READ - Query
Future<List<User>> getUsersByAge(int minAge) async {
try {
final snapshot = await _firestore
.collection('users')
.where('age', isGreaterThanOrEqualTo: minAge)
.orderBy('age')
.get();
return snapshot.docs
.map((doc) => User.fromDocument(doc))
.toList();
} catch (e) {
throw Exception('Error getting users: $e');
}
}

// READ - Stream
Stream<List<User>> getUsersStream() {
return _firestore
.collection('users')
.snapshots()
.map((snapshot) => snapshot.docs
.map((doc) => User.fromDocument(doc))
.toList());
}

// UPDATE
Future<void> updateUser(String userId, Map<String, dynamic> data) async {
try {
await _firestore
.collection('users')
.doc(userId)
.update(data);
} catch (e) {
throw Exception('Error updating user: $e');
}
}

// DELETE
Future<void> deleteUser(String userId) async {
try {
await _firestore
.collection('users')
.doc(userId)
.delete();
} catch (e) {
throw Exception('Error deleting user: $e');
}
}
}

9️⃣ Sử Dụng Với UI

9.1 StreamBuilder

class UsersListWidget extends StatelessWidget {
final FirestoreService _service = FirestoreService();

@override
Widget build(BuildContext context) {
return StreamBuilder<List<User>>(
stream: _service.getUsersStream(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}

if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}

if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('No users found'));
}

final users = snapshot.data!;
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
trailing: Text('Age: ${user.age}'),
);
},
);
},
);
}
}

🔟 Best Practices

10.1 Indexes

  • ✅ Tạo composite indexes cho queries phức tạp
  • ✅ Firestore sẽ tự động suggest indexes

10.2 Security Rules

  • ✅ Luôn validate data trong security rules
  • ✅ Chỉ cho phép authenticated users
  • ✅ Validate data structure

10.3 Performance

  • ✅ Sử dụng pagination cho large collections
  • ✅ Limit số lượng documents trong queries
  • ✅ Sử dụng indexes cho queries

10.4 Error Handling

  • ✅ Luôn handle errors
  • ✅ Hiển thị thông báo cho người dùng
  • ✅ Log errors để debug

1️⃣1️⃣ Kết Luận

Cloud Firestore giúp bạn:

NoSQL database: Linh hoạt, dễ scale
Real-time updates: Stream data tự động
Offline support: Hoạt động offline
Security: Security rules mạnh mẽ
Scalable: Tự động scale

💡 Lời khuyên: Sử dụng StreamBuilder cho real-time data. Luôn validate data và sử dụng security rules. Tạo indexes cho queries phức tạp.


🎓 Học Sâu Hơn Về Flutter

Muốn master Flutter, Firestore, và các best practices? Tham gia các khóa học tại Hướng Nghiệp Dữ Liệu:

📚 Khóa Học Liên Quan:


📝 Bài viết này được biên soạn bởi đội ngũ Hướng Nghiệp Dữ Liệu. Để cập nhật thêm về Flutter, Firestore và các best practices trong phát triển ứng dụng di động, hãy theo dõi blog của chúng tôi.

Hướng Dẫn Firebase Authentication Cho Người Mới

· 11 min read

Firebase Authentication là một dịch vụ xác thực người dùng mạnh mẽ và dễ sử dụng. Bài viết này sẽ hướng dẫn bạn cách tích hợp Firebase Authentication vào Flutter từ đầu đến cuối.


1️⃣ Cài Đặt và Setup

1.1 Tạo Firebase Project

  1. Truy cập Firebase Console
  2. Click "Add project"
  3. Điền tên project và làm theo hướng dẫn
  4. Bật Authentication trong Firebase Console

1.2 Cài Đặt FlutterFire CLI

dart pub global activate flutterfire_cli

1.3 Cấu Hình Firebase cho Flutter

flutterfire configure

Lệnh này sẽ:

  • Tự động tạo file firebase_options.dart
  • Cấu hình cho cả Android và iOS

1.4 Thêm Dependencies

Thêm vào pubspec.yaml:

dependencies:
firebase_core: ^2.24.2
firebase_auth: ^4.15.3
google_sign_in: ^6.1.6 # Cho Google Sign In

Sau đó chạy:

flutter pub get

1.5 Khởi Tạo Firebase

import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';

void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(MyApp());
}

2️⃣ Email/Password Authentication

2.1 Đăng Ký (Sign Up)

import 'package:firebase_auth/firebase_auth.dart';

class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;

// Đăng ký với email và password
Future<UserCredential?> signUpWithEmail({
required String email,
required String password,
}) async {
try {
final userCredential = await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);

// Gửi email xác nhận (optional)
await userCredential.user?.sendEmailVerification();

return userCredential;
} on FirebaseAuthException catch (e) {
print('Sign up error: ${e.message}');
throw _handleAuthException(e);
} catch (e) {
print('Unexpected error: $e');
throw Exception('Đã xảy ra lỗi không xác định');
}
}

String _handleAuthException(FirebaseAuthException e) {
switch (e.code) {
case 'weak-password':
return 'Mật khẩu quá yếu';
case 'email-already-in-use':
return 'Email đã được sử dụng';
case 'invalid-email':
return 'Email không hợp lệ';
default:
return e.message ?? 'Đã xảy ra lỗi';
}
}
}

2.2 Đăng Nhập (Sign In)

// Đăng nhập với email và password
Future<UserCredential?> signInWithEmail({
required String email,
required String password,
}) async {
try {
final userCredential = await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);

return userCredential;
} on FirebaseAuthException catch (e) {
print('Sign in error: ${e.message}');
throw _handleSignInException(e);
} catch (e) {
print('Unexpected error: $e');
throw Exception('Đã xảy ra lỗi không xác định');
}
}

String _handleSignInException(FirebaseAuthException e) {
switch (e.code) {
case 'user-not-found':
return 'Không tìm thấy tài khoản với email này';
case 'wrong-password':
return 'Mật khẩu không đúng';
case 'invalid-email':
return 'Email không hợp lệ';
case 'user-disabled':
return 'Tài khoản đã bị vô hiệu hóa';
default:
return e.message ?? 'Đã xảy ra lỗi';
}
}

2.3 Đăng Xuất (Sign Out)

Future<void> signOut() async {
try {
await _auth.signOut();
} catch (e) {
print('Sign out error: $e');
throw Exception('Không thể đăng xuất');
}
}

2.4 Reset Password

Future<void> resetPassword(String email) async {
try {
await _auth.sendPasswordResetEmail(email: email);
} on FirebaseAuthException catch (e) {
throw _handleAuthException(e);
} catch (e) {
throw Exception('Không thể gửi email reset password');
}
}

3️⃣ Google Sign In

3.1 Cấu Hình Google Sign In

Android

Thêm vào android/app/build.gradle:

dependencies {
implementation 'com.google.android.gms:play-services-auth:20.7.0'
}

Lấy SHA-1 key:

keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

Thêm SHA-1 vào Firebase Console > Project Settings > Your apps

iOS

Thêm vào ios/Runner/Info.plist:

<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>YOUR_REVERSED_CLIENT_ID</string>
</array>
</dict>
</array>

3.2 Implement Google Sign In

import 'package:google_sign_in/google_sign_in.dart';

class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = GoogleSignIn();

// Đăng nhập với Google
Future<UserCredential?> signInWithGoogle() async {
try {
// Trigger the authentication flow
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();

if (googleUser == null) {
// User canceled the sign-in
return null;
}

// Obtain the auth details from the request
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;

// Create a new credential
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);

// Sign in to Firebase with the Google credential
return await _auth.signInWithCredential(credential);
} on FirebaseAuthException catch (e) {
print('Google sign in error: ${e.message}');
throw Exception('Đăng nhập Google thất bại: ${e.message}');
} catch (e) {
print('Unexpected error: $e');
throw Exception('Đã xảy ra lỗi không xác định');
}
}

// Đăng xuất Google
Future<void> signOutGoogle() async {
await _googleSignIn.signOut();
await _auth.signOut();
}
}

4️⃣ Phone Authentication

4.1 Cấu Hình Phone Auth

  1. Bật Phone Authentication trong Firebase Console
  2. Thêm test phone numbers (cho development)

4.2 Implement Phone Auth

class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
String? _verificationId;

// Gửi OTP
Future<void> sendOTP(String phoneNumber) async {
try {
await _auth.verifyPhoneNumber(
phoneNumber: phoneNumber,
verificationCompleted: (PhoneAuthCredential credential) async {
// Auto verification (Android only)
await _auth.signInWithCredential(credential);
},
verificationFailed: (FirebaseAuthException e) {
throw Exception('Xác thực thất bại: ${e.message}');
},
codeSent: (String verificationId, int? resendToken) {
_verificationId = verificationId;
},
codeAutoRetrievalTimeout: (String verificationId) {
_verificationId = verificationId;
},
timeout: Duration(seconds: 60),
);
} catch (e) {
throw Exception('Không thể gửi OTP: $e');
}
}

// Xác thực OTP
Future<UserCredential?> verifyOTP(String smsCode) async {
try {
if (_verificationId == null) {
throw Exception('Verification ID không tồn tại');
}

final credential = PhoneAuthProvider.credential(
verificationId: _verificationId!,
smsCode: smsCode,
);

return await _auth.signInWithCredential(credential);
} on FirebaseAuthException catch (e) {
throw Exception('Mã OTP không đúng: ${e.message}');
} catch (e) {
throw Exception('Xác thực thất bại: $e');
}
}
}

5️⃣ Quản Lý User Session

5.1 Auth State Stream

class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;

// Stream để theo dõi trạng thái đăng nhập
Stream<User?> get authStateChanges => _auth.authStateChanges();

// User hiện tại
User? get currentUser => _auth.currentUser;

// Kiểm tra đăng nhập
bool get isSignedIn => _auth.currentUser != null;

// Lấy thông tin user
User? get user => _auth.currentUser;
}

5.2 Auth Wrapper Widget

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';

class AuthWrapper extends StatelessWidget {
final Widget signedInWidget;
final Widget signedOutWidget;

const AuthWrapper({
Key? key,
required this.signedInWidget,
required this.signedOutWidget,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}

if (snapshot.hasData) {
return signedInWidget;
} else {
return signedOutWidget;
}
},
);
}
}

// Sử dụng
void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: AuthWrapper(
signedInWidget: HomeScreen(),
signedOutWidget: LoginScreen(),
),
);
}
}

6️⃣ User Profile Management

6.1 Cập Nhật Thông Tin User

class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;

// Cập nhật display name
Future<void> updateDisplayName(String name) async {
try {
await _auth.currentUser?.updateDisplayName(name);
await _auth.currentUser?.reload();
} catch (e) {
throw Exception('Không thể cập nhật tên: $e');
}
}

// Cập nhật email
Future<void> updateEmail(String newEmail) async {
try {
await _auth.currentUser?.verifyBeforeUpdateEmail(newEmail);
} catch (e) {
throw Exception('Không thể cập nhật email: $e');
}
}

// Cập nhật password
Future<void> updatePassword(String newPassword) async {
try {
await _auth.currentUser?.updatePassword(newPassword);
} catch (e) {
throw Exception('Không thể cập nhật mật khẩu: $e');
}
}

// Cập nhật photo URL
Future<void> updatePhotoURL(String photoURL) async {
try {
await _auth.currentUser?.updatePhotoURL(photoURL);
await _auth.currentUser?.reload();
} catch (e) {
throw Exception('Không thể cập nhật ảnh đại diện: $e');
}
}

// Xóa tài khoản
Future<void> deleteAccount() async {
try {
await _auth.currentUser?.delete();
} catch (e) {
throw Exception('Không thể xóa tài khoản: $e');
}
}
}

6.2 Email Verification

// Gửi email xác nhận
Future<void> sendEmailVerification() async {
try {
await _auth.currentUser?.sendEmailVerification();
} catch (e) {
throw Exception('Không thể gửi email xác nhận: $e');
}
}

// Kiểm tra email đã xác nhận chưa
bool get isEmailVerified => _auth.currentUser?.emailVerified ?? false;

// Reload user để cập nhật trạng thái
Future<void> reloadUser() async {
await _auth.currentUser?.reload();
}

7️⃣ Ví Dụ Hoàn Chỉnh: Auth Service

import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';

class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = GoogleSignIn();
String? _verificationId;

// Stream
Stream<User?> get authStateChanges => _auth.authStateChanges();
User? get currentUser => _auth.currentUser;
bool get isSignedIn => _auth.currentUser != null;

// Email/Password
Future<UserCredential?> signUpWithEmail({
required String email,
required String password,
}) async {
try {
final userCredential = await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
await userCredential.user?.sendEmailVerification();
return userCredential;
} on FirebaseAuthException catch (e) {
throw _handleAuthException(e);
}
}

Future<UserCredential?> signInWithEmail({
required String email,
required String password,
}) async {
try {
return await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
throw _handleAuthException(e);
}
}

// Google Sign In
Future<UserCredential?> signInWithGoogle() async {
try {
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
if (googleUser == null) return null;

final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;

final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);

return await _auth.signInWithCredential(credential);
} catch (e) {
throw Exception('Google sign in failed: $e');
}
}

// Phone Auth
Future<void> sendOTP(String phoneNumber) async {
await _auth.verifyPhoneNumber(
phoneNumber: phoneNumber,
verificationCompleted: (credential) async {
await _auth.signInWithCredential(credential);
},
verificationFailed: (e) {
throw Exception('Verification failed: ${e.message}');
},
codeSent: (verificationId, resendToken) {
_verificationId = verificationId;
},
codeAutoRetrievalTimeout: (verificationId) {
_verificationId = verificationId;
},
timeout: Duration(seconds: 60),
);
}

Future<UserCredential?> verifyOTP(String smsCode) async {
if (_verificationId == null) {
throw Exception('Verification ID not found');
}

final credential = PhoneAuthProvider.credential(
verificationId: _verificationId!,
smsCode: smsCode,
);

return await _auth.signInWithCredential(credential);
}

// Other methods
Future<void> signOut() async {
await _googleSignIn.signOut();
await _auth.signOut();
}

Future<void> resetPassword(String email) async {
await _auth.sendPasswordResetEmail(email: email);
}

Future<void> sendEmailVerification() async {
await _auth.currentUser?.sendEmailVerification();
}

bool get isEmailVerified => _auth.currentUser?.emailVerified ?? false;

String _handleAuthException(FirebaseAuthException e) {
switch (e.code) {
case 'weak-password':
return 'Mật khẩu quá yếu';
case 'email-already-in-use':
return 'Email đã được sử dụng';
case 'user-not-found':
return 'Không tìm thấy tài khoản';
case 'wrong-password':
return 'Mật khẩu không đúng';
case 'invalid-email':
return 'Email không hợp lệ';
default:
return e.message ?? 'Đã xảy ra lỗi';
}
}
}

8️⃣ UI Example: Login Screen

class LoginScreen extends StatefulWidget {
@override
_LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _authService = AuthService();
bool _isLoading = false;

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

try {
await _authService.signInWithEmail(
email: _emailController.text.trim(),
password: _passwordController.text,
);
// Navigation sẽ được xử lý bởi AuthWrapper
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.toString())),
);
} finally {
setState(() => _isLoading = false);
}
}

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

try {
await _authService.signInWithGoogle();
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.toString())),
);
} finally {
setState(() => _isLoading = false);
}
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
keyboardType: TextInputType.emailAddress,
),
SizedBox(height: 16),
TextField(
controller: _passwordController,
decoration: InputDecoration(labelText: 'Mật khẩu'),
obscureText: true,
),
SizedBox(height: 24),
ElevatedButton(
onPressed: _isLoading ? null : _signIn,
child: _isLoading
? CircularProgressIndicator()
: Text('Đăng nhập'),
),
SizedBox(height: 16),
ElevatedButton.icon(
onPressed: _isLoading ? null : _signInWithGoogle,
icon: Icon(Icons.g_mobiledata),
label: Text('Đăng nhập với Google'),
),
],
),
),
);
}
}

9️⃣ Best Practices

9.1 Security

  • ✅ Luôn validate input
  • ✅ Sử dụng strong password requirements
  • ✅ Enable email verification
  • ✅ Implement rate limiting
  • ✅ Store sensitive data securely

9.2 Error Handling

  • ✅ Hiển thị thông báo lỗi thân thiện
  • ✅ Log errors để debug
  • ✅ Handle network errors

9.3 User Experience

  • ✅ Hiển thị loading state
  • ✅ Auto-fill email khi có thể
  • ✅ Remember me functionality
  • ✅ Biometric authentication (optional)

🔟 Kết Luận

Firebase Authentication giúp bạn:

Dễ tích hợp: Setup nhanh chóng
Nhiều phương thức: Email, Google, Phone, etc.
Bảo mật cao: Được Google quản lý
Miễn phí: Free tier rộng rãi
Scalable: Tự động scale

💡 Lời khuyên: Luôn enable email verification và implement proper error handling. Sử dụng AuthWrapper để quản lý authentication state trong toàn bộ app.


🎓 Học Sâu Hơn Về Flutter

Muốn master Flutter, Firebase, và các best practices? Tham gia các khóa học tại Hướng Nghiệp Dữ Liệu:

📚 Khóa Học Liên Quan:


📝 Bài viết này được biên soạn bởi đội ngũ Hướng Nghiệp Dữ Liệu. Để cập nhật thêm về Flutter, Firebase Authentication và các best practices trong phát triển ứng dụng di động, hãy theo dõi blog của chúng tôi.