Tạo một ứng dụng Android bằng Firebase và Jetpack Compose

1. Giới thiệu

Lần cập nhật gần đây nhất: ngày 16 tháng 11 năm 2022

Tạo ứng dụng Android bằng Firebase và Jetpack Compose

Trong lớp học lập trình này, bạn sẽ xây dựng một ứng dụng Android có tên Make It So. Giao diện người dùng của ứng dụng này được xây dựng hoàn toàn bằng Jetpack Compose – bộ công cụ hiện đại của Android để xây dựng giao diện người dùng gốc. Bộ công cụ này rất trực quan và cần ít mã hơn so với việc viết tệp .xml và liên kết chúng với Hoạt động, Mảnh hoặc Khung hiển thị.

Bước đầu tiên để hiểu rõ mức độ hiệu quả của Firebase và Jetpack Compose khi hoạt động cùng nhau là hiểu về cấu trúc Android hiện đại. Một cấu trúc tốt giúp hệ thống trở nên dễ hiểu, dễ phát triển và bảo trì, vì nó thể hiện rất rõ cách các thành phần được sắp xếp và giao tiếp với nhau. Trong thế giới Android, cấu trúc được đề xuất có tên là Mô hình – Khung hiển thị – ViewModel. Mô hình đại diện cho lớp truy cập Dữ liệu trong ứng dụng. Khung hiển thị là lớp giao diện người dùng và không biết gì về logic nghiệp vụ. Và ViewModel là nơi áp dụng logic nghiệp vụ, đôi khi yêu cầu ViewModel gọi lớp Mô hình.

Bạn nên đọc bài viết này để hiểu cách áp dụng Mô hình – Xem – ViewModel cho ứng dụng Android được tạo bằng Jetpack Compose, vì điều này sẽ giúp cơ sở mã dễ hiểu hơn và giúp hoàn thành các bước tiếp theo dễ dàng hơn.

Sản phẩm bạn sẽ tạo ra

Make It So là một ứng dụng danh sách việc cần làm đơn giản, cho phép người dùng thêm và chỉnh sửa công việc, thêm cờ, mức độ ưu tiên và ngày đến hạn, cũng như đánh dấu công việc là đã hoàn thành. Các hình ảnh dưới đây hiển thị hai trang chính của ứng dụng này: trang tạo công việc và trang chính chứa danh sách công việc được tạo.

Màn hình "Make It So Add Tasks" (Tạo tác vụ) Biến video thành màn hình chính

Bạn sẽ thêm một số tính năng còn thiếu trong ứng dụng này:

  • Xác thực người dùng bằng email và mật khẩu
  • Thêm trình nghe vào bộ sưu tập Firestore và làm cho giao diện người dùng phản ứng với các thay đổi
  • Thêm dấu vết tuỳ chỉnh để giám sát hiệu suất của một số mã cụ thể trong ứng dụng
  • Tạo nút bật/tắt tính năng bằng cách sử dụng Cấu hình từ xa và sử dụng phương thức phát hành theo giai đoạn để chạy tính năng đó

Kiến thức bạn sẽ học được

  • Cách sử dụng tính năng Xác thực Firebase, Giám sát hiệu suất, Cấu hình từ xa và Cloud Firestore trong một ứng dụng Android hiện đại
  • Cách điều chỉnh API Firebase sao cho phù hợp với kiến trúc MVVM
  • Cách phản ánh các thay đổi được thực hiện bằng API Firebase trong giao diện người dùng Compose

Bạn cần có

2. Tải ứng dụng mẫu và thiết lập Firebase

Nhận mã của ứng dụng mẫu

Sao chép kho lưu trữ GitHub từ dòng lệnh:

git clone https://github.com/FirebaseExtended/make-it-so-android.git

Thiết lập Firebase

Việc đầu tiên bạn cần làm là chuyển đến bảng điều khiển của Firebase và tạo một dự án Firebase bằng cách nhấp vào nút "+ Thêm dự án" như bạn có thể thấy dưới đây:

bảng điều khiển của Firebase

Làm theo các bước trên màn hình để hoàn tất quá trình tạo dự án.

