Lấy dữ liệu trên Internet

1. Trước khi bắt đầu

Hầu hết ứng dụng Android trên thị trường đều kết nối với Internet để thực hiện các hoạt động mạng, chẳng hạn như truy xuất email, tin nhắn hoặc thông tin khác từ máy chủ phụ trợ. Ví dụ: Gmail, YouTube và Google Photos là các ứng dụng có kết nối với Internet để hiển thị dữ liệu người dùng.

Trong lớp học lập trình này, bạn sẽ dùng thư viện nguồn mở và thư viện do cộng đồng tạo để xây dựng lớp dữ liệu và nhận dữ liệu từ máy chủ phụ trợ. Việc này giúp đơn giản hoá quá trình tìm nạp dữ liệu, đồng thời giúp ứng dụng tuân thủ các phương pháp hay nhất của Android, chẳng hạn như thực hiện thao tác trên một luồng ở chế độ nền. Bạn cũng sẽ thấy thông báo lỗi nếu Internet bị chậm hoặc không hoạt động, cho người dùng biết về mọi vấn đề kết nối mạng.

Điều kiện tiên quyết

  • Kiến thức cơ bản về cách tạo hàm có khả năng kết hợp.
  • Kiến thức cơ bản về cách sử dụng bộ thành phần cấu trúc Android ViewModel.
  • Kiến thức cơ bản về cách sử dụng coroutine cho các tác vụ chạy trong thời gian dài.
  • Kiến thức cơ bản về cách thêm phần phụ thuộc trong build.gradle.kts.

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

  • Dịch vụ web REST là gì.
  • Cách sử dụng thư viện Retrofit để kết nối với dịch vụ web REST trên Internet và nhận phản hồi.
  • Cách sử dụng thư viện Chuyển đổi tuần tự (kotlinx.serialization) để phân tích cú pháp phản hồi JSON thành đối tượng dữ liệu.

Bạn sẽ thực hiện

  • Sửa đổi ứng dụng khởi động (starter app) để tạo yêu cầu API dịch vụ web và xử lý phản hồi.
  • Triển khai lớp dữ liệu cho ứng dụng bằng cách sử dụng thư viện Retrofit.
  • Phân tích cú pháp phản hồi JSON qua dịch vụ web thành danh sách đối tượng dữ liệu của ứng dụng bằng thư viện kotlinx.serialization và đính kèm danh sách đó vào trạng thái giao diện người dùng.
  • Sử dụng tính năng hỗ trợ của Retrofit dành cho coroutine để đơn giản hoá mã.

Bạn cần có

  • Máy tính đã cài đặt Android Studio
  • Mã khởi động cho ứng dụng Mars Photos

2. Tổng quan về ứng dụng

Bạn sẽ thao tác với ứng dụng có tên là Mars Photos cho thấy hình ảnh bề mặt sao Hoả. Ứng dụng này kết nối với một dịch vụ web để truy xuất và hiển thị ảnh sao Hoả. Những bức ảnh này là ảnh sao Hoả thực tế được chụp qua thiết bị thám hiểm sao Hoả của NASA. Dưới đây là ảnh chụp màn hình của ứng dụng hoàn thiện, hiển thị các hình ảnh theo bố cục lưới.

e6e7f2702174bf88.png

Phiên bản ứng dụng bạn xây dựng trong lớp học lập trình này sẽ không có nhiều hình ảnh trực quan. Lớp học lập trình này tập trung vào phần lớp dữ liệu của ứng dụng để kết nối với Internet và tải dữ liệu thuộc tính thô xuống bằng dịch vụ web. Để đảm bảo ứng dụng truy xuất và phân tích cú pháp dữ liệu này đúng cách, bạn có thể in số lượng ảnh nhận được từ máy chủ phụ trợ trong thành phần kết hợp (composable) Text.

fb569daf7348206a.png

3. Khám phá ứng dụng khởi đầu Mars Photos

Tải mã khởi đầu xuống

Để bắt đầu, hãy tải mã khởi đầu xuống:

Ngoài ra, bạn có thể sao chép kho lưu trữ GitHub cho mã:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-mars-photos.git
$ cd basic-android-kotlin-compose-training-mars-photos
$ git checkout starter

Bạn có thể xem mã này trong kho lưu trữ GitHub Mars Photos.

Chạy mã khởi đầu

  1. Mở dự án đã tải xuống trong Android Studio. Tên thư mục của dự án là basic-android-kotlin-compose-training-mars-photos.
  2. Trong ngăn Android, mở rộng app (ứng dụng) > java. Hãy lưu ý rằng ứng dụng có thư mục gói tên là ui. Đây là lớp giao diện người dùng (UI layer) của ứng dụng.

34feb1008317f518.png

  1. Chạy ứng dụng. Khi biên dịch và chạy ứng dụng này, bạn sẽ thấy màn hình sau đây với văn bản phần giữ chỗ ở giữa. Kết thúc lớp học lập trình này, bạn sẽ cập nhật văn bản phần giữ chỗ nêu trên bằng số lượng ảnh đã truy xuất.

af554506fed04200.png

Hướng dẫn từng bước về mã khởi động

Trong nhiệm vụ này, bạn sẽ làm quen với cấu trúc của dự án. Dưới đây là các danh sách đưa ra hướng dẫn từng bước về các tệp và thư mục quan trọng trong dự án.

ui\MarsPhotosApp.kt:

  • Tệp này chứa thành phần kết hợp MarsPhotosApp hiển thị nội dung trên màn hình, chẳng hạn như thanh ứng dụng trên cùng và thành phần kết hợp HomeScreen. Văn bản phần giữ chỗ ở bước trước đó sẽ hiện trong thành phần kết hợp này.
  • Trong lớp học lập trình tiếp theo, thành phần kết hợp này sẽ hiển thị dữ liệu nhận được từ máy chủ phụ trợ của Mars Photos.

screens\MarsViewModel.kt:

  • Đây là tệp mô hình khung hiển thị (view) tương ứng cho MarsPhotosApp.
  • Lớp (class) này chứa một thuộc tính MutableState có tên là marsUiState. Việc cập nhật giá trị của thuộc tính này cũng sẽ cập nhật văn bản phần giữ chỗ đang hiện trên màn hình.
  • Phương thức getMarsPhotos() cập nhật phản hồi của phần giữ chỗ. Trong phần sau của lớp học lập trình, bạn sẽ sử dụng phương thức này để hiển thị dữ liệu đã tìm nạp qua máy chủ. Mục tiêu của lớp học lập trình này là cập nhật MutableState trong ViewModel bằng cách sử dụng dữ liệu mà bạn lấy được trên Internet.

