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
- Tải ứng dụng TrackMySleepquality-Coroutines-Starter xuống từ GitHub.
- 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.
- 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. - 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.
- 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
- Trong gói sleeptracker, hãy mở SleepTrackerViewModel.kt.
- 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ộngAndroidViewModel()
. Lớp này giống vớiViewModel
, 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
- Trong gói sleeptracker, hãy mở SleepTrackerViewModelFactory.kt.
- 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ớiViewModel
và mở rộngViewModelProvider.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ớpSleepTrackerViewModel
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
- Trong
SleepTrackerFragment
, hãy lấy thông tin tham chiếu đến bối cảnh đăng ký. Đặt tham chiếu trongonCreateView()
, bên dướibinding
. 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ửiIllegalArgumentException
nếu giá trị lànull
.
val application = requireNotNull(this.activity).application
- 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ướcreturn
, hãy xác địnhdataSource
. Để tham chiếu đến DAO của cơ sở dữ liệu, hãy sử dụngSleepDatabase.getInstance(application).sleepDatabaseDao
.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
- Trong
onCreateView()
, trướcreturn
, hãy tạo một bản sao củaviewModelFactory
. Bạn cần chuyển thẻdataSource
vàapplication
.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
- 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)
- 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
:
- Bên trong khối
<data>
, hãy tạo một<variable>
để tham chiếu đến lớpSleepTrackerViewModel
.
<data>
<variable
name="sleepTrackerViewModel"
type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>
Trong SleepTrackerFragment
:
- Đặ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ệnhreturn
:
binding.setLifecycleOwner(this)
- Chỉ định biến liên kết
sleepTrackerViewModel
chosleepTrackerViewModel
. Đặt mã này bên trongonCreateView()
, bên dưới mã tạoSleepTrackerViewModel
:
binding.sleepTrackerViewModel = sleepTrackerViewModel
- 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.
- 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.
- 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ệpbuild.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"
- Mở tệp
SleepTrackerViewModel
. - Trong phần nội dung của lớp, hãy xác định
viewModelJob
và chỉ định thực thể củaJob
.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()
- Ở cuối phần nội dung của lớp, hãy ghi đè
onCleared()
và hủy tất cả coroutine. KhiViewModel
bị hủy bỏ,onCleared()
sẽ được gọi.
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
- Ngay bên dưới định nghĩa
viewModelJob
, hãy xác định mộtuiScope
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ủaCoroutineScope
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)
- 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ếnMutableLiveData
vì bạn có thể quan sát và thay đổi dữ liệu.
private var tonight = MutableLiveData<SleepNight?>()
- Để khởi chạy biến
tonight
sớm nhất có thể, hãy tạo một khốiinit
bên dưới định nghĩatonight
và gọiinitializeTonight()
. Bạn xác địnhinitializeTonight()
trong bước tiếp theo.
init {
initializeTonight()
}
- Bên dưới khối
init
, hãy triển khaiinitializeTonight()
. TronguiScope
, hãy chạy coroutine. Bên trong, hãy lấy giá trị củatonight
từ cơ sở dữ liệu bằng cách gọigetTonightFromDatabase()
và chỉ định giá trị đó chotonight.value
. Bạn xác địnhgetTonightFromDatabase()
trong bước tiếp theo.
private fun initializeTonight() {
uiScope.launch {
tonight.value = getTonightFromDatabase()
}
}
- Triển khai
getTonightFromDatabase()
. Xác định hàm này dưới dạng hàmprivate 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? { }
- 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ảnhDispatchers.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) {}
- 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()
.
- 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ênonCleared()
trong tệpSleepTrackerViewModel
.
fun onStartTracking() {}
- Bên trong
onStartTracking()
, hãy chạy một coroutine tronguiScope
, 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 {}
- 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()
- Vẫn ở trong trình chạy coroutine, hãy gọi
insert()
để chènnewNight
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ưnginsert()
này. (Đây không phải là hàm DAO có cùng tên.)
insert(newNight)
- Ngoài ra, trong bản chạy coroutine, hãy cập nhật
tonight
.
tonight.value = getTonightFromDatabase()
- Dưới
onStartTracking()
, hãy xác địnhinsert()
là một hàmprivate suspend
lấySleepNight
làm đối số.
private suspend fun insert(night: SleepNight) {}
- Đố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ọiinsert()
từ DAO.
withContext(Dispatchers.IO) {
database.insert(night)
}
- Trong tệp bố cục
fragment_sleep_tracker.xml
, hãy thêm trình xử lý lượt nhấp choonStartTracking()
vàostart_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 trongsleepTrackerViewModel
.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
- 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.
- Mở tệp
Util.kt
và hủy nhận xét mã cho định nghĩa củaformatNights()
và câu lệnhimport
đượ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ấnCmd+/
hoặcControl+/
. - Xin lưu ý rằng
formatNights()
sẽ trả về loạiSpanned
, là một chuỗi có định dạng HTML. - 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ủ. - Mở SleepTrackerViewModel. Trong lớp
SleepTrackerViewModel
, bên dưới định nghĩauiScope
, 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ếnnights
.
private val nights = database.getAllNights()
- Ngay bên dưới định nghĩa về
nights
, hãy thêm mã để chuyển đổinights
thànhnightsString
. Dùng hàmformatNights()
trongUtil.kt
.
Chuyểnnights
vào hàmmap()
trong lớpTransformations
. Để 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ọiformatNights()
. Hãy cung cấpnights
và một đối tượngResources
.
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
- Mở tệp bố cục
fragment_sleep_tracker.xml
. Trong thuộc tínhTextView
, trong thuộc tínhandroid:text
, bạn hiện có thể thay thế chuỗi tài nguyên bằng tham chiếu đếnnightsString
.
"@{sleepTrackerViewModel.nightsString}"
- 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ờ.
- 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.
- Hãy thêm
onStopTracking()
vàoViewModel
. Chạy một coroutine tronguiScope
. Nếu thời gian kết thúc chưa được đặt, hãy đặtendTimeMilli
thành giờ hiện tại của hệ thống và gọiupdate()
kèm theo dữ liệu ban đêm.
Trong Kotlin, cú phápreturn@
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)
}
}
- 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 khaiinsert()
.
private suspend fun update(night: SleepNight) {
withContext(Dispatchers.IO) {
database.update(night)
}
}
- Để 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àostop_button
.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
- Xây dựng và chạy ứng dụng.
- 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
- Tương tự, hãy triển khai
onClear()
vàclear()
.
fun onClear() {
uiScope.launch {
clear()
tonight.value = null
}
}
suspend fun clear() {
withContext(Dispatchers.IO) {
database.clear()
}
}
- Để 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àoclear_button
.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
- Xây dựng và chạy ứng dụng.
- Nhấn vào Xóa để loại bỏ tất cả dữ liệu. Sau đó, nhấn vào Bắt đầu và Dừ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ặn và tạ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ònDispartcher.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:
- 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.
- 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ả.
- 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.
- 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:
RoomDatabase
- Sử dụng lại bố cục với <include/>
ViewModelProvider.Factory
SimpleDateFormat
HtmlCompat
Tài liệu và bài viết khác:
- Kiểu nhà máy
- Lớp học lập trình về coroutine
- Coroutine, tài liệu chính thức
- Ngữ cảnh và trình điều phối coroutine
Dispatchers
- Vượt quá giới hạn tốc độ của Android
Job
launch
- Trả về và chuyển đến trong Kotlin
- CDATA là viết tắt của dữ liệu ký tự. CDATA có nghĩa là dữ liệu giữa các chuỗi này bao gồm dữ liệu có thể được diễn giải dưới dạng đánh dấu XML, nhưng không nên được làm như vậy.
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:
Để 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.