Android Kotlin Fundamentals 06.2: Coroutine và Phòng

Lớp học lập trình này nằm trong khóa học về Khái niệm cơ bản về Android Kotlin. Bạn sẽ nhận được nhiều giá trị nhất từ khóa học này nếu bạn làm việc qua các lớp học lập trình theo trình tự. Tất cả các lớp học lập trình trong khóa học đều có trên trang đích của các lớp học lập trình cơ bản về Android Kotlin.

Giới thiệu

Một trong những ưu tiên hàng đầu để tạo ra trải nghiệm người dùng hoàn hảo cho ứng dụng của bạn là đảm bảo giao diện người dùng luôn phản hồi và chạy trơn tru. Một cách để cải thiện hiệu suất giao diện người dùng là di chuyển các tác vụ chạy trong thời gian dài, chẳng hạn như hoạt động của cơ sở dữ liệu, vào nền.

Trong lớp học lập trình này, bạn triển khai phần giao diện người dùng của ứng dụng TrackMySleep chiến lược, sử dụng coroutine của Kotlin để thực hiện các thao tác với cơ sở dữ liệu ngoài chuỗi chính.

Kiến thức bạn cần có

Bạn cần thông thạo:

  • Xây dựng giao diện người dùng (UI) cơ bản bằng cách sử dụng hoạt động, mảnh, chế độ xem và trình xử lý lượt nhấp.
  • Di chuyển giữa các mảnh và sử dụng safeArgs để chuyển dữ liệu đơn giản giữa các mảnh.
  • Xem các mô hình, xem thông tin về nhà máy, phép biến đổi và LiveData.
  • Cách tạo cơ sở dữ liệu Room, tạo DAO và xác định các thực thể.
  • Sẽ rất hữu ích nếu bạn đã nắm rõ các khái niệm theo chuỗi và xử lý đa.

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

  • Cách các chuỗi cuộc trò chuyện hoạt động trong Android.
  • Cách sử dụng coroutine của Kotlin để di chuyển các thao tác cơ sở dữ liệu ra khỏi chuỗi chính.
  • Cách hiển thị dữ liệu được định dạng trong TextView.

Bạn sẽ thực hiện

  • Mở rộng ứng dụngTrackMySleepChất lượng để thu thập, lưu trữ và hiển thị dữ liệu trong và từ cơ sở dữ liệu.
  • Dùng coroutine để chạy các thao tác cơ sở dữ liệu chạy trong nền trong thời gian dài.
  • Sử dụng LiveData để kích hoạt thanh điều hướng và hiển thị thanh thông báo nhanh.
  • Sử dụng LiveData để bật và tắt các nút.

Trong lớp học lập trình này, bạn xây dựng mô hình chế độ xem, coroutine và phần hiển thị dữ liệu của ứng dụng TrackMySleepChất lượng.

Ứng dụng có hai màn hình, được biểu thị bằng các mảnh, như minh họa trong hình bên dưới.

Màn hình đầu tiên (hiển thị ở bên trái) có các nút để bắt đầu và dừng theo dõi. Màn hình hiển thị tất cả dữ liệu giấc ngủ của người dùng. Nút Xóa xóa vĩnh viễn tất cả dữ liệu mà ứng dụng đã thu thập cho người dùng.

Màn hình thứ hai hiển thị ở bên phải, cho biết mức chọn điểm xếp hạng chất lượng giấc ngủ. Trong ứng dụng, điểm xếp hạng được biểu thị bằng số. Để phát triển, ứng dụng sẽ hiển thị cả biểu tượng khuôn mặt và giá trị tương đương bằng số.

Luồng người dùng như sau:

  • Người dùng mở ứng dụng và hiển thị màn hình theo dõi giấc ngủ.
  • Người dùng nhấn vào nút Bắt đầu. Thao tác này sẽ ghi lại thời gian bắt đầu và hiển thị thời gian đó. Nút Bắt đầu đã tắt và nút Dừng.
  • Người dùng nhấn vào nút Dừng. Thao tác này sẽ ghi lại thời gian kết thúc và mở màn hình chất lượng giấc ngủ.
  • Người dùng chọn một biểu tượng chất lượng giấc ngủ. Màn hình sẽ đóng lại và màn hình theo dõi hiển thị thời gian ngủ và chất lượng giấc ngủ. Nút Dừng bị tắt và nút Bắt đầu đang bật. Ứng dụng đã sẵn sàng cho một đêm khác.
  • Nút Xóa được bật bất cứ khi nào có dữ liệu trong cơ sở dữ liệu. Khi người dùng nhấn vào nút Xóa, tất cả dữ liệu của họ sẽ bị xóa mà không cần truy cập lại – không có "Bạn có chắc chắn{5}quot; tin nhắn không.