screens\HomeScreen.kt:

  • Tệp này chứa các thành phần kết hợp HomeScreenResultScreen. ResultScreen có bố cục Box đơn giản hiển thị giá trị marsUiState trong thành phần kết hợp Text.

MainActivity.kt:

  • Nhiệm vụ duy nhất của hoạt động này là tải ViewModel và hiển thị thành phần kết hợp MarsPhotosApp.

4. Giới thiệu về dịch vụ web

Trong lớp học lập trình này, bạn tạo một lớp dành cho dịch vụ mạng giao tiếp với máy chủ phụ trợ và tìm nạp dữ liệu cần thiết. Bạn sử dụng một thư viện của bên thứ ba có tên là Retrofit để triển khai nhiệm vụ này. Bạn sẽ tìm hiểu thêm về thư viện này sau. ViewModel giao tiếp với lớp dữ liệu và phần còn lại của ứng dụng sẽ minh bạch trong quá trình triển khai này.

1a5ab459694c8cd7.png

MarsViewModel chịu trách nhiệm thực hiện lệnh gọi qua mạng để lấy dữ liệu ảnh sao Hoả. Trong ViewModel, bạn dùng MutableState để cập nhật giao diện người dùng ứng dụng khi dữ liệu thay đổi.

5. Dịch vụ web và Retrofit

Dữ liệu ảnh sao Hoả được lưu trữ trên máy chủ web. Để đưa dữ liệu này vào ứng dụng, bạn cần thiết lập kết nối và giao tiếp với máy chủ trên Internet.

5623549c54d20a6e.png

b32f5c6b823bbc29.png

Ngày nay, hầu hết máy chủ web chạy dịch vụ web bằng một kiến trúc web không có trạng thái (stateless) phổ biến được gọi là REST, viết tắt của REpresentational State Transfer (Kiến trúc chuyển trạng thái đại diện). Các dịch vụ web cung cấp kiến trúc này được gọi là các dịch vụ RESTful.

Các yêu cầu được gửi đến dịch vụ web RESTful theo cách chuẩn hoá, thông qua Mã nhận dạng tài nguyên đồng nhất (URI). Mỗi URI xác định một tài nguyên trong máy chủ theo tên mà không bao hàm vị trí của tài nguyên đó hay cách truy cập vào tài nguyên đó. Ví dụ: trong ứng dụng dành cho bài học này, bạn truy xuất URL hình ảnh bằng cách sử dụng URI máy chủ sau đây. (Máy chủ này lưu trữ cả ảnh sao Hoả và ảnh bất động sản trên sao Hoả):

android-kotlin-fun-mars-server.appspot.com

URL (Bộ định vị tài nguyên đồng nhất) là một tập hợp con của URI chỉ định vị trí tài nguyên tồn tại và cơ chế truy xuất tài nguyên đó.

Ví dụ:

URL sau đây liệt kê danh sách các tài sản bất động sản hiện có trên sao Hoả:

https://android-kotlin-fun-mars-server.appspot.com/realestate

URL sau đây liệt kê danh sách ảnh sao Hoả:

https://android-kotlin-fun-mars-server.appspot.com/photos

Các URL này tham chiếu đến một tài nguyên đã xác định (chẳng hạn như /realestate hoặc /photos) có thể truy cập được bằng Giao thức truyền siêu văn bản (http:) qua mạng. Bạn đang dùng điểm cuối /photos trong lớp học lập trình này. Điểm cuối là một URL cho phép bạn truy cập vào dịch vụ web đang chạy trên máy chủ.

Yêu cầu dịch vụ web

Mỗi yêu cầu dịch vụ web chứa một URI và được chuyển tới máy chủ bằng chính giao thức HTTP mà các trình duyệt web (như Chrome) sử dụng. Các yêu cầu HTTP chứa một thao tác để cho máy chủ biết cần làm gì.

Có thể kể đến một số thao tác HTTP phổ biến như sau:

  • GET để truy xuất dữ liệu máy chủ.
  • POST để tạo dữ liệu mới trên máy chủ.
  • PUT để cập nhật dữ liệu hiện có trên máy chủ.
  • DELETE để xoá dữ liệu khỏi máy chủ.

Ứng dụng của bạn sẽ gửi một yêu cầu HTTP GET tới máy chủ về việc cung cấp thông tin liên quan đến ảnh sao Hoả, sau đó máy chủ trả về phản hồi cho ứng dụng, bao gồm cả URL hình ảnh.

6631ce7e1157c39b.png

91616cc822ef8b48.png

Phản hồi của một dịch vụ web được định dạng theo một trong các định dạng dữ liệu phổ biến, chẳng hạn như XML (Ngôn ngữ đánh dấu mở rộng) hoặc JSON (Ký hiệu đối tượng JavaScript). Định dạng JSON biểu thị dữ liệu có cấu trúc trong các cặp khoá–giá trị. Một ứng dụng giao tiếp với API REST bằng JSON. Bạn sẽ tìm hiểu thêm về điều này trong nhiệm vụ sau.

Trong nhiệm vụ này, bạn thiết lập kết nối mạng với máy chủ, giao tiếp với máy chủ và nhận phản hồi JSON. Bạn sẽ sử dụng một máy chủ phụ trợ đã được viết sẵn cho bạn. Trong lớp học lập trình này, bạn dùng một thư viện bên thứ ba có tên là Retrofit để giao tiếp với máy chủ phụ trợ.

Thư viện bên ngoài

Thư viện bên ngoài hoặc thư viện bên thứ ba giống như phần mở rộng cho các API Android cốt lõi. Các thư viện mà bạn sử dụng trong khoá học này là dạng nguồn mở, do cộng đồng phát triển và duy trì được nhờ đóng góp của tập thể cộng đồng Android lớn mạnh trên toàn thế giới. Những tài nguyên này giúp các nhà phát triển Android như bạn xây dựng ứng dụng tốt hơn.

Thư viện Retrofit

Thư viện Retrofit mà bạn sử dụng trong lớp học lập trình này để giao tiếp với dịch vụ web RESTful sao Hoả là một ví dụ điển hình về thư viện được hỗ trợ và duy trì tốt. Bạn có thể nhận thấy điều này bằng cách xem trang GitHub của thư viện và xem xét các vấn đề còn tồn đọng cũng như các vấn đề đã đóng (một vài trong số đó là yêu cầu về tính năng). Nếu nhà phát triển thường xuyên giải quyết các vấn đề đó và phản hồi yêu cầu về tính năng, thì thư viện này có thể đã được duy trì tốt và là một lựa chọn phù hợp để dùng trong ứng dụng. Bạn cũng có thể tham khảo tài liệu về Retrofit để tìm hiểu thêm về thư viện này.