Bên trong mỗi dự án Firebase, bạn có thể tạo các ứng dụng khác nhau: dành cho Android, iOS, Web, Flutter và Unity. Chọn tuỳ chọn Android, như bạn thấy dưới đây:

Tổng quan về dự án Firebase

Sau đó, hãy làm theo những bước sau:

  1. Nhập com.example.makeitso làm tên gói và nhập biệt hiệu (không bắt buộc). Đối với lớp học lập trình này, bạn không cần thêm chứng chỉ ký gỡ lỗi.
  2. Nhấp vào Tiếp theo để đăng ký ứng dụng của bạn và truy cập vào tệp cấu hình Firebase.
  3. Nhấp vào Tải google-services.json xuống để tải tệp cấu hình của bạn xuống rồi lưu tệp đó trong thư mục make-it-so-android/app.
  4. Nhấp vào Tiếp theo. Vì Firebase SDK đã có trong tệp build.gradle trong dự án mẫu, hãy nhấp vào Tiếp theo để chuyển sang Các bước tiếp theo.
  5. Nhấp vào Tiếp tục tới bảng điều khiển để hoàn tất.

Để giúp ứng dụng Make It So hoạt động đúng cách, có 2 việc bạn cần làm trong Console trước khi chuyển sang đoạn mã: bật nhà cung cấp dịch vụ xác thực và tạo cơ sở dữ liệu Firestore. Trước tiên, hãy bật tính năng Xác thực để người dùng có thể đăng nhập vào ứng dụng:

  1. Trong trình đơn Build (Bản dựng), hãy chọn Xác thực rồi nhấp vào Get Started (Bắt đầu).
  2. Từ thẻ Phương thức đăng nhập, hãy chọn Email/Mật khẩu rồi bật phương thức này.
  3. Tiếp theo, hãy nhấp vào Thêm nhà cung cấp mới rồi chọn và bật chế độ Ẩn danh.

Tiếp theo, hãy thiết lập Firestore. Bạn sẽ sử dụng Firestore để lưu trữ các công việc của một người dùng đã đăng nhập. Mỗi người dùng sẽ nhận được tài liệu riêng trong một bộ sưu tập của cơ sở dữ liệu.

  1. Trên trình đơn Build (Tạo), hãy chọn Firestore (Khôi phục cơ sở dữ liệu), sau đó nhấp vào Tạo cơ sở dữ liệu (Create Database).
  2. Luôn bật Bắt đầu ở chế độ phát hành công khai rồi nhấp vào Tiếp theo.
  3. Khi được nhắc, hãy chọn vị trí lưu trữ dữ liệu Cloud Firestore của bạn. Khi phát triển một ứng dụng chính thức, bạn nên đặt khu vực này ở khu vực gần với phần lớn người dùng của mình và gần giống với các dịch vụ Firebase khác, như Functions. Đối với lớp học lập trình này, bạn có thể giữ lại khu vực mặc định hoặc chọn khu vực gần bạn nhất.
  4. Nhấp vào Bật để cung cấp cơ sở dữ liệu Firestore của bạn.

Hãy dành chút thời gian để xây dựng các Quy tắc bảo mật mạnh mẽ cho cơ sở dữ liệu Firestore. Mở trang tổng quan Firestore rồi chuyển đến thẻ Quy tắc. Sau đó, hãy cập nhật Quy tắc bảo mật có dạng như sau:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow create: if request.auth != null;
      allow read, update, delete: if request.auth != null && resource.data.userId == request.auth.uid;
    }
  }
}

Về cơ bản, các quy tắc này cho biết rằng bất kỳ người dùng đã đăng nhập nào vào ứng dụng đều có thể tạo tài liệu cho chính mình trong bất kỳ bộ sưu tập nào. Sau đó, khi được tạo, chỉ người dùng đã tạo tài liệu đó mới có thể xem, cập nhật hoặc xoá tài liệu đó.

Chạy ứng dụng

Bây giờ, bạn đã sẵn sàng chạy ứng dụng! Mở thư mục make-it-so-android/start trong Android Studio và chạy ứng dụng (bạn có thể thực hiện việc này bằng Trình mô phỏng Android hoặc thiết bị Android thực).