Ứng dụng này dùng một cấu trúc đơn giản như trong bối cảnh của cấu trúc đầy đủ. Ứng dụng này chỉ sử dụng các thành phần sau:

  • Bộ điều khiển giao diện người dùng
  • Xem mô hình và LiveData
  • Cơ sở dữ liệu Phòng

Trong nhiệm vụ này, bạn sử dụng TextView để hiển thị dữ liệu theo dõi giấc ngủ được định dạng. (Đây chưa phải là giao diện cuối cùng. Bạn sẽ học được cách tốt hơn trong một lớp học lập trình khác.)

Bạn có thể tiếp tục dùng ứng dụng TrackMySleepChất lượng mà bạn đã xây dựng trong lớp học lập trình trước đó hoặc tải ứng dụng dành cho người mới tham gia lớp học lập trình này.

Bước 1: Tải xuống và chạy ứng dụng dành cho người mới bắt đầu

  1. Tải ứng dụng TrackMySleepquality-Coroutines-Starter xuống từ GitHub.
  2. Tạo và chạy ứng dụng. Ứng dụng này hiển thị giao diện người dùng cho mảnh SleepTrackerFragment nhưng không hiển thị dữ liệu. Các nút không phản hồi khi nhấn.

Bước 2: Kiểm tra mã

Mã bắt đầu cho lớp học lập trình này giống với mã giải pháp cho 6.1 Tạo lớp học lập trình cơ sở dữ liệu phòng.

  1. Mở tệp res/layout/activity_main.xml. Bố cục này chứa đoạn nav_host_fragment. Ngoài ra, hãy chú ý đến thẻ <merge>.

    Bạn có thể dùng thẻ merge để loại bỏ các bố cục thừa khi cần thêm các bố cục. Bạn nên sử dụng thẻ này. Một ví dụ về bố cục thừa sẽ là ConstraintLayout > LinearLayout > TextView, trong đó hệ thống có thể loại bỏ LinearLayout. Loại tối ưu hóa này có thể đơn giản hóa hệ phân cấp chế độ xem và cải thiện hiệu suất ứng dụng.
  2. Trong thư mục navigation, hãy mở navigation.xml. Bạn có thể thấy hai mảnh và các hành động điều hướng kết nối chúng.
  3. Trong thư mục layout, hãy nhấp đúp vào mảnh theo dõi giấc ngủ để xem bố cục XML của tệp đó. Hãy chú ý những điều sau:
  • Dữ liệu bố cục được bao bọc trong một phần tử <layout> để kích hoạt liên kết dữ liệu.
  • ConstraintLayout và các chế độ xem khác được sắp xếp bên trong phần tử <layout>.
  • Tệp này có thẻ phần giữ chỗ <data>.

Ứng dụng dành cho người mới bắt đầu cũng cung cấp kích thước, màu sắc và kiểu cho giao diện người dùng. Ứng dụng này chứa cơ sở dữ liệu Room, DAO và thực thể SleepNight. Nếu bạn chưa hoàn thành lớp học lập trình trước đó, hãy đảm bảo bạn tự khám phá các khía cạnh này của mã.

Bây giờ, bạn đã có cơ sở dữ liệu và giao diện người dùng, bạn cần thu thập dữ liệu, thêm dữ liệu vào cơ sở dữ liệu và hiển thị dữ liệu. Tất cả việc này được thực hiện trong mô hình chế độ xem. Mô hình chế độ xem trình theo dõi giấc ngủ sẽ xử lý các lượt nhấp vào nút, tương tác với cơ sở dữ liệu thông qua DAO và cung cấp dữ liệu cho giao diện người dùng qua LiveData. Tất cả các thao tác với cơ sở dữ liệu sẽ phải được chạy khỏi luồng giao diện người dùng chính và bạn sẽ thực hiện việc đó bằng cách sử dụng coroutine.

Bước 1: Thêm SleepTrackerViewModel

  1. Trong gói sleeptracker, hãy mở SleepTrackerViewModel.kt.
  2. Hãy kiểm tra lớp SleepTrackerViewModel được cung cấp cho bạn trong ứng dụng dành cho người mới bắt đầu và cũng hiển thị bên dưới. Xin lưu ý rằng lớp này sẽ mở rộng AndroidViewModel(). Lớp này giống với ViewModel, nhưng nhận bối cảnh ứng dụng dưới dạng một thông số và cung cấp lớp này dưới dạng một thuộc tính. Bạn sẽ cần các bộ lọc này sau.
class SleepTrackerViewModel(
       val database: SleepDatabaseDao,
       application: Application) : AndroidViewModel(application) {
}

Bước 2: Thêm SleepTrackerViewModelFactory

  1. Trong gói sleeptracker, hãy mở SleepTrackerViewModelFactory.kt.
  2. Kiểm tra mã mà bạn được cung cấp cho nhà máy, như hiển thị bên dưới:
class SleepTrackerViewModelFactory(
       private val dataSource: SleepDatabaseDao,
       private val application: Application) : ViewModelProvider.Factory {
   @Suppress("unchecked_cast")
   override fun <T : ViewModel?> create(modelClass: Class<T>): T {
       if (modelClass.isAssignableFrom(SleepTrackerViewModel::class.java)) {
           return SleepTrackerViewModel(dataSource, application) as T
       }
       throw IllegalArgumentException("Unknown ViewModel class")
   }
}

Hãy lưu ý những điều sau:

  • SleepTrackerViewModelFactory đã cung cấp nhận cùng một đối số với ViewModel và mở rộng ViewModelProvider.Factory.
  • Bên trong trạng thái ban đầu, mã này ghi đè create(). Thao tác này sẽ lấy bất kỳ loại lớp nào làm đối số và trả về ViewModel.
  • Trong phần nội dung của create(), mã này sẽ kiểm tra để đảm bảo rằng còn có một lớp SleepTrackerViewModel và nếu có, thì lớp này sẽ trả về một bản sao của lớp đó. Nếu không, mã sẽ gửi một ngoại lệ.

Bước 3: Cập nhật SleepTrackerFragment

  1. Trong SleepTrackerFragment, hãy lấy thông tin tham chiếu đến bối cảnh đăng ký. Đặt tham chiếu trong onCreateView(), bên dưới binding. Bạn cần tham chiếu đến ứng dụng mà mảnh này được đính kèm để chuyển vào nhà cung cấp mô hình chế độ xem ban đầu.

    Hàm Kotlin được gửi IllegalArgumentException nếu giá trịnull.
val application = requireNotNull(this.activity).application
  1. Bạn cần tham chiếu đến nguồn dữ liệu của mình qua tham chiếu đến DAO. Trong onCreateView(), trước return, hãy xác định dataSource. Để tham chiếu đến DAO của cơ sở dữ liệu, hãy sử dụng SleepDatabase.getInstance(application).sleepDatabaseDao.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
  1. Trong onCreateView(), trước return, hãy tạo một bản sao của viewModelFactory. Bạn cần chuyển thẻ dataSourceapplication.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
  1. Giờ thì bạn đã có nhà máy, hãy tham khảo SleepTrackerViewModel. Thông số SleepTrackerViewModel::class.java đề cập đến lớp Java của thời gian chạy của đối tượng này.
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)
  1. Mã hoàn tất của bạn sẽ có dạng như sau:
// Create an instance of the ViewModel Factory.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)

// Get a reference to the ViewModel associated with this fragment.
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)

Dưới đây là phương thức onCreateView() cho đến nay:

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {

        // Get a reference to the binding object and inflate the fragment views.
        val binding: FragmentSleepTrackerBinding = DataBindingUtil.inflate(
                inflater, R.layout.fragment_sleep_tracker, container, false)

        val application = requireNotNull(this.activity).application

        val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao

        val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)

        val sleepTrackerViewModel =
                ViewModelProviders.of(
                        this, viewModelFactory).get(SleepTrackerViewModel::class.java)

        return binding.root
    }

Bước 4: Thêm liên kết dữ liệu cho mô hình chế độ xem

Với ViewModel cơ bản, bạn cần hoàn tất việc thiết lập liên kết dữ liệu trong SleepTrackerFragment để kết nối ViewModel với giao diện người dùng.


Trong tệp bố cục fragment_sleep_tracker.xml:

  1. Bên trong khối <data>, hãy tạo một <variable> để tham chiếu đến lớp SleepTrackerViewModel.
<data>
   <variable
       name="sleepTrackerViewModel"
       type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>

Trong SleepTrackerFragment:

  1. Đặt hoạt động hiện tại làm chủ sở hữu vòng đời của liên kết. Thêm mã này vào trong phương thức onCreateView(), trước câu lệnh return:
binding.setLifecycleOwner(this)
  1. Chỉ định biến liên kết sleepTrackerViewModel cho sleepTrackerViewModel. Đặt mã này bên trong onCreateView(), bên dưới mã tạo SleepTrackerViewModel:
binding.sleepTrackerViewModel = sleepTrackerViewModel
  1. Bạn có thể sẽ thấy lỗi vì bạn phải tạo lại đối tượng liên kết. Dọn dẹp và tạo lại dự án để loại bỏ lỗi.
  2. Cuối cùng, như mọi khi, hãy đảm bảo mã của bạn được tạo và chạy mà không gặp lỗi.