Thư viện Retrofit giao tiếp với phần phụ trợ REST. Phần này tạo mã, nhưng bạn cần cung cấp URI cho dịch vụ web dựa trên các tham số mà chúng tôi cung cấp. Bạn sẽ tìm hiểu thêm về chủ đề này trong các phần sau.

c486bf54e3167e0d.png

Thêm phần phụ thuộc Retrofit

Android Gradle cho phép bạn thêm các thư viện bên ngoài vào dự án của mình. Ngoài phần phụ thuộc thư viện, bạn cũng cần đưa vào kho lưu trữ nơi lưu trữ thư viện.

  1. Mở tệp gradle cấp mô-đun build.gradle.kts (Module :app).
  2. Trong phần dependencies, hãy thêm những dòng sau cho các thư viện Retrofit:
// Retrofit
implementation("com.squareup.retrofit2:retrofit:2.9.0")
// Retrofit with Scalar Converter
implementation("com.squareup.retrofit2:converter-scalars:2.9.0")

Cả hai thư viện hoạt động cùng nhau. Phần phụ thuộc đầu tiên là dành cho chính thư viện Retrofit2 và phần phụ thuộc thứ hai là dành cho bộ chuyển đổi vô hướng Retrofit (Retrofit scalar converter). Retrofit2 là phiên bản cập nhật của thư viện Retrofit. Bộ chuyển đổi vô hướng này cho phép Retrofit trả về kết quả JSON dưới dạng String. JSON là định dạng lưu trữ và truyền dữ liệu giữa ứng dụng và máy chủ. Bạn sẽ tìm hiểu về JSON trong phần sau.

  1. Nhấp vào Đồng bộ hoá ngay (Sync Now) để xây dựng lại dự án với các phần phụ thuộc mới.

6. Kết nối với Internet

Bạn sử dụng thư viện Retrofit để giao tiếp với dịch vụ web sao Hoả và hiện phản hồi JSON thô dưới dạng String. Phần giữ chỗ Text sẽ cho thấy chuỗi phản hồi JSON được trả về hoặc thông báo lỗi kết nối.

Retrofit tạo một API mạng cho ứng dụng dựa trên nội dung từ dịch vụ web. Retrofit tìm nạp dữ liệu từ dịch vụ web và định tuyến dữ liệu này thông qua một thư viện chuyển đổi riêng biệt biết cách giải mã dữ liệu và trả lại dữ liệu dưới dạng các đối tượng như String. Retrofit có tính năng hỗ trợ tích hợp cho các định dạng dữ liệu phổ biến như XML và JSON. Cuối cùng, Retrofit sẽ tạo mã để gọi và sử dụng dịch vụ này cho bạn, bao gồm cả các thông tin quan trọng như chạy yêu cầu trên các luồng ở chế độ nền.

cdc907b8b666b47e.png

Trong nhiệm vụ này, bạn thêm một lớp dữ liệu vào dự án Mars PhotosViewModel của bạn dùng để giao tiếp với dịch vụ web. Bạn sẽ triển khai API của dịch vụ Retrofit theo các bước sau đây:

  • Tạo nguồn dữ liệu, lớp MarsApiService.
  • Tạo đối tượng Retrofit với URL cơ sở và nhà máy (factory) chuyển đổi để chuyển đổi chuỗi.
  • Tạo giao diện giải thích cách Retrofit giao tiếp với máy chủ web.
  • Tạo dịch vụ Retrofit và hiện thực thể của dịch vụ API cho phần còn lại của ứng dụng.

Triển khai các bước trên:

  1. Nhấp chuột phải vào gói com.example.marsphotos trong ngăn dự án Android rồi chọn New > Package (Mới > Gói).
  2. Trong cửa sổ bật lên, hãy thêm network vào cuối tên gói được đề xuất.
  3. Tạo một tệp Kotlin mới trong gói mới. Đặt tên tệp này là MarsApiService.
  4. Mở network/MarsApiService.kt.
  5. Thêm hằng số sau đây cho URL cơ sở dành cho dịch vụ web.
private const val BASE_URL =
   "https://android-kotlin-fun-mars-server.appspot.com"
  1. Thêm trình tạo Retrofit ngay bên dưới hằng số đó để xây dựng và tạo đối tượng Retrofit.
import retrofit2.Retrofit

private val retrofit = Retrofit.Builder()

Retrofit cần URI cơ sở dành cho dịch vụ web và nhà máy chuyển đổi để xây dựng API dịch vụ web. Bộ chuyển đổi sẽ cho Retrofit biết cần làm gì với dữ liệu nhận được qua dịch vụ web. Trong trường hợp này, bạn muốn Retrofit tìm nạp phản hồi JSON qua dịch vụ web và trả về phản hồi đó dưới dạng String. Retrofit có một ScalarsConverter hỗ trợ các chuỗi và kiểu nguyên gốc khác.

  1. Gọi addConverterFactory() trên trình tạo bằng một thực thể của ScalarsConverterFactory.
import retrofit2.converter.scalars.ScalarsConverterFactory

private val retrofit = Retrofit.Builder()
   .addConverterFactory(ScalarsConverterFactory.create())
  1. Thêm URL cơ sở cho dịch vụ web bằng phương thức baseUrl().
  2. Gọi build() để tạo đối tượng Retrofit.
private val retrofit = Retrofit.Builder()
   .addConverterFactory(ScalarsConverterFactory.create())
   .baseUrl(BASE_URL)
   .build()
  1. Bên dưới lệnh gọi tới trình tạo Retrofit, hãy định nghĩa một giao diện có tên là MarsApiService. Giao diện này xác định cách Retrofit giao tiếp với máy chủ web thông qua các yêu cầu HTTP.
interface MarsApiService {
}
  1. Thêm một hàm có tên là getPhotos() vào giao diện MarsApiService để nhận chuỗi phản hồi từ dịch vụ web.
interface MarsApiService {
    fun getPhotos()
}
  1. Sử dụng chú thích @GET để cho Retrofit biết rằng đây là yêu cầu GET và chỉ định điểm cuối cho phương thức dịch vụ web đó. Trong trường hợp này, điểm cuối là photos. Như đã đề cập trong nhiệm vụ trước, bạn sẽ sử dụng điểm cuối /photos trong lớp học lập trình này.
import retrofit2.http.GET

interface MarsApiService {
    @GET("photos")
    fun getPhotos()
}

Khi phương thức getPhotos() được gọi, Retrofit sẽ thêm điểm cuối photos vào URL cơ sở (mà bạn đã xác định trong trình tạo Retrofit) được dùng để bắt đầu yêu cầu.

  1. Thêm loại dữ liệu trả về của hàm vào String.
interface MarsApiService {
    @GET("photos")
    fun getPhotos(): String
}

Khai báo đối tượng

Trong Kotlin, phần khai báo đối tượng được dùng để khai báo các đối tượng singleton. Mẫu singleton đảm bảo rằng chỉ tạo một và chỉ một thực thể của đối tượng, sẽ có một điểm truy cập toàn cục đến đối tượng đó. Hoạt động khởi tạo đối tượng sẽ an toàn cho luồng và được thực hiện ở lần truy cập đầu tiên.

Sau đây là ví dụ về nội dung khai báo cũng như quyền truy cập của đối tượng. Nội dung khai báo đối tượng luôn có tên đứng sau từ khoá object.

Ví dụ:

// Example for Object declaration, do not copy over

object SampleDataProvider {
    fun register(provider: SampleProvider) {
        // ...
    }
​
    // ...
}

// To refer to the object, use its name directly.
SampleDataProvider.register(...)

Lệnh gọi hàm create() trên đối tượng Retrofit gây tốn kém về bộ nhớ, tốc độ và hiệu suất. Ứng dụng chỉ cần duy nhất một thực thể của dịch vụ API Retrofit, nên bạn có thể hiển thị dịch vụ này cho phần còn lại của ứng dụng bằng cách khai báo đối tượng.

  1. Bên ngoài phần khai báo giao diện MarsApiService, hãy khai báo một đối tượng công khai (public) có tên là MarsApi để khởi chạy dịch vụ Retrofit. Đối tượng này là đối tượng singleton công khai mà phần còn lại của ứng dụng có thể truy cập.
object MarsApi {}
  1. Bên trong phần khai báo đối tượng MarsApi, thêm một thuộc tính đối tượng Retrofit khởi chạy từng phần (lazy initialize) với tên gọi là retrofitService thuộc kiểu MarsApiService. Bạn thực hiện việc khởi chạy từng phần này để đảm bảo khởi chạy thành công trong lần sử dụng đầu tiên. Bỏ qua lỗi mà bạn khắc phục trong các bước tiếp theo.
object MarsApi {
    val retrofitService : MarsApiService by lazy {}
}
  1. Khởi chạy biến retrofitService bằng phương thức retrofit.create() với giao diện MarsApiService.
object MarsApi {
    val retrofitService : MarsApiService by lazy {
       retrofit.create(MarsApiService::class.java)
    }
}

Đã thiết lập xong Retrofit! Mỗi khi ứng dụng của bạn gọi MarsApi.retrofitService, phương thức gọi sẽ truy cập vào chính đối tượng singleton Retrofit triển khai MarsApiService được tạo trong lần truy cập đầu tiên. Trong nhiệm vụ tiếp theo, bạn sẽ dùng đối tượng Retrofit mà mình đã triển khai.

Gọi dịch vụ web trong MarsViewModel

Ở bước này, bạn sẽ triển khai phương thức getMarsPhotos() gọi dịch vụ REST, sau đó xử lý chuỗi JSON được trả về.

ViewModelScope

viewModelScope là phạm vi coroutine tích hợp sẵn được khai báo cho mỗi ViewModel trong ứng dụng. Mọi coroutine chạy trong phạm vi này sẽ tự động bị huỷ nếu ViewModel bị xoá.

Bạn có thể sử dụng viewModelScope để chạy coroutine và thực hiện yêu cầu dịch vụ web trong nền. Vì viewModelScope thuộc ViewModel, nên yêu cầu vẫn tiếp tục ngay cả khi ứng dụng có sự thay đổi cấu hình.

  1. Trong tệp MarsApiService.kt, hãy đặt getPhotos() làm hàm tạm ngưng để hàm đó không đồng bộ và không chặn luồng gọi. Bạn gọi hàm này từ bên trong viewModelScope.
@GET("photos")
suspend fun getPhotos(): String
  1. Mở tệp ui/screens/MarsViewModel.kt. Cuộn xuống phương thức getMarsPhotos(). Xoá dòng thiết lập phản hồi trạng thái thành "Set the Mars API Response here!" để phương thức getMarsPhotos() bị trống.
private fun getMarsPhotos() {}
  1. Bên trong getMarsPhotos(), hãy mở coroutine bằng viewModelScope.launch.
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

private fun getMarsPhotos() {
    viewModelScope.launch {}
}
  1. Bên trong viewModelScope, hãy sử dụng đối tượng singleton MarsApi, để gọi phương thức getPhotos() qua giao diện retrofitService. Lưu phản hồi được trả về trong một val có tên là listResult.
import com.example.marsphotos.network.MarsApi

viewModelScope.launch {
    val listResult = MarsApi.retrofitService.getPhotos()
}
  1. Gán kết quả vừa nhận được qua máy chủ phụ trợ cho marsUiState marsUiState là đối tượng trạng thái có thể thay đổi biểu thị trạng thái của yêu cầu web gần đây nhất.
val listResult = MarsApi.retrofitService.getPhotos()
marsUiState = listResult
  1. Chạy ứng dụng. Để ý rằng ứng dụng đóng ngay lập tức, ứng dụng có thể (hoặc không) hiện cửa sổ bật lên báo lỗi. Đây là sự cố ứng dụng.
  2. Nhấp vào thẻ Logcat trong Android Studio rồi ghi chú lỗi trong nhật ký, bắt đầu bằng một dòng như thế này: "------- beginning of crash"
    --------- beginning of crash
22803-22865/com.example.android.marsphotos E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
    Process: com.example.android.marsphotos, PID: 22803
    java.lang.SecurityException: Permission denied (missing INTERNET permission?)
...

Thông báo lỗi này cho biết có thể ứng dụng bị thiếu các quyền INTERNET. Nhiệm vụ tiếp theo sẽ hướng dẫn bạn cách thêm quyền truy cập Internet cho ứng dụng và giải quyết vấn đề này.

7. Thêm quyền Internet và xử lý ngoại lệ

Quyền trên Android

Mục đích của các quyền (permission) trên Android là bảo vệ quyền riêng tư của người dùng Android. Ứng dụng Android phải khai báo hoặc yêu cầu cấp quyền nếu muốn truy cập vào dữ liệu nhạy cảm của người dùng, chẳng hạn như danh bạ, nhật ký cuộc gọi và một số tính năng nhất định của hệ thống, chẳng hạn như máy ảnh hoặc Internet.

Để có thể truy cập Internet, ứng dụng của bạn cần có quyền INTERNET. Kết nối Internet dẫn đến nhiều mối lo ngại về bảo mật, do đó, các ứng dụng đều không có kết nối Internet theo mặc định. Bạn cần phải khai báo rõ ràng rằng ứng dụng cần truy cập Internet. Việc khai báo này được xem là một quyền thông thường. Để tìm hiểu thêm về các loại quyền trên Android, vui lòng tham khảo bài viết Các quyền trên Android.

Ở bước này, ứng dụng của bạn khai báo (các) quyền mà ứng dụng cần đến bằng cách đưa các thẻ <uses-permission> vào tệp AndroidManifest.xml.

  1. Mở manifests/AndroidManifest.xml. Thêm dòng này ngay trước thẻ <application>:
<uses-permission android:name="android.permission.INTERNET" />
  1. Biên dịch và chạy lại ứng dụng.

Nếu có kết nối Internet đang hoạt động, bạn sẽ thấy văn bản JSON chứa dữ liệu liên quan đến các ảnh sao Hoả. Hãy quan sát cách lặp lại của idimg_src đối với mỗi bản ghi hình ảnh. Bạn có thể tìm hiểu thêm về định dạng JSON ở phần sau của lớp học lập trình này.

7491daa719616213.png

  1. Nhấn vào nút Back (Quay lại) trong thiết bị hoặc trình mô phỏng để đóng ứng dụng.

Xử lý ngoại lệ

Mã của bạn bị lỗi. Hãy thực hiện các bước sau để xem mã:

  1. Đặt thiết bị hoặc trình mô phỏng của bạn vào Chế độ trên máy bay (Airplane Mode) để mô phỏng lỗi kết nối mạng.
  2. Mở lại ứng dụng trong trình đơn Gần đây (Recents) hoặc chạy ứng dụng trong Android Studio.
  3. Nhấp vào thẻ Logcat trong Android Studio và để ý đến ngoại lệ nghiêm trọng trong nhật ký như sau:
3302-3302/com.example.android.marsphotos E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.android.marsphotos, PID: 3302

Thông báo lỗi này cho biết ứng dụng đã cố gắng kết nối nhưng hết thời gian chờ. Những ngoại lệ như thế này rất phổ biến trong thời gian thực. Không giống như vấn đề về quyền, dù không thể khắc phục nhưng bạn có thể xử lý lỗi này. Trong bước tiếp theo, bạn sẽ tìm hiểu cách xử lý các ngoại lệ như vậy.

Ngoại lệ

Ngoại lệ (Exception) là các lỗi có thể xảy ra trong thời gian chạy (không phải thời gian biên dịch) và làm ứng dụng chấm dứt đột ngột mà không thông báo cho người dùng. Tình trạng này có thể đem lại trải nghiệm không tốt cho người dùng. Xử lý ngoại lệ là một cơ chế giúp bạn ngăn chặn tình trạng ứng dụng chấm dứt đột ngột và xử lý tình huống theo cách thân thiện với người dùng.

Lý do cho các ngoại lệ có thể đơn giản như phép chia cho không hoặc một lỗi với kết nối mạng. Các ngoại lệ này tương tự như IllegalArgumentException mà lớp học lập trình trước đó thảo luận.

Sau đây là ví dụ về các vấn đề có thể xảy ra khi kết nối với máy chủ:

  • URL hoặc URI sử dụng trong API không chính xác.
  • Máy chủ không truy cập được và ứng dụng không kết nối được với máy chủ đó.
  • Vấn đề về độ trễ mạng.
  • Kết nối Internet kém hoặc không có kết nối Internet trên thiết bị.

Không thể xử lý các ngoại lệ này trong thời gian biên dịch, nhưng bạn có thể dùng khối try-catch để xử lý ngoại lệ trong thời gian chạy. Để tìm hiểu thêm, hãy tham khảo bài viết Ngoại lệ.

Cú pháp mẫu cho khối try-catch

try {
    // some code that can cause an exception.
}
catch (e: SomeException) {
    // handle the exception to avoid abrupt termination.
}

Trong khối try, bạn thêm mã mà bạn dự đoán là sẽ xảy ra ngoại lệ. Trong ứng dụng của bạn, đây sẽ là một lệnh gọi mạng. Trong khối catch, bạn cần triển khai mã ngăn chặn hiện tượng dừng ứng dụng đột ngột. Nếu có một ngoại lệ thì khối catch sẽ được thực thi để khắc phục lỗi thay vì dừng ứng dụng đột ngột.

  1. Trong getMarsPhotos(), bên trong khối launch, hãy thêm khối try xung quanh lệnh gọi MarsApi để xử lý các ngoại lệ.
  2. Thêm khối catch sau khối try.
import java.io.IOException

viewModelScope.launch {
   try {
       val listResult = MarsApi.retrofitService.getPhotos()
       marsUiState = listResult
   } catch (e: IOException) {

   }
}
  1. Chạy lại ứng dụng. Lưu ý rằng lần này ứng dụng không gặp sự cố.

Thêm giao diện người dùng trạng thái

Trong lớp MarsViewModel, trạng thái của yêu cầu web gần đây nhất (marsUiState) được lưu dưới dạng đối tượng trạng thái có thể thay đổi. Tuy nhiên, lớp này không thể lưu trạng thái khác: tải, thành công và không thành công.

  • Trạng thái Đang tải cho biết ứng dụng đang chờ dữ liệu.
  • Trạng thái Thành công cho biết dữ liệu được truy xuất thành công từ dịch vụ web.
  • Trạng thái Lỗi cho biết lỗi mạng hoặc lỗi kết nối.

Để thể hiện ba trạng thái này trong ứng dụng, bạn sử dụng giao diện kín. sealed interface giúp bạn dễ dàng quản lý trạng thái bằng cách giới hạn các giá trị có thể có. Trong ứng dụng Mars Photos, bạn giới hạn phản hồi web của marsUiState ở ba trạng thái (đối tượng lớp dữ liệu): tải, thành công và lỗi, có dạng như mã sau đây:

// No need to copy over
sealed interface MarsUiState {
   data class Success : MarsUiState
   data class Loading : MarsUiState
   data class Error : MarsUiState
}

Trong đoạn mã trên, trong trường hợp phản hồi thành công, bạn sẽ nhận được thông tin về ảnh sao Hoả từ máy chủ. Để lưu trữ dữ liệu, hãy thêm một tham số hàm khởi tạo vào lớp dữ liệu Success.

Trong trường hợp trạng thái LoadingError, bạn không cần thiết lập dữ liệu mới và tạo đối tượng mới; bạn chỉ đang trả về phản hồi trên web. Thay đổi lớp data thành Object để tạo các đối tượng cho phản hồi trên web.

  1. Mở tệp ui/MarsViewModel.kt. Sau các câu lệnh nhập, hãy thêm giao diện kín MarsUiState. Thao tác này giúp các giá trị mà đối tượng MarsUiState có thể có trở nên đầy đủ.
sealed interface MarsUiState {
    data class Success(val photos: String) : MarsUiState
    object Error : MarsUiState
    object Loading : MarsUiState
}
  1. Bên trong lớp MarsViewModel, hãy cập nhật định nghĩa marsUiState. Thay đổi kiểu thành MarsUiStateMarsUiState.Loading làm giá trị mặc định. Đặt phương thức setter ở chế độ riêng tư để bảo vệ lượt ghi vào marsUiState.
var marsUiState: MarsUiState by mutableStateOf(MarsUiState.Loading)
  private set
  1. Cuộn xuống phương thức getMarsPhotos(). Cập nhật giá trị marsUiState thành MarsUiState.Success và truyền listResult.
val listResult = MarsApi.retrofitService.getPhotos()
marsUiState = MarsUiState.Success(listResult)
  1. Bên trong khối catch, hãy xử lý phản hồi lỗi. Đặt MarsUiState thành Error.
catch (e: IOException) {
   marsUiState = MarsUiState.Error
}
  1. Bạn có thể đưa lệnh gán marsUiState ra khỏi khối try-catch. Hàm hoàn chỉnh sẽ có dạng như mã sau:
private fun getMarsPhotos() {
   viewModelScope.launch {
       marsUiState = try {
           val listResult = MarsApi.retrofitService.getPhotos()
           MarsUiState.Success(listResult)
       } catch (e: IOException) {
           MarsUiState.Error
       }
   }
}
  1. Trong tệp screens/HomeScreen.kt, thêm biểu thức when vào marsUiState. Nếu marsUiStateMarsUiState.Success, hãy gọi ResultScreen và truyền vào marsUiState.photos. Tạm thời bỏ qua lỗi.
import androidx.compose.foundation.layout.fillMaxWidth

fun HomeScreen(
   marsUiState: MarsUiState,
   modifier: Modifier = Modifier
) {
    when (marsUiState) {
        is MarsUiState.Success -> ResultScreen(
            marsUiState.photos, modifier = modifier.fillMaxWidth()
        )
    }
}
  1. Trong khối when, hãy thêm các bước kiểm tra cho MarsUiState.LoadingMarsUiState.Error. Ứng dụng sẽ hiển thị các thành phần kết hợp LoadingScreen, ResultScreenErrorScreen mà bạn triển khai sau này.
import androidx.compose.foundation.layout.fillMaxSize

fun HomeScreen(
   marsUiState: MarsUiState,
   modifier: Modifier = Modifier
) {
    when (marsUiState) {
        is MarsUiState.Loading -> LoadingScreen(modifier = modifier.fillMaxSize())
        is MarsUiState.Success -> ResultScreen(
            marsUiState.photos, modifier = modifier.fillMaxWidth()
        )

        is MarsUiState.Error -> ErrorScreen( modifier = modifier.fillMaxSize())
    }
}
  1. Mở res/drawable/loading_animation.xml. Thành phần có thể vẽ này là một hiệu ứng động làm cho hình ảnh có thể vẽ (loading_img.xml) xoay xung quanh điểm giữa. (Bạn sẽ không thấy ảnh động trong bản xem trước).

180609c4f6f00984.png

  1. Trong tệp screens/HomeScreen.kt, bên dưới thành phần kết hợp HomeScreen, hãy thêm hàm có khả năng kết hợp LoadingScreen sau đây để hiển thị ảnh động về trạng thái đang tải. Tài nguyên có thể vẽ loading_img được đưa vào mã khởi đầu.
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.Image

@Composable
fun LoadingScreen(modifier: Modifier = Modifier) {
    Image(
        modifier = modifier.size(200.dp),
        painter = painterResource(R.drawable.loading_img),
        contentDescription = stringResource(R.string.loading)
    )
}
  1. Bên dưới thành phần kết hợp LoadingScreen, thêm hàm có khả năng kết hợp ErrorScreen sau đây để ứng dụng có thể hiển thị thông báo lỗi.
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding

@Composable
fun ErrorScreen(modifier: Modifier = Modifier) {
    Column(
        modifier = modifier,
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Image(
            painter = painterResource(id = R.drawable.ic_connection_error), contentDescription = ""
        )
        Text(text = stringResource(R.string.loading_failed), modifier = Modifier.padding(16.dp))
    }
}
  1. Chạy lại ứng dụng khi đang bật Chế độ trên máy bay. Lần này ứng dụng không đóng đột ngột và xuất hiện thông báo lỗi sau đây:

ae10ceb8ed7d0105.png

  1. Tắt Chế độ trên máy bay trên điện thoại hoặc trình mô phỏng. Chạy và kiểm thử ứng dụng, đảm bảo rằng mọi thứ đều hoạt động đúng cách và bạn thấy được chuỗi JSON.

Điện thoại hiển thị ảnh động về trạng thái đang tải

Màn hình điện thoại hiển thị danh sách đối tượng JSON

8. Phân tích cú pháp phản hồi JSON bằng kotlinx.serialization

JSON

Dữ liệu yêu cầu thường được định dạng theo một trong những định dạng dữ liệu phổ biến như XML hoặc JSON. Mỗi lệnh gọi đều trả về dữ liệu có cấu trúc và ứng dụng của bạn cần biết cấu trúc đó là gì để đọc được dữ liệu trong phản hồi.

Ví dụ: trong ứng dụng này, bạn đang truy xuất dữ liệu qua máy chủ https:// android-kotlin-fun-mars-server.appspot.com/photos. Khi nhập URL này vào trình duyệt, bạn sẽ thấy danh sách mã nhận dạng và URL ảnh bề mặt sao Hoả ở định dạng JSON!

Cấu trúc của phản hồi JSON mẫu

hiển thị các giá trị khoá và đối tượng JSON

Cấu trúc của phản hồi JSON có các đặc điểm sau đây:

  • Phản hồi JSON là một mảng (array), được biểu thị bằng các dấu ngoặc vuông. Mảng này chứa các đối tượng JSON.
  • Đối tượng JSON được đặt trong dấu ngoặc nhọn.
  • Mỗi đối tượng JSON chứa một tập hợp cặp khoá-giá trị được phân tách bằng dấu phẩy.
  • Khoá và giá trị trong một cặp được phân tách bằng dấu hai chấm.
  • Tên được đặt trong dấu ngoặc kép.
  • Giá trị có thể là số, chuỗi (string), boolean, mảng (array), đối tượng (đối tượng JSON) hoặc giá trị rỗng (null).

Ví dụ: img_src là một URL và đó cũng là một chuỗi. Khi dán URL vào một trình duyệt web, bạn sẽ thấy hình ảnh bề mặt sao Hoả.

8c84dfca7825481.png

Lúc này, trong ứng dụng, bạn sẽ nhận được phản hồi JSON qua dịch vụ web sao Hoả, đó là một khởi đầu tuyệt vời. Tuy nhiên, bạn thực sự cần hiển thị hình ảnh các đối tượng Kotlin, chứ không phải hình ảnh một chuỗi JSON lớn. Quá trình này được gọi là huỷ chuyển đổi tuần tự.

Chuyển đổi tuần tự là quá trình chuyển đổi dữ liệu mà ứng dụng dùng sang định dạng có thể chuyển được qua mạng. Khác với quá trình chuyển đổi tuần tự, quá trình huỷ chuyển đổi tuần tự sẽ đọc dữ liệu từ nguồn bên ngoài (như máy chủ) và chuyển đổi dữ liệu đó thành một đối tượng thời gian chạy. Đây là hai thành phần thiết yếu của hầu hết các ứng dụng trao đổi dữ liệu qua mạng.

kotlinx.serialization cung cấp các nhóm thư viện chuyển đổi chuỗi JSON thành các đối tượng Kotlin. Có một thư viện bên thứ ba do cộng đồng phát triển hoạt động với Retrofit, đó là Trình chuyển đổi tuần tự Kotlin.

Trong nhiệm vụ này, bạn sử dụng thư viện kotlinx.serialization để phân tích cú pháp phản hồi JSON qua dịch vụ web thành các đối tượng Kotlin hữu ích biểu thị ảnh sao Hoả. Bạn thay đổi ứng dụng để hiện số lượng ảnh sao Hoả được trả về (thay vì cho thấy JSON thô).

Thêm phần phụ thuộc thư viện kotlinx.serialization

  1. Mở build.gradle.kts (Module :app).
  2. Trong khối plugins, hãy thêm trình bổ trợ kotlinx serialization.
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.10"
  1. Trong mục dependencies, hãy thêm mã sau đây để thêm phần phụ thuộc kotlinx.serialization vào. Phần phụ thuộc này sẽ cung cấp tính năng chuyển đổi tuần tự JSON cho các dự án Kotlin.
// Kotlin serialization
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
  1. Xác định vị trí các dòng dành cho trình chuyển đổi vô hướng Retrofit trong khối dependencies và thay đổi trình này để sử dụng kotlinx-serialization-converter:

Thay thế mã dưới đây

// Retrofit with scalar Converter
implementation("com.squareup.retrofit2:converter-scalars:2.9.0")

bằng mã sau

// Retrofit with Kotlin serialization Converter

implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
implementation("com.squareup.okhttp3:okhttp:4.11.0")
  1. Nhấp vào Đồng bộ hoá ngay (Sync Now) để xây dựng lại dự án với các phần phụ thuộc mới.

Triển khai lớp dữ liệu Mars Photo

Mục mẫu của phản hồi JSON mà bạn nhận được từ dịch vụ web sẽ tương tự với những gì bạn đã thấy trước đó và có dạng như sau:

[
    {
        "id":"424906",
        "img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631300305227E03_DXXX.jpg"
    },
...]

Trong ví dụ trên, hãy lưu ý rằng mỗi mục ảnh sao Hoả có các cặp giá trị và khoá JSON như sau:

  • id: mã nhận dạng thuộc tính ở dạng chuỗi. Vì được đặt trong dấu ngoặc kép (" "), nên thuộc tính này thuộc loại String chứ không phải Integer.
  • img_src: URL của hình ảnh ở dạng chuỗi.

kotlinx.serialization phân tích cú pháp dữ liệu JSON này và chuyển đổi thành các đối tượng Kotlin. Để làm việc này, kotlinx.serialization cần có một lớp dữ liệu Kotlin để lưu trữ các kết quả đã phân tích cú pháp. Ở bước này, bạn tạo lớp dữ liệu MarsPhoto.

  1. Nhấp chuột phải vào gói network (mạng) rồi chọn New > Kotlin File/Class (Mới > Lớp/tệp Kotlin).
  2. Trong cửa sổ này, hãy chọn Class (Lớp) rồi nhập MarsPhoto làm tên của lớp đó. Thao tác này sẽ tạo một tệp mới có tên là MarsPhoto.kt trong gói network.
  3. Đặt MarsPhoto làm lớp dữ liệu bằng cách thêm từ khoá data trước phần định nghĩa lớp.
  4. Thay đổi dấu ngoặc nhọn {} thành dấu ngoặc đơn (). Sự thay đổi này sẽ gây ra lỗi vì các lớp dữ liệu phải có ít nhất một thuộc tính được xác định.
data class MarsPhoto()
  1. Thêm các thuộc tính sau vào phần định nghĩa lớp MarsPhoto.
data class MarsPhoto(
    val id: String,  val img_src: String
)
  1. Để lớp MarsPhoto có thể chuyển đổi tuần tự, hãy dùng chú thích @Serializable cho lớp này.
import kotlinx.serialization.Serializable

@Serializable
data class MarsPhoto(
    val id: String,  val img_src: String
)

Hãy lưu ý rằng mỗi biến trong lớp MarsPhoto tương ứng với một tên khoá trong đối tượng JSON. Để khớp với các kiểu trong phản hồi JSON cụ thể, bạn sử dụng đối tượng String cho mọi giá trị.

Khi phân tích cú pháp JSON, kotlinx serialization sẽ so khớp các khoá theo tên rồi điền thông tin thích hợp vào đối tượng dữ liệu.

Chú thích @SerialName

Đôi khi tên khoá trong phản hồi JSON có thể làm cho các thuộc tính Kotlin nhầm lẫn hoặc có thể không khớp với kiểu mã hoá đề xuất. Ví dụ: trong tệp JSON, khoá img_src sử dụng dấu gạch dưới, trong khi quy ước Kotlin cho các thuộc tính sử dụng chữ hoa và chữ thường (quy tắc viết hoa kiểu lạc đà).

Để sử dụng tên biến trong lớp dữ liệu khác với tên khoá trong phản hồi JSON, hãy sử dụng chú thích @SerialName. Trong ví dụ dưới đây, tên của biến trong lớp dữ liệu là imgSrc. Bạn có thể liên kết biến này với thuộc tính JSON img_src bằng @SerialName(value = "img_src").

  1. Thay thế dòng dành cho khoá img_src bằng dòng dưới đây.
import kotlinx.serialization.SerialName

@SerialName(value = "img_src")
val imgSrc: String

Cập nhật MarsApiService và MarsViewModel

Trong nhiệm vụ này, bạn sẽ dùng trình chuyển đổi kotlinx.serialization để chuyển đổi đối tượng JSON thành đối tượng Kotlin.

  1. Mở network/MarsApiService.kt.
  2. Lưu ý những lỗi tham chiếu chưa được giải quyết cho ScalarsConverterFactory. Những lỗi này là do có sự thay đổi về phần phụ thuộc Retrofit trong phần trước.
  3. Xoá dữ liệu đã nhập cho ScalarConverterFactory. Bạn khắc phục lỗi còn lại vào lúc khác.

Xoá:

import retrofit2.converter.scalars.ScalarsConverterFactory
  1. Trong phần khai báo đối tượng retrofit, hãy thay đổi trình tạo Retrofit để dùng kotlinx.serialization thay vì ScalarConverterFactory.
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json
import okhttp3.MediaType

private val retrofit = Retrofit.Builder()
        .addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
        .baseUrl(BASE_URL)
        .build()

Giờ đây khi đã có kotlinx.serialization, bạn có thể yêu cầu Retrofit trả về danh sách đối tượng MarsPhoto qua mảng JSON thay vì trả về một chuỗi JSON.

  1. Cập nhật giao diện MarsApiService để Retrofit trả về danh sách đối tượng MarsPhoto thay vì trả về một String.
interface MarsApiService {
    @GET("photos")
    suspend fun getPhotos(): List<MarsPhoto>
}
  1. Thực hiện các thay đổi tương tự đối với viewModel. Mở MarsViewModel.kt rồi cuộn xuống phương thức getMarsPhotos().

Trong phương thức getMarsPhotos(), listResultList<MarsPhoto> chứ không phải là String nữa. Kích thước của danh sách đó là số lượng ảnh đã nhận và phân tích cú pháp.

  1. Để in số lượng ảnh đã truy xuất, hãy cập nhật marsUiState như sau:
val listResult = MarsApi.retrofitService.getPhotos()
marsUiState = MarsUiState.Success(
   "Success: ${listResult.size} Mars photos retrieved"
)
  1. Đảm bảo rằng bạn đã tắt Chế độ trên máy bay trên trình mô phỏng hoặc thiết bị. Biên dịch và chạy ứng dụng.

Lần này thông báo phải cho thấy số lượng thuộc tính được trả về qua dịch vụ web chứ không phải là một chuỗi JSON lớn:

fb569daf7348206a.png

9. Mã giải pháp

Để tải mã này xuống khi lớp học lập trình đã kết thúc, bạn có thể sử dụng các lệnh git sau:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-mars-photos.git
$ cd basic-android-kotlin-compose-training-mars-photos
$ git checkout repo-starter

Ngoài ra, bạn có thể tải kho lưu trữ xuống dưới dạng tệp zip rồi giải nén và mở trong Android Studio.

Nếu bạn muốn tham khảo mã giải pháp cho lớp học lập trình này, hãy xem trên GitHub.

10. Tóm tắt

Dịch vụ web REST

  • Dịch vụ web là chức năng dựa trên phần mềm được cung cấp qua Internet nhằm giúp ứng dụng của bạn đưa ra yêu cầu và nhận về dữ liệu.
  • Các dịch vụ web phổ biến sử dụng kiến trúc REST. Các dịch vụ web cung cấp kiến trúc REST được gọi là dịch vụ RESTful. Các dịch vụ web RESTful được xây dựng bằng các giao thức và thành phần web tiêu chuẩn.
  • Thông qua URI, bạn sẽ đưa ra yêu cầu cho một dịch vụ web REST theo cách được chuẩn hoá.
  • Để sử dụng dịch vụ web, ứng dụng phải thiết lập một kết nối mạng và giao tiếp với dịch vụ. Sau đó, ứng dụng phải nhận và phân tích cú pháp dữ liệu phản hồi thành định dạng mà ứng dụng sử dụng được.
  • Thư viện Retrofit là một thư viện ứng dụng khách (client library) cho phép ứng dụng của bạn đưa ra yêu cầu đến một dịch vụ web REST.
  • Sử dụng các bộ chuyển đổi để cho Retrofit biết cần làm gì với dữ liệu mà ứng dụng gửi đến dịch vụ web và nhận về từ dịch vụ web. Ví dụ: ScalarsConverter coi dữ liệu dịch vụ web là String hoặc một kiểu dữ liệu gốc khác.
  • Để cho phép ứng dụng tạo kết nối Internet, hãy thêm quyền "android.permission.INTERNET" vào tệp kê khai Android.
  • Quá trình khởi tạo từng phần sẽ uỷ quyền tạo đối tượng vào lần sử dụng đầu tiên. Phương thức này tạo tệp đối chiếu nhưng không tạo đối tượng. Khi đối tượng được truy cập lần đầu tiên, mục tham chiếu sẽ được tạo và sử dụng mỗi lần sau đó.

Phân tích cú pháp JSON

  • Phản hồi qua dịch vụ web thường có định dạng JSON, một định dạng phổ biến để trình bày dữ liệu có cấu trúc.
  • Mỗi đối tượng JSON là một tập hợp cặp khoá-giá trị.
  • Một tập hợp đối tượng JSON là một mảng JSON. Bạn nhận được một mảng JSON dưới dạng phản hồi qua một dịch vụ web.
  • Khoá trong cặp khoá-giá trị được đặt trong dấu ngoặc kép. Giá trị có thể là số hoặc chuỗi.
  • Trong Kotlin, các công cụ chuyển đổi tuần tự dữ liệu có sẵn trong một thành phần riêng biệt là kotlinx.serialization. kotlinx.serialization cung cấp các nhóm thư viện chuyển đổi chuỗi JSON thành các đối tượng Kotlin.
  • Có một thư viện Trình chuyển đổi tuần tự Kotlin do cộng đồng phát triển cho Retrofit, đó là retrofit2-kotlinx-serialization-converter
  • kotlinx.serialization so khớp các khoá trong phản hồi JSON với các thuộc tính trong đối tượng dữ liệu cùng tên.
  • Để sử dụng tên thuộc tính khác cho một khoá, chú thích thuộc tính đó bằng chú thích @SerialName và khoá JSON value.

11. Tìm hiểu thêm

Tài liệu dành cho nhà phát triển Android:

Tài liệu về Kotlin:

Tài liệu khác: