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: 16/11/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 là 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. Đây là bộ công cụ hiện đại của Android để xây dựng giao diện người dùng gốc – trực quan và yêu cầu ít mã hơn so với việc viết tệp .xml và liên kết các tệp đó với Hoạt động, Mảnh hoặc Khung hiển thị.

Bước đầu tiên để hiểu cách Firebase và Jetpack Compose hoạt động cùng nhau là tìm hiểu cấu trúc Android hiện đại. Cấu trúc tốt giúp hệ thống dễ hiểu, dễ phát triển và dễ bảo trì, vì cấu trúc này cho biế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 gọi là Mô hình – Chế độ xem – ViewModel. Mô hình (Model) đại diện cho lớp truy cập vào Dữ liệu trong ứng dụng. Khung hiển thị là lớp giao diện người dùng và không được 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 – Chế độ xem – ViewModel cho một ứng dụng Android được tạo bằng Jetpack Compose, vì điều này sẽ giúp bạn dễ hiểu cơ sở mã hơn và dễ dàng hoàn thành các bước tiếp theo 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 việc cần làm, thêm cờ, mức độ ưu tiên và ngày đến hạn, đồng thời đánh dấu việc cần làm là đã hoàn thành. Hình ảnh bên dưới cho thấy hai trang chính của ứng dụng này: trang tạo việc cần làm và trang chính có danh sách việc cần làm đã tạo.

Màn hình Thêm việc cần làm của Make it So Màn hình chính của Make it So

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 một tập hợp Firestore và khiến 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 để theo dõi hiệu suất của một mã cụ thể trong ứng dụng
  • Tạo nút bật/tắt tính năng bằng Cấu hình từ xa và sử dụng tính năng triển khai 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 cho phù hợp với cấu 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

Những gì bạn cần

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

Tải 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

Tạo dự án 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 bên dưới:

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 việc tạo dự án.

Thêm ứng dụng Android vào dự án Firebase

Trong dự án Firebase, bạn có thể đăng ký nhiều ứng dụng: cho Android, iOS, Web, Flutter và Unity.

Chọn Android như bạn thấy ở đây:

Tổng quan về dự án Firebase

Sau đó, hãy làm theo các 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 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 xuống và lưu vào thư mục make-it-so-android/app.
  4. Nhấp vào Tiếp theo. Vì các SDK Firebase đã có trong tệp build.gradle trong dự án mẫu, nên hãy nhấp vào Tiếp theo để chuyển đến Các bước tiếp theo.
  5. Nhấp vào Tiếp tục đến bảng điều khiển để hoàn tất.

Để ứng dụng Make it So hoạt động đúng cách, bạn cần làm hai việc trong Console trước khi chuyển sang mã: bật trình cung cấp dịch vụ xác thực và tạo cơ sở dữ liệu Firestore.

Thiết lập tính năng Xác thực

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 (Tạo), hãy chọn Authentication (Xác thực), rồi nhấp vào Get Started (Bắt đầu).
  2. Trong 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 Ẩn danh.

Thiết lập Cloud Firestore

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

  1. Trong bảng điều khiển bên trái của Firebase, hãy mở rộng mục Build (Tạo) rồi chọn Firestore database (Cơ sở dữ liệu Firestore).
  2. Nhấp vào Tạo cơ sở dữ liệu.
  3. Đặt Mã nhận dạng cơ sở dữ liệu thành (default).
  4. Chọn một vị trí cho cơ sở dữ liệu, sau đó nhấp vào Tiếp theo.
    Đối với một ứng dụng thực tế, bạn nên chọn một vị trí gần với người dùng.
  5. Nhấp vào Bắt đầu ở chế độ thử nghiệm. Đọc tuyên bố từ chối trách nhiệm về các quy tắc bảo mật.
    Trong các bước tiếp theo của phần này, bạn sẽ thêm Quy tắc bảo mật để bảo mật dữ liệu của mình. Đừng phân phối hoặc công khai ứng dụng mà không thêm Quy tắc bảo mật cho cơ sở dữ liệu của bạn.
  6. Nhấp vào Tạo.

Hãy dành chút thời gian để tạo các Quy tắc bảo mật mạnh mẽ cho cơ sở dữ liệu Firestore.

  1. Mở trang tổng quan về Firestore rồi chuyển đến thẻ Quy tắc.
  2. Cập nhật Quy tắc bảo mật 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 mọi người dùng đã đăng nhập của ứng dụng đều có thể tạo tài liệu cho riêng 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

Giờ thì 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. Phương thức này sử dụng phương thức xác thực ẩn danh để đạt được điều 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ù quy trình xác thực ẩn danh rất hữu ích cho việc làm quen, nhưng bạn phải luôn cung cấp cho người dùng lựa chọn chuyển đổi sang một hình thức đăng nhập khác. Do đó, trong lớp học lập trình này, bạn sẽ thêm tính năng xác thực bằng email và mật khẩu vào ứng dụng Make It So.

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

Ngay 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 Firebase Authentication API cung cấp thông tin xác thực qua email, sau đó liên kết thông tin xác thực mới với tài khoản ẩn danh. Mở tệp AccountServiceImpl.kt trong Android Studio và cập nhật hàm linkAccount 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 linkAccount của dịch vụ bên trong khối launchCatching của hàm onSignUpClick:

screens/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, ứng dụng 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 khối launchCatching, nếu xảy ra lỗi trên dòng đầu tiên, thì ngoại lệ sẽ được phát hiện và xử lý, đồng thời dòng thứ hai sẽ không được thực thi.

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ì giờ đây người dùng đã được xác thực. Để thực hiện việc này, hãy tạo SettingsViewModel để theo dõi trạng thái của người dùng hiện tại (có trong AccountService.kt) nhằm 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:

screens/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 phát ra:

screens/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 để hiển thị 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 thử!

Chạy Make it So rồi 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 của màn hình. Tại đó, hãy nhấp vào lựa chọn tạo tài khoản:

Màn hình cài đặt Make it So Màn hình đăng ký Make it So

Nhập một địa chỉ email hợp lệ và mật khẩu mạnh để tạo tài khoản. Thao tác này sẽ hoạt động và bạn sẽ được chuyển hướng đến trang cài đặt. Tại đây, bạn sẽ thấy hai lựa chọn mới: đăng xuất và xoá tài khoả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 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 trình nghe vào bộ sưu tập Firestore để lưu trữ các tài liệu đại diện cho các tác vụ hiển thị trong 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ó trong StorageServiceImpl.kt 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 tác vụ dựa trên user.id. Mỗi việc cần làm được biểu thị bằng một tài liệu trong một bộ sưu tập có tên là tasks và mỗi tài liệu trong đó có một trường có tên là userId. Xin lưu ý rằng Flow mới sẽ được phát nếu trạng thái của currentUser thay đổi (ví dụ: khi đă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ụ:

screens/tasks/TasksViewModel.kt

val tasks = storageService.tasks

Và việc 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 được luồng này và thu thập luồng này 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à hiển thị trạng thái mới nhất cho người dùng. Thêm nội dung này vào TasksScreen composable function:

screens/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:

screens/tasks/TasksScreen.kt

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

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

Để kiểm tra xem ứng dụng có hoạt động hay không, hãy thêm một việc cần làm 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 hoàn tất việc tạo tác vụ, tác vụ đó sẽ xuất hiện trong tập hợp Firestore trong Firestore Console. Nếu đăng nhập vào Make it So trên các thiết bị khác bằng cùng một tài khoản, bạn có thể chỉnh sửa các việc cần làm và xem các việc cần làm đó được cập nhật theo thời gian thực trên tất cả thiết bị.

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 yếu tố rất quan trọng cần chú ý vì người dùng rất có thể sẽ từ bỏ việc sử dụng ứ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 thành một thao tác đơn giản bằng ứng dụng đó. Đó là lý do đôi khi bạn nên thu thập một số chỉ số về một hành trình cụ thể mà người dùng thực hiện trong ứng dụng. Để giúp bạn làm việc đó, tính năng Giám sát hiệu suất Firebase cung cấp dấu vết tuỳ chỉnh. 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 các đoạn mã khác nhau trong Make it So.

Đã đế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. 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, truyền tên dấu vết dưới dạng tham 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 thiết để chạy hoàn toàn:

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 những 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 dấu vết tuỳ chỉnh vào hàm linkAccount mà bạn đã thấy trước đó (trong AccountServiceImpl.kt) trong 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 rồi chuyển sang phần tiếp theo để kiểm tra xem ứng dụng có hoạt động như mong đợi không.

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

Sau khi bạn thêm xong các dấu vết tuỳ chỉnh, hãy chạy ứng dụng và nhớ sử dụng các tính năng mà bạn muốn đo lường 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 3 thẻ: Yêu cầu mạng, Dấu vết tuỳ chỉnhHiển thị màn hình.

Chuyển đến thẻ Custom traces (Dấu vết tuỳ chỉnh) và kiểm tra để đảm bảo rằng các dấu vết bạn đã thêm vào cơ sở mã đang hiển thị ở đó và bạn có thể xem thời gian thường mất để 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ó rất nhiều trường hợp sử dụng cho Cấu hình từ xa, từ việc thay đổi giao diện của ứng dụng từ xa đến việc định cấu hình các hành vi khác nhau cho các phân khúc người dùng khác nhau. Trong lớp học lập trình này, bạn sẽ sử dụng Remote Config để tạo một nút bật/tắt tính năng hiển thị hoặc ẩn tính năng chỉnh sửa việc cần làm mới trên ứng dụng Make it So.

Đã đế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 Firebase. Để làm việc này, bạn cần chuyển đến trang tổng quan về Cấu hình từ xa rồi nhấp vào nút Thêm tham số. Điền vào các trường theo hình ảnh bên dưới:

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

Sau khi điền 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, khi 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 cách triển khai hai 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 trong tất cả màn hình ngay từ đầu. Người dùng sẽ không có trải nghiệm tốt nếu bạn thay đổi giao diện người dùng hoặc hành vi của ứng dụng sau này, khi người dùng đang thực hiện một việc gì đó!

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

screens/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 tuỳ chọn trình đơn cho các mục công việc trên dòng thứ hai. Nếu giá trị là false, thì tức là trình đơn sẽ không chứa tuỳ chọn chỉnh sửa. Bây giờ, bạn đã 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 đó. Khi xây dựng ứ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ó sẵn trong TasksViewModel.kt:

screens/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ụ. Và mỗi tác vụ đều có một trình đơn với các tuỳ chọn hiển thị khi người dùng nhấp vào biểu tượng ba dấu chấm ở cuối tác vụ.

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

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

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

Hãy thử thay đổi giá trị vài lần trong Console rồi khởi động lại ứng dụng. Bạn có thể dễ dàng ra mắt các tính năng mới trong ứng dụng bằ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 tạo hoàn toàn bằng Jetpack Compose cho giao diện người dùng và bạn đã điều chỉnh ứng dụng đó cho phù hợp với cấu trúc MVVM được đề xuất!

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

Tài liệu tham khảo