Trong Kotlin, coroutine là cách xử lý hiệu quả các tác vụ chạy trong thời gian dài một cách trang nhã và hiệu quả. Coroutine Kotlin cho phép bạn chuyển đổi mã dựa trên lệnh gọi lại thành mã tuần tự. Mã được viết tuần tự thường dễ đọc hơn và thậm chí có thể dùng các tính năng bằng ngôn ngữ như ngoại lệ. Cuối cùng, coroutine và lệnh gọi lại cũng làm như vậy: chúng chờ cho đến khi có kết quả từ một tác vụ chạy trong thời gian dài và tiếp tục thực thi.

Coroutine có các thuộc tính sau:

  • Coroutine không đồng bộ và không chặn.
  • Coroutine sử dụng các hàm suspend để tạo mã không đồng bộ theo tuần tự.

Coroutine không đồng bộ.

Coroutine chạy độc lập với các bước thực thi chính của chương trình. Thao tác này có thể diễn ra song song hoặc trên một bộ xử lý riêng biệt. Cũng có thể là trong khi phần còn lại của ứng dụng đang chờ nhập dữ liệu, bạn có thể lén lút xử lý một chút. Một trong những khía cạnh quan trọng của tính năng không đồng bộ là bạn không thể mong đợi kết quả đó sẽ có sẵn cho đến khi bạn đợi kết quả một cách rõ ràng.

Ví dụ: giả sử bạn có câu hỏi cần được nghiên cứu và bạn nhờ một đồng nghiệp tìm câu trả lời. Họ bỏ ra và làm việc với trò chơi đó, giống như họ đang làm công việc "không đồng bộ" và "trên một chuỗi riêng." Bạn có thể tiếp tục làm những việc khác không phụ thuộc vào câu trả lời cho đến khi đồng nghiệp quay lại và cho bạn biết câu trả lời là gì.

Coroutine không chặn.

Không chặn có nghĩa là coroutine không chặn luồng chính hoặc luồng giao diện người dùng. Vì vậy, với coroutine, người dùng luôn có trải nghiệm mượt mà nhất có thể, vì hành động tương tác trên giao diện người dùng luôn có mức độ ưu tiên.

Coroutine dùng các hàm tạm ngưng để tạo mã không đồng bộ theo trình tự.

Từ khóa suspend là cách Kotlin đánh dấu một hàm hoặc loại hàm sẵn có trên coroutine. Khi coroutine gọi một hàm được đánh dấu bằng suspend, thay vì chặn cho đến khi hàm trả về như một lệnh gọi hàm thông thường, coroutine sẽ tạm ngưng thực thi cho đến khi kết quả sẵn sàng. Sau đó, coroutine sẽ tiếp tục từ nơi dừng lại, kèm theo kết quả.

Trong khi coroutine bị tạm ngưng và đang chờ kết quả, coroutine sẽ bỏ chặn luồng mà coroutine đó đang chạy. Bằng cách đó, các hàm hoặc coroutine khác có thể chạy.

Từ khóa suspend không chỉ định chuỗi mà mã chạy. Hàm tạm ngưng có thể chạy trên luồng trong nền hoặc trên luồng chính.

Để sử dụng coroutine trong Kotlin, bạn cần có 3 mục sau:

  • Công việc
  • Điều phối viên
  • Phạm vi

Việc làm: Về cơ bản, công việc là bất kỳ thứ gì có thể hủy. Mọi coroutine đều có một công việc và bạn có thể dùng công việc đó để hủy coroutine. Có thể sắp xếp công việc vào các hệ phân cấp gốc-con. Việc hủy một công việc mẹ sẽ hủy tất cả công việc con của công việc đó ngay lập tức, việc này thuận tiện hơn rất nhiều so với việc hủy từng coroutine theo cách thủ công.

Người điều phối: Người điều phối gửi coroutine để chạy trên nhiều luồng. Ví dụ: Dispatcher.Main chạy các tác vụ trên chuỗi chính và Dispatcher.IO giảm tải các tác vụ I/O chung cho một nhóm chuỗi được chia sẻ.

Phạm vi: Phạm vi của coroutine sẽ xác định ngữ cảnh mà coroutine chạy. Phạm vi kết hợp thông tin về công việc của coroutine và người điều phối. Phạm vi theo dõi coroutine. Khi bạn chạy một coroutine, giá trị này sẽ nằm trong phạm vi và" có nghĩa là bạn đã chỉ ra phạm vi nào sẽ theo dõi coroutine.

Bạn muốn người dùng có thể tương tác với dữ liệu giấc ngủ theo những cách sau:

  • Khi người dùng nhấn vào nút Bắt đầu, ứng dụng sẽ tạo một giấc ngủ mới và lưu trữ đêm ngủ trong cơ sở dữ liệu.
  • Khi người dùng nhấn vào nút Dừng, ứng dụng sẽ cập nhật đêm theo thời gian kết thúc.
  • Khi người dùng nhấn vào nút Xóa, ứng dụng sẽ xóa dữ liệu trong cơ sở dữ liệu.