3. Xác thực Firebase

Bạn sẽ thêm tính năng nào?

Ở trạng thái hiện tại của ứng dụng mẫu Make It So, người dùng có thể bắt đầu sử dụng ứng dụng mà không cần phải đăng nhập trước. Tính năng này sử dụng phương thức xác thực ẩn danh để thực hiện việc này. Tuy nhiên, tài khoản ẩn danh không cho phép người dùng truy cập vào dữ liệu của họ trên các thiết bị khác hoặc thậm chí trong các phiên trong tương lai. Mặc dù xác thực ẩn danh rất hữu ích cho quá trình làm quen với ứng dụng, nhưng bạn phải luôn cung cấp tuỳ chọn để người dùng chuyển đổi sang một hình thức đăng nhập khác. Vì vậy, trong lớp học lập trình này, bạn sẽ thêm phương thức xác thực email và mật khẩu vào ứng dụng Make It So.

Đã đến lúc lập trình!

Ngay sau khi người dùng tạo tài khoản, bằng cách nhập email và mật khẩu, bạn cần yêu cầu API xác thực Firebase cung cấp thông tin đăng nhập email, sau đó liên kết thông tin đăng nhập mới với tài khoản ẩn danh. Mở tệp AccountServiceImpl.kt trong Android Studio rồi cập nhật hàm linkAccount để hàm này có dạng như sau:

model/service/impl/AccountServiceImpl.kt

override suspend fun linkAccount(email: String, password: String) {
    val credential = EmailAuthProvider.getCredential(email, password)
    auth.currentUser!!.linkWithCredential(credential).await()
}

Bây giờ, hãy mở SignUpViewModel.kt và gọi hàm dịch vụ linkAccount bên trong khối launchCatching của hàm onSignUpClick:

screen/sign_up/SignUpViewModel.kt

launchCatching {
    accountService.linkAccount(email, password)
    openAndPopUp(SETTINGS_SCREEN, SIGN_UP_SCREEN)
}

Trước tiên, ứng dụng sẽ cố gắng xác thực và nếu lệnh gọi thành công, lệnh gọi sẽ chuyển sang màn hình tiếp theo (SettingsScreen). Khi bạn thực thi các lệnh gọi này bên trong một khối launchCatching, nếu có lỗi xảy ra trên dòng đầu tiên, thì hệ thống sẽ phát hiện và xử lý ngoại lệ, còn dòng thứ hai sẽ không truy cập được.

Ngay khi SettingsScreen được mở lại, bạn cần đảm bảo rằng các tuỳ chọn Đăng nhậpTạo tài khoản đã biến mất vì bây giờ người dùng đã được xác thực. Để thực hiện việc này, hãy làm cho SettingsViewModel theo dõi trạng thái của người dùng hiện tại (có trong AccountService.kt), để kiểm tra xem tài khoản có ẩn danh hay không. Để thực hiện việc này, hãy cập nhật uiState trong SettingsViewModel.kt như sau:

screen/settings/SettingsViewModel.kt

val uiState = accountService.currentUser.map {
    SettingsUiState(it.isAnonymous)
}

Việc cuối cùng bạn cần làm là cập nhật uiState trong SettingsScreen.kt để thu thập các trạng thái do SettingsViewModel đưa ra:

screen/settings/SettingsScreen.kt

val uiState by viewModel.uiState.collectAsState(
    initial = SettingsUiState(false)
)

Giờ đây, mỗi khi người dùng thay đổi, SettingsScreen sẽ tự kết hợp lại để cho thấy các tuỳ chọn theo trạng thái xác thực mới của người dùng.

Đã đến lúc kiểm tra!

Chạy Make It So và chuyển đến phần cài đặt bằng cách nhấp vào biểu tượng bánh răng ở góc trên cùng bên phải màn hình. Từ đó, hãy nhấp vào tuỳ chọn tạo tài khoản:

Màn hình Cài đặt cách tự động thiết lập Màn hình Đăng ký

Nhập email hợp lệ và mật khẩu mạnh để tạo tài khoản của bạn. Thao tác này sẽ hoạt động và bạn nên được chuyển hướng đến trang cài đặt, nơi bạn sẽ thấy hai tuỳ chọn mới: đăng xuất và xoá tài khoản của bạn. Bạn có thể kiểm tra tài khoản mới được tạo trong trang tổng quan Xác thực trên bảng điều khiển của Firebase bằng cách nhấp vào thẻ Người dùng.

4. Cloud Firestore

Bạn sẽ thêm tính năng nào?

Đối với Cloud Firestore, bạn sẽ thêm một trình nghe vào bộ sưu tập Firestore. Bộ sưu tập này lưu trữ các tài liệu đại diện cho những việc cần làm xuất hiện trong phần Make It So. Sau khi thêm trình nghe này, bạn sẽ nhận được mọi nội dung cập nhật đối với bộ sưu tập này.

Đã đến lúc lập trình!

Cập nhật Flow có sẵn trong StorageServiceImpl.kt thành như sau:

model/service/impl/StorageServiceImpl.kt

override val tasks: Flow<List<Task>>
    get() =
      auth.currentUser.flatMapLatest { user ->
        firestore.collection(TASK_COLLECTION).whereEqualTo(USER_ID_FIELD, user.id).dataObjects()
      }

Mã này đang thêm trình nghe vào tập hợp công việc dựa trên user.id. Mỗi công việc được biểu thị bằng một tài liệu trong bộ sưu tập có tên tasks, và mỗi công việc có một trường tên là userId. Xin lưu ý rằng Flow mới sẽ được kích hoạt nếu trạng thái của currentUser thay đổi (ví dụ: bằng cách đăng xuất).

Bây giờ, bạn cần làm cho Flow trong TasksViewModel.kt phản ánh giống như trong dịch vụ:

screen/tasks/TasksViewModel.kt

val tasks = storageService.tasks

Và điều cuối cùng là làm cho composable function trong TasksScreens.kt đại diện cho giao diện người dùng, nhận biết về luồng này và thu thập nó dưới dạng trạng thái. Mỗi khi trạng thái thay đổi, hàm có khả năng kết hợp sẽ tự động kết hợp lại và cho người dùng thấy trạng thái gần đây nhất. Thêm đoạn mã này vào TasksScreen composable function:

screen/tasks/TasksScreen.kt

val tasks = viewModel
    .tasks
    .collectAsStateWithLifecycle(emptyList())

Sau khi hàm có khả năng kết hợp có quyền truy cập vào các trạng thái này, bạn có thể cập nhật LazyColumn (là cấu trúc bạn sử dụng để hiển thị danh sách trên màn hình) như sau:

screen/tasks/TasksScreen.kt

LazyColumn {
    items(tasks.value, key = { it.id }) { taskItem ->
        TaskItem( [...] )
    }
}

Đã đến lúc kiểm tra!

Để kiểm tra xem tính năng này có hoạt động không, hãy thêm công việc mới bằng ứng dụng (bằng cách nhấp vào nút thêm ở góc dưới cùng bên phải màn hình). Sau khi bạn tạo xong nhiệm vụ, nhiệm vụ đó sẽ xuất hiện trong tập hợp Firestore trong Bảng điều khiển của Firestore. Nếu đăng nhập vào chương trình Make It So trên các thiết bị khác bằng cùng tài khoản, bạn sẽ có thể chỉnh sửa các mục việc cần làm và xem các mục đó được cập nhật trên tất cả thiết bị theo thời gian thực.

5. Giám sát hiệu suất

Bạn sẽ thêm tính năng nào?

Hiệu suất là một vấn đề rất quan trọng cần được chú ý vì người dùng rất có thể sẽ bỏ ứng dụng của bạn nếu hiệu suất không tốt và họ mất quá nhiều thời gian để hoàn tất một thao tác đơn giản là sử dụng ứng dụng đó. Đó là lý do tại sao đôi khi việc thu thập một vài chỉ số về hành trình cụ thể của người dùng trong ứng dụng lại hữu ích. Và để giúp bạn làm điều đó, Giám sát hiệu suất Firebase cung cấp dấu vết tùy chỉnh. Hãy làm theo các bước tiếp theo để thêm dấu vết tuỳ chỉnh và đo lường hiệu suất trong nhiều đoạn mã trong bài viết Make It So (Tạo dấu vết tuỳ chỉnh).