Những thao tác cơ sở dữ liệu này có thể mất nhiều thời gian, vì vậy, chúng sẽ chạy trên một luồng riêng.

Bước 1: Thiết lập coroutine để thực hiện các thao tác với cơ sở dữ liệu

Khi nhấn vào nút Bắt đầu trong ứng dụng Theo dõi giấc ngủ, bạn muốn gọi một hàm trong SleepTrackerViewModel để tạo một thực thể mới của SleepNight và lưu trữ thực thể đó trong cơ sở dữ liệu.

Thao tác nhấn vào một nút bất kỳ sẽ kích hoạt thao tác với cơ sở dữ liệu, chẳng hạn như tạo hoặc cập nhật SleepNight. Vì lý do này và các lý do khác, bạn sử dụng coroutine để triển khai trình xử lý lượt nhấp cho các nút của ứng dụng.

  1. Mở tệp build.gradle cấp ứng dụng và tìm phần phụ thuộc cho coroutine. Để sử dụng coroutine, bạn cần thêm các phần phụ thuộc này cho chúng.

    $coroutine_version được xác định trong tệp build.gradle của dự án là coroutine_version = '1.0.0'.
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
  1. Mở tệp SleepTrackerViewModel.
  2. Trong phần nội dung của lớp, hãy xác định viewModelJob và chỉ định thực thể của Job. viewModelJob này cho phép bạn hủy tất cả coroutine đã bắt đầu bởi mô hình chế độ xem này khi mô hình chế độ xem không còn được sử dụng và bị hủy bỏ. Bằng cách này, bạn sẽ không kết thúc bằng những coroutine không có giá trị để quay lại.
private var viewModelJob = Job()
  1. Ở cuối phần nội dung của lớp, hãy ghi đè onCleared() và hủy tất cả coroutine. Khi ViewModel bị hủy bỏ, onCleared() sẽ được gọi.
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}
  1. Ngay bên dưới định nghĩa viewModelJob, hãy xác định một uiScope cho coroutine. Phạm vi xác định coroutine sẽ chạy trên luồng nào và phạm vi đó cũng cần biết về công việc. Để nhận phạm vi, hãy yêu cầu bản sao của CoroutineScope và chuyển vào người điều phối và công việc.

Việc dùng Dispatchers.Main có nghĩa là các coroutine được chạy trong uiScope sẽ chạy trên luồng chính. Điều này hợp lý đối với nhiều coroutine do ViewModel bắt đầu, vì sau khi những coroutine này thực hiện một số quá trình xử lý, chúng sẽ dẫn đến việc cập nhật giao diện người dùng.

private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
  1. Bên dưới định nghĩa của uiScope, hãy xác định một biến có tên là tonight để lưu giữ đêm hiện tại. Tạo biến MutableLiveData vì bạn có thể quan sát và thay đổi dữ liệu.
private var tonight = MutableLiveData<SleepNight?>()
  1. Để khởi chạy biến tonight sớm nhất có thể, hãy tạo một khối init bên dưới định nghĩa tonight và gọi initializeTonight(). Bạn xác định initializeTonight() trong bước tiếp theo.
init {
   initializeTonight()
}
  1. Bên dưới khối init, hãy triển khai initializeTonight(). Trong uiScope, hãy chạy coroutine. Bên trong, hãy lấy giá trị của tonight từ cơ sở dữ liệu bằng cách gọi getTonightFromDatabase() và chỉ định giá trị đó cho tonight.value. Bạn xác định getTonightFromDatabase() trong bước tiếp theo.
private fun initializeTonight() {
   uiScope.launch {
       tonight.value = getTonightFromDatabase()
   }
}
  1. Triển khai getTonightFromDatabase(). Xác định hàm này dưới dạng hàm private suspend trả về SleepNight có thể có giá trị null, nếu không có SleepNight nào được bắt đầu hiện tại. Điều này khiến bạn gặp lỗi vì hàm phải trả về nội dung nào đó.
private suspend fun getTonightFromDatabase(): SleepNight? { }
  1. Bên trong phần nội dung hàm của getTonightFromDatabase(), hãy trả về kết quả từ một coroutine chạy trong ngữ cảnh Dispatchers.IO. Sử dụng người điều phối I/O, vì việc nhận dữ liệu từ cơ sở dữ liệu là hoạt động I/O và không liên quan đến giao diện người dùng.
  return withContext(Dispatchers.IO) {}
  1. Bên trong khối trả về, hãy cho coroutine vào tối nay (đêm mới nhất) từ cơ sở dữ liệu. Nếu thời gian bắt đầu và thời gian kết thúc không giống nhau, nghĩa là đêm đó đã kết thúc, hãy trả về null. Nếu không, hãy trả lại đêm.
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night

Hàm tạm ngưng getTonightFromDatabase() đã hoàn tất của bạn sẽ trông giống như sau. Không có lỗi nào nữa.

private suspend fun getTonightFromDatabase(): SleepNight? {
   return withContext(Dispatchers.IO) {
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night
   }
}

Bước 2: Thêm trình xử lý lượt nhấp cho nút Bắt đầu

Bây giờ, bạn có thể triển khai onStartTracking(), trình xử lý lượt nhấp cho nút Bắt đầu. Bạn cần phải tạo một SleepNight mới, chèn nó vào cơ sở dữ liệu và chỉ định nó cho tonight. Cấu trúc của onStartTracking() sẽ rất giống initializeTonight().

  1. Bắt đầu bằng định nghĩa hàm cho onStartTracking(). Bạn có thể đặt trình xử lý lượt nhấp phía trên onCleared() trong tệp SleepTrackerViewModel.
fun onStartTracking() {}
  1. Bên trong onStartTracking(), hãy chạy một coroutine trong uiScope, vì bạn cần có kết quả này để tiếp tục và cập nhật giao diện người dùng.
uiScope.launch {}
  1. Bên trong hoạt động khởi chạy coroutine, hãy tạo một SleepNight mới, ghi lại thời gian hiện tại làm thời gian bắt đầu.
        val newNight = SleepNight()
  1. Vẫn ở trong trình chạy coroutine, hãy gọi insert() để chèn newNight vào cơ sở dữ liệu. Bạn sẽ thấy lỗi vì bạn chưa xác định hàm tạm ngưng insert() này. (Đây không phải là hàm DAO có cùng tên.)
       insert(newNight)
  1. Ngoài ra, trong bản chạy coroutine, hãy cập nhật tonight.
       tonight.value = getTonightFromDatabase()
  1. Dưới onStartTracking(), hãy xác định insert() là một hàm private suspend lấy SleepNight làm đối số.
private suspend fun insert(night: SleepNight) {}
  1. Đối với nội dung của insert(), hãy chạy coroutine trong bối cảnh I/O và chèn đêm vào cơ sở dữ liệu bằng cách gọi insert() từ DAO.
   withContext(Dispatchers.IO) {
       database.insert(night)
   }
  1. Trong tệp bố cục fragment_sleep_tracker.xml, hãy thêm trình xử lý lượt nhấp cho onStartTracking() vào start_button bằng cách sử dụng phép liên kết dữ liệu mà bạn đã thiết lập trước đó. Ký hiệu hàm @{() -> tạo một hàm lambda không nhận đối số nào và gọi trình xử lý lượt nhấp trong sleepTrackerViewModel.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
  1. Tạo và chạy ứng dụng. Nhấn vào nút Bắt đầu. Thao tác này sẽ tạo dữ liệu, nhưng bạn chưa thể xem bất kỳ dữ liệu nào. Bạn khắc phục vấn đề tiếp theo nhé.
fun someWorkNeedsToBeDone {
   uiScope.launch {

        suspendFunction()

   }
}

suspend fun suspendFunction() {
   withContext(Dispatchers.IO) {
       longrunningWork()
   }
}

Bước 3: Hiển thị dữ liệu

Trong SleepTrackerViewModel, biến nights tham chiếu đến LiveData do getAllNights() trong DAO trả về LiveData.

Đây là một tính năng của Room mà mỗi khi dữ liệu trong cơ sở dữ liệu thay đổi, LiveData nights sẽ được cập nhật để hiển thị dữ liệu mới nhất. Bạn không bao giờ cần đặt LiveData hoặc cập nhật ứng dụng một cách rõ ràng. Room sẽ cập nhật dữ liệu để khớp với cơ sở dữ liệu.

Tuy nhiên, nếu bạn hiển thị nights trong chế độ xem văn bản, giao diện này sẽ hiển thị tham chiếu đối tượng. Để xem nội dung của đối tượng, hãy biến đổi dữ liệu thành một chuỗi được định dạng. Sử dụng bản đồ Transformation mà sẽ được thực thi mỗi khi nights nhận dữ liệu mới từ cơ sở dữ liệu.

  1. Mở tệp Util.kt và hủy nhận xét mã cho định nghĩa của formatNights() và câu lệnh import được liên kết. Để hủy nhận xét mã trong Android Studio, hãy chọn tất cả mã được đánh dấu bằng // rồi nhấn Cmd+/ hoặc Control+/.
  2. Xin lưu ý rằng formatNights() sẽ trả về loại Spanned, là một chuỗi có định dạng HTML.
  3. Mở tệp strings.xml. Lưu ý việc sử dụng CDATA để định dạng các tài nguyên chuỗi để hiển thị dữ liệu giấc ngủ.
  4. Mở SleepTrackerViewModel. Trong lớp SleepTrackerViewModel, bên dưới định nghĩa uiScope, hãy xác định một biến có tên là nights. Nhận tất cả các đêm từ cơ sở dữ liệu và chỉ định các đêm đó cho biến nights.
private val nights = database.getAllNights()
  1. Ngay bên dưới định nghĩa về nights, hãy thêm mã để chuyển đổi nights thành nightsString. Dùng hàm formatNights() trong Util.kt.

    Chuyển nights vào hàm map() trong lớp Transformations. Để có quyền truy cập vào các tài nguyên chuỗi của bạn, hãy xác định chức năng liên kết là gọi formatNights(). Hãy cung cấp nights và một đối tượng Resources.
val nightsString = Transformations.map(nights) { nights ->
   formatNights(nights, application.resources)
}
  1. Mở tệp bố cục fragment_sleep_tracker.xml. Trong thuộc tính TextView, trong thuộc tính android:text, bạn hiện có thể thay thế chuỗi tài nguyên bằng tham chiếu đến nightsString.
"@{sleepTrackerViewModel.nightsString}"
  1. Tạo lại mã của bạn và chạy ứng dụng. Tất cả dữ liệu giấc ngủ của bạn với thời gian bắt đầu đều sẽ hiển thị ngay bây giờ.
  2. Nhấn vào nút Bắt đầu thêm vài lần nữa, rồi bạn sẽ thấy thêm dữ liệu.

Bước tiếp theo mà bạn bật chức năng cho nút Dừng.

Bước 4: Thêm trình xử lý lượt nhấp cho nút Dừng

Sử dụng cùng một mẫu như trong bước trước, triển khai trình xử lý lượt nhấp cho nút Dừng trong SleepTrackerViewModel.

  1. Hãy thêm onStopTracking() vào ViewModel. Chạy một coroutine trong uiScope. Nếu thời gian kết thúc chưa được đặt, hãy đặt endTimeMilli thành giờ hiện tại của hệ thống và gọi update() kèm theo dữ liệu ban đêm.

    Trong Kotlin, cú pháp return@label chỉ định hàm mà câu lệnh này trả về trong một số hàm lồng nhau.
fun onStopTracking() {
   uiScope.launch {
       val oldNight = tonight.value ?: return@launch
       oldNight.endTimeMilli = System.currentTimeMillis()
       update(oldNight)
   }
}
  1. Triển khai update() bằng cách sử dụng cùng một mẫu như bạn đã sử dụng để triển khai insert().
private suspend fun update(night: SleepNight) {
   withContext(Dispatchers.IO) {
       database.update(night)
   }
}
  1. Để kết nối trình xử lý lượt nhấp với giao diện người dùng, hãy mở tệp bố cục fragment_sleep_tracker.xml rồi thêm trình xử lý lượt nhấp vào stop_button.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
  1. Xây dựng và chạy ứng dụng.
  2. Nhấn vào Bắt đầu, rồi nhấn vào Dừng. Bạn thấy thời gian bắt đầu, thời gian kết thúc, chất lượng giấc ngủ và không có giá trị, cũng như thời gian ngủ.

Bước 5: Thêm trình xử lý lượt nhấp cho nút Xóa

  1. Tương tự, hãy triển khai onClear()clear().
fun onClear() {
   uiScope.launch {
       clear()
       tonight.value = null
   }
}

suspend fun clear() {
   withContext(Dispatchers.IO) {
       database.clear()
   }
}
  1. Để kết nối trình xử lý lượt nhấp với giao diện người dùng, hãy mở fragment_sleep_tracker.xml và thêm trình xử lý lượt nhấp vào clear_button.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
  1. Xây dựng và chạy ứng dụng.
  2. Nhấn vào Xóa để loại bỏ tất cả dữ liệu. Sau đó, nhấn vào Bắt đầuDừng để tạo dữ liệu mới.

Dự án Android Studio: TrackMySleepChấtCoroutines

  • Hãy dùng ViewModel, ViewModelFactory và liên kết dữ liệu để thiết lập cấu trúc giao diện người dùng cho ứng dụng.
  • Để đảm bảo giao diện người dùng hoạt động trơn tru, hãy sử dụng coroutine cho các thao tác dài hạn, chẳng hạn như tất cả thao tác với cơ sở dữ liệu.
  • Coroutine không đồng bộ và không chặn. Họ dùng các hàm suspend để tạo mã không đồng bộ theo trình tự.
  • Khi coroutine gọi một hàm được đánh dấu bằng suspend, thay vì chặn cho đến khi hàm đó trả về như một lệnh gọi hàm thông thường, hệ thống sẽ tạm ngưng thực thi cho đến khi kết quả sẵn sàng. Sau đó, Chrome tiếp tục từ nơi dừng lại kèm theo kết quả.
  • Sự khác biệt giữa chặntạm ngưng là nếu một luồng bị chặn, thì không có tác vụ nào khác xảy ra. Nếu chuỗi cuộc trò chuyện bị tạm ngưng, thì tác vụ khác sẽ diễn ra cho đến khi có kết quả.

Để chạy coroutine, bạn cần có một công việc, người điều phối và phạm vi:

  • Về cơ bản, việc làm là bất cứ thứ gì có thể hủy. Mọi coroutine đều có một công việc và bạn có thể dùng công việc đó để hủy coroutine.
  • Trình điều phối gửi coroutine để chạy trên nhiều luồng. Dispatcher.Main chạy các tác vụ trên chuỗi chính, còn Dispartcher.IO dùng để giảm tải các tác vụ I/O bị chặn sang một nhóm chuỗi được chia sẻ.
  • Phạm vi kết hợp thông tin, bao gồm công việc và người điều phối, để xác định ngữ cảnh mà coroutine chạy. Phạm vi theo dõi coroutine.

Để triển khai trình xử lý lượt nhấp kích hoạt hoạt động của cơ sở dữ liệu, hãy làm theo mẫu sau:

  1. Chạy một coroutine chạy trên luồng chính hoặc luồng giao diện người dùng vì kết quả này ảnh hưởng đến giao diện người dùng.
  2. Gọi một hàm tạm ngưng để thực hiện tác vụ chạy trong thời gian dài để bạn không bị chặn chuỗi giao diện người dùng trong khi chờ kết quả.
  3. Công việc chạy trong thời gian dài không liên quan đến giao diện người dùng, vì vậy, hãy chuyển sang ngữ cảnh I/O. Bằng cách đó, tác vụ có thể chạy trong một nhóm luồng được tối ưu hóa và dành riêng cho các loại hoạt động này.
  4. Sau đó, gọi hàm cơ sở dữ liệu để làm việc.

Dùng bản đồ Transformations để tạo một chuỗi từ đối tượng LiveData mỗi khi đối tượng thay đổi.

Khóa học từ Udacity:

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

Tài liệu và bài viết khác:

Phần này liệt kê các bài tập về nhà có thể được giao cho học viên đang làm việc qua lớp học lập trình này trong khóa học do người hướng dẫn tổ chức. Người hướng dẫn có thể làm những việc sau:

  • Giao bài tập về nhà nếu được yêu cầu.
  • Trao đổi với học viên cách nộp bài tập về nhà.
  • Chấm điểm bài tập về nhà.

Người hướng dẫn có thể sử dụng những đề xuất này ít hay nhiều tùy ý. Do đó, họ có thể thoải mái giao bất kỳ bài tập về nhà nào khác mà họ cảm thấy phù hợp.

Nếu bạn đang tự mình làm việc qua lớp học lập trình này, hãy thoải mái sử dụng các bài tập về nhà này để kiểm tra kiến thức của bạn.

Trả lời các câu hỏi sau

Câu hỏi 1

coroutine nào sau đây có lợi thế về coroutine:

  • Các quảng cáo này không chặn
  • Chạy không đồng bộ.
  • Có thể chạy trên một luồng không phải là luồng chính.
  • Luôn giúp ứng dụng chạy nhanh hơn.
  • Có thể sử dụng ngoại lệ.
  • Có thể viết và đọc như mã tuyến tính.

Câu hỏi 2

Hàm tạm ngưng là gì?

  • Một hàm thông thường được chú thích bằng từ khóa suspend.
  • Hàm có thể được gọi là bên trong coroutine.
  • Trong khi hàm tạm ngưng đang chạy, chuỗi gọi sẽ bị tạm ngưng.
  • Hàm tạm ngưng phải luôn chạy trong nền.

Câu hỏi 3

Sự khác nhau giữa việc chặn và tạm ngưng một chuỗi là gì? Hãy đánh dấu tất cả câu trả lời là đúng.

  • Khi quá trình thực thi bị chặn, không thể thực thi công việc nào khác trên luồng bị chặn.
  • Khi quá trình thực thi bị tạm ngưng, chuỗi có thể thực hiện công việc khác trong khi chờ hoàn tất công việc giảm tải.
  • Việc tạm ngưng sẽ hiệu quả hơn vì các luồng có thể sẽ không chờ đợi, không làm gì cả.
  • Cho dù bị chặn hay bị tạm ngưng, quá trình thực thi vẫn đang chờ kết quả của coroutine trước khi tiếp tục.

Chuyển đến bài học tiếp theo: 6.3 Sử dụng LiveData để kiểm soát các trạng thái của nút

Để biết đường liên kết đến các lớp học lập trình khác trong khóa học này, hãy xem trang đích của các lớp học lập trình cơ bản về Android Kotlin.