Đã đến lúc lập trình!

Nếu mở tệp Performance.kt, bạn sẽ thấy một hàm cùng dòng có tên là trace (dấu vết). Hàm này gọi API Giám sát hiệu suất để tạo một dấu vết tuỳ chỉnh, chuyển tên dấu vết đó dưới dạng thông số. Tham số khác mà bạn thấy là khối mã mà bạn muốn theo dõi. Chỉ số mặc định được thu thập cho mỗi dấu vết là thời gian cần để chạy hoàn chỉnh:

model/service/Performance.kt

inline fun <T> trace(name: String, block: Trace.() -> T): T = Trace.create(name).trace(block)

Bạn có thể chọn các phần của cơ sở mã mà bạn cho là quan trọng để đo lường và thêm dấu vết tuỳ chỉnh vào đó. Dưới đây là ví dụ về cách thêm một dấu vết tuỳ chỉnh vào hàm linkAccount mà bạn đã thấy trước đó (trong AccountServiceImpl.kt) ở lớp học lập trình này:

model/service/impl/AccountServiceImpl.kt

override suspend fun linkAccount(email: String, password: String): Unit =
  trace(LINK_ACCOUNT_TRACE) {
      val credential = EmailAuthProvider.getCredential(email, password)
      auth.currentUser!!.linkWithCredential(credential).await()
  }

Giờ đến lượt bạn! Thêm một số dấu vết tuỳ chỉnh vào ứng dụng Make It So (Tạo nên vậy) và chuyển sang phần tiếp theo để kiểm tra xem thành phần này có hoạt động như mong đợi không.

Đã đến lúc kiểm tra!

Sau khi bạn hoàn tất việc thêm dấu vết tuỳ chỉnh, hãy chạy ứng dụng và đảm bảo rằng bạn sử dụng các tính năng mà bạn muốn đo lường một vài lần. Sau đó, hãy chuyển đến bảng điều khiển của Firebase rồi chuyển đến Trang tổng quan về hiệu suất. Ở cuối màn hình, bạn sẽ thấy ba thẻ: Yêu cầu mạng, Dấu vết tuỳ chỉnhHiển thị màn hình.

Chuyển đến thẻ Dấu vết tuỳ chỉnh và kiểm tra để đảm bảo các dấu vết bạn đã thêm trong cơ sở mã đang hiển thị ở đó. Bạn cũng có thể biết được thời gian thực thi các đoạn mã này.

6. Cấu hình từ xa

Bạn sẽ thêm tính năng nào?

Có nhiều trường hợp sử dụng Cấu hình từ xa, từ việc thay đổi giao diện của ứng dụng từ xa đến thiết lập các hành vi riêng cho từng phân khúc người dùng. Trong lớp học lập trình này, bạn sẽ sử dụng Cấu hình từ xa để tạo một nút bật/tắt tính năng nhằm hiện hoặc ẩn tính năng chỉnh sửa nhiệm vụ mới trên ứng dụng Make It So (Chỉnh sửa công việc).

Đã đến lúc lập trình!

Việc đầu tiên bạn cần làm là tạo cấu hình trong bảng điều khiển của Firebase. Để làm việc này, bạn cần chuyển đến trang tổng quan Cấu hình từ xa rồi nhấp vào nút Thêm thông số. Điền vào các trường theo hình ảnh dưới đây:

Hộp thoại Tạo thông số cho Cấu hình từ xa

Sau khi điền vào tất cả các trường, bạn có thể nhấp vào nút Lưu, rồi nhấp vào Xuất bản. Giờ đây tham số đã được tạo và có sẵn cho cơ sở mã, bạn cần thêm mã sẽ tìm nạp các giá trị mới vào ứng dụng. Mở tệp ConfigurationServiceImpl.kt và cập nhật phương thức triển khai 2 hàm này:

model/service/impl/ConfigurationServiceImpl.kt

override suspend fun fetchConfiguration(): Boolean {
  return remoteConfig.fetchAndActivate().await()
}

override val isShowTaskEditButtonConfig: Boolean
  get() = remoteConfig[SHOW_TASK_EDIT_BUTTON_KEY].asBoolean()

Hàm đầu tiên tìm nạp các giá trị từ máy chủ và được gọi ngay khi ứng dụng khởi động, trong SplashViewModel.kt. Đây là cách tốt nhất để đảm bảo rằng các giá trị mới nhất sẽ có sẵn trên tất cả các màn hình ngay từ đầu. Việc bạn thay đổi giao diện người dùng hoặc hành vi của ứng dụng (khi người dùng đang thực hiện một thao tác nào đó) sau này sẽ gây ra trải nghiệm không tốt cho người dùng!

Hàm thứ hai trả về giá trị boolean đã được xuất bản cho tham số mà bạn vừa tạo trong Console. Bạn cần truy xuất thông tin này trong TasksViewModel.kt bằng cách thêm đoạn mã sau vào hàm loadTaskOptions:

screen/tasks/TasksViewModel.kt

fun loadTaskOptions() {
  val hasEditOption = configurationService.isShowTaskEditButtonConfig
  options.value = TaskActionOption.getOptions(hasEditOption)
}

Bạn đang truy xuất giá trị trên dòng đầu tiên và sử dụng giá trị đó để tải các lựa chọn trình đơn cho các mục tác vụ trên dòng thứ hai. Nếu giá trị là false, điều này có nghĩa là trình đơn sẽ không chứa lựa chọn chỉnh sửa. Giờ đây, khi đã có danh sách các tuỳ chọn, bạn cần làm cho giao diện người dùng hiển thị chính xác danh sách này. Khi tạo một ứng dụng bằng Jetpack Compose, bạn cần tìm composable function khai báo giao diện người dùng của TasksScreen. Vì vậy, hãy mở tệp TasksScreen.kt và cập nhật LazyColum để trỏ đến các tuỳ chọn có trong TasksViewModel.kt:

screen/tasks/TasksScreen.kt

val options by viewModel.options

LazyColumn {
  items(tasks.value, key = { it.id }) { taskItem ->
    TaskItem(
      options = options,
      [...]
    )
  }
}

TaskItem là một composable function khác khai báo giao diện người dùng của một tác vụ. Đồng thời, mỗi việc cần làm sẽ có một trình đơn chứa các lựa chọn xuất hiện khi người dùng nhấp vào biểu tượng ba dấu chấm ở cuối nhiệm vụ đó.

Đã đến lúc kiểm tra!

Bây giờ, bạn đã sẵn sàng chạy ứng dụng! Kiểm tra để đảm bảo rằng giá trị mà bạn đã xuất bản bằng bảng điều khiển của Firebase khớp với hành vi của ứng dụng:

  • Nếu là false, bạn sẽ chỉ thấy hai lựa chọn khi nhấp vào biểu tượng ba dấu chấm;
  • Nếu trạng thái là true, bạn sẽ thấy 3 tuỳ chọn khi nhấp vào biểu tượng ba dấu chấm;

Hãy thử thay đổi giá trị một vài lần trong Console rồi khởi động lại ứng dụng. Thật dễ dàng để ra mắt các tính năng mới trong ứng dụng của bạn bằng cách sử dụng Cấu hình từ xa!

7. Xin chúc mừng

Xin chúc mừng, bạn đã tạo thành công một ứng dụng Android bằng Firebase và Jetpack Compose!

Bạn đã thêm tính năng Xác thực Firebase, Giám sát hiệu suất, Cấu hình từ xa và Cloud Firestore vào một ứng dụng Android được xây dựng hoàn toàn bằng Jetpack Compose cho giao diện người dùng và bạn đã làm cho ứng dụng này phù hợp với kiến trúc MVVM được đề xuất!

Tài liệu đọc thêm

Tài liệu tham khảo