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.
Màn hình tiêu đề |
Màn hình trò chơi |
Màn hình điểm |
Giới thiệu
Trong lớp học lập trình này, bạn sẽ tìm hiểu về một trong các Thành phần cấu trúc Android, ViewModel
:
- Bạn sử dụng lớp
ViewModel
để lưu trữ và quản lý dữ liệu liên quan đến giao diện người dùng theo cách chú trọng đến vòng đời. LớpViewModel
cho phép dữ liệu tồn tại sau khi có thay đổi về cấu hình thiết bị, chẳng hạn như xoay màn hình và thay đổi về khả năng sử dụng bàn phím. - Bạn dùng lớp
ViewModelFactory
để tạo thực thể và trả về đối tượngViewModel
còn hiệu lực sau các thay đổi về cấu hình.
Kiến thức bạn cần có
- Cách tạo các ứng dụng Android cơ bản trong Kotlin.
- Cách sử dụng sơ đồ điều hướng để triển khai hệ thống điều hướng trong ứng dụng của bạn.
- Cách thêm mã để chuyển giữa các đích của ứng dụng và chuyển dữ liệu giữa các đích điều hướng.
- Cách hoạt động của vòng đời của hoạt động và mảnh.
- Cách thêm thông tin ghi nhật ký vào một ứng dụng và đọc nhật ký bằng Logcat trong Android Studio.
Kiến thức bạn sẽ học được
- Cách sử dụng cấu trúc ứng dụng Android được đề xuất.
- Cách sử dụng các lớp
Lifecycle
,ViewModel
vàViewModelFactory
trong ứng dụng của bạn. - Cách giữ lại dữ liệu giao diện người dùng thông qua các thay đổi về cấu hình thiết bị.
- Mẫu thiết kế phương thức ban đầu là gì và cách sử dụng mẫu đó.
- Cách tạo đối tượng
ViewModel
bằng giao diệnViewModelProvider.Factory
.
Bạn sẽ thực hiện
- Thêm
ViewModel
vào ứng dụng để lưu dữ liệu của ứng dụng để dữ liệu có hiệu lực sau khi cấu hình thay đổi. - Dùng
ViewModelFactory
và mẫu thiết kế phương thức gốc để tạo thực thể cho đối tượngViewModel
bằng các tham số hàm dựng.
Trong các lớp học lập trình của Bài học 5, bạn phát triển ứng dụng GuessTheWord, bắt đầu bằng mã dành cho người mới bắt đầu. GuessTheWord là trò chơi theo phong cách charades Hai người chơi, trong đó người chơi cộng tác để đạt được điểm số cao nhất có thể.
Người chơi đầu tiên nhìn vào các từ trong ứng dụng và lần lượt thực hiện từng từ, đảm bảo không hiển thị từ đó cho người chơi thứ hai. Người chơi thứ hai cố gắng đoán từ.
Để chơi trò chơi, người chơi đầu tiên mở ứng dụng trên thiết bị và thấy một từ, ví dụ như "guitar," như minh họa trong ảnh chụp màn hình dưới đây.
Người chơi đầu tiên thực hiện từ này, cẩn thận để không thực sự nói từ đó.
- Khi người chơi thứ hai đoán đúng từ, người chơi đầu tiên nhấn nút Tôi hiểu để tăng số lượng và tính từ tiếp theo.
- Nếu người chơi thứ hai không thể đoán từ, người chơi đầu tiên sẽ nhấn nút Bỏ qua để giảm một từ và bỏ qua từ tiếp theo.
- Để kết thúc trò chơi, hãy nhấn nút Kết thúc trò chơi. (Chức năng này không có trong mã bắt đầu cho lớp học lập trình đầu tiên trong chuỗi.)
Trong nhiệm vụ này, bạn tải xuống và chạy ứng dụng dành cho người mới bắt đầu và kiểm tra mã.
Bước 1: Bắt đầu
- Tải mã khởi động GuessTheWord xuống và mở dự án trong Android Studio.
- Chạy ứng dụng trên thiết bị chạy Android hoặc trên trình mô phỏng.
- Nhấn vào các nút. Lưu ý rằng nút Bỏ qua hiển thị từ tiếp theo và giảm điểm số một, đồng thời nút Tôi hiểu hiển thị từ tiếp theo và tăng điểm số một. Nút Trò chơi kết thúc không được triển khai, vì vậy không có gì xảy ra khi bạn nhấn vào.
Bước 2: Thực hiện hướng dẫn bằng mã
- Trong Android Studio, hãy khám phá mã này để biết cách hoạt động của ứng dụng.
- Đảm bảo xem các tệp mô tả bên dưới, đặc biệt quan trọng.
MainActivity.kt
Tệp này chỉ chứa mã mặc định do mẫu tạo.
res/layout/main_activity.xml
Tệp này chứa bố cục chính của ứng dụng. NavHostFragment
lưu trữ các mảnh khác khi người dùng di chuyển qua ứng dụng.
Các mảnh trên giao diện người dùng
Mã dành cho người mới bắt đầu có 3 mảnh trong 3 gói khác nhau trong gói com.example.android.guesstheword.screens
:
title/TitleFragment
cho màn hình tiêu đềgame/GameFragment
cho màn hình trò chơiscore/ScoreFragment
cho màn hình điểm số
screen/title/TitleFragment.kt
Mảnh tiêu đề là màn hình đầu tiên hiển thị khi người dùng chạy ứng dụng. Trình xử lý lượt nhấp được đặt thành nút Chơi để chuyển đến màn hình trò chơi.
màn hình/trò chơi/GameFragment.kt
Đây là mảnh chính, nơi hầu hết hành động của trò chơi diễn ra:
- Các biến được xác định cho từ hiện tại và điểm số hiện tại.
wordList
được xác định bên trong phương thứcresetList()
là danh sách mẫu các từ sẽ được sử dụng trong trò chơi.- Phương thức
onSkip()
là trình xử lý lượt nhấp cho nút Bỏ qua. Điểm này giảm 1 điểm, sau đó hiển thị từ tiếp theo bằng phương thứcnextWord()
. - Phương thức
onCorrect()
là trình xử lý lượt nhấp cho nút Tôi hiểu. Phương thức này được triển khai tương tự như phương thứconSkip()
. Điểm khác biệt duy nhất là phương thức này thêm 1 vào điểm số thay vì trừ đi.
màn hình/score/ScoreFragment.kt
ScoreFragment
là màn hình cuối cùng trong trò chơi và hiển thị điểm cuối cùng của người chơi. Trong lớp học lập trình này, bạn thêm phương thức triển khai để hiển thị màn hình này và hiển thị điểm cuối cùng.
res/navigation/main_navigation.xml
Sơ đồ điều hướng cho thấy cách các mảnh được kết nối thông qua trình đơn điều hướng:
- Từ mảnh tiêu đề, người dùng có thể chuyển đến mảnh trò chơi.
- Từ mảnh trò chơi, người dùng có thể chuyển đến mảnh điểm.
- Từ mảnh điểm, người dùng có thể quay lại mảnh trò chơi.
Trong nhiệm vụ này, bạn sẽ tìm thấy các vấn đề liên quan đến ứng dụng Bắt đầu đoán từ.
- Chạy mã bắt đầu và chơi trò chơi qua một vài từ, nhấn vào Bỏ qua hoặc Tôi hiểu sau mỗi từ.
- Giờ đây, màn hình trò chơi sẽ hiện một từ và tỷ số hiện tại. Thay đổi hướng màn hình bằng cách xoay thiết bị hoặc trình mô phỏng. Hãy lưu ý rằng điểm số hiện tại bị mất.
- Chơi trò chơi qua một vài từ nữa. Khi màn hình trò chơi hiển thị với một số điểm, hãy đóng rồi mở lại ứng dụng. Xin lưu ý rằng trò chơi sẽ khởi động lại từ đầu vì trạng thái ứng dụng không được lưu.
- Chơi trò chơi qua một vài từ, sau đó nhấn vào nút Kết thúc trò chơi. Xin lưu ý rằng không có gì xảy ra.
Các vấn đề trong ứng dụng:
- Ứng dụng khởi động không lưu và khôi phục trạng thái ứng dụng trong quá trình thay đổi cấu hình, chẳng hạn như khi hướng thiết bị thay đổi hoặc khi ứng dụng tắt và khởi động lại.
Bạn có thể giải quyết vấn đề này bằng cách sử dụng lệnh gọi lạionSaveInstanceState()
. Tuy nhiên, để sử dụng phương thứconSaveInstanceState()
, bạn phải viết mã bổ sung để lưu trạng thái trong một gói và triển khai logic để truy xuất trạng thái đó. Ngoài ra, lượng dữ liệu có thể lưu trữ là rất nhỏ. - Màn hình trò chơi không chuyển đến màn hình điểm khi người dùng nhấn vào nút Kết thúc trò chơi.
Bạn có thể giải quyết những vấn đề này bằng cách sử dụng các thành phần cấu trúc ứng dụng mà bạn tìm hiểu trong lớp học lập trình này.
Cấu trúc ứng dụng
Cấu trúc ứng dụng là cách thiết kế ứng dụng của bạn\39; các lớp và mối quan hệ giữa chúng, sao cho mã được sắp xếp, thực hiện tốt trong các tình huống cụ thể và dễ sử dụng. Trong tập hợp bốn lớp học lập trình này, những điểm cải tiến mà bạn thực hiện đối với ứng dụng GuessTheWord tuân theo các nguyên tắc cấu trúc ứng dụng Android và bạn sử dụng Các thành phần cấu trúc của Android. Cấu trúc ứng dụng Android tương tự như cấu trúc cấu trúc MVVM (model-view-viewmodel).
Ứng dụng GuessTheWord tuân theo nguyên tắc thiết kế tách biệt mối quan ngại và được chia thành các lớp, trong đó mỗi lớp giải quyết một mối quan tâm riêng biệt. Trong lớp học lập trình đầu tiên của bài học này, các lớp học bạn làm việc cùng là một bộ điều khiển giao diện người dùng, một ViewModel
và một ViewModelFactory
.
Bộ điều khiển giao diện người dùng
Bộ điều khiển giao diện người dùng là một lớp dựa trên giao diện người dùng, chẳng hạn như Activity
hoặc Fragment
. Bộ điều khiển giao diện người dùng chỉ được chứa logic xử lý giao diện người dùng và các hoạt động tương tác với hệ điều hành như hiển thị chế độ xem và thu thập dữ liệu đầu vào của người dùng. Đừng dùng logic quyết định, chẳng hạn như logic xác định văn bản sẽ hiển thị, vào bộ điều khiển giao diện người dùng.
Trong mã điều kiện khởi động GuessTheWord, bộ điều khiển giao diện người dùng là ba mảnh: GameFragment
, ScoreFragment,
và TitleFragment
. Tuân theo nguyên tắc về thiết kế và nguyên tắc thiết kế; GameFragment
chỉ chịu trách nhiệm vẽ các yếu tố trong trò chơi lên màn hình và biết được khi nào người dùng nhấn vào nút và không thực hiện thao tác nào khác. Khi người dùng nhấn vào một nút, thông tin này được chuyển đến GameViewModel
.
ViewModel
ViewModel
lưu giữ dữ liệu sẽ hiển thị trong một mảnh hoặc hoạt động liên kết với ViewModel
. ViewModel
có thể thực hiện các phép tính và phép biến đổi đơn giản trên dữ liệu để chuẩn bị dữ liệu mà bộ điều khiển giao diện người dùng hiển thị. Trong cấu trúc này, ViewModel
thực hiện quyết định.GameViewModel
lưu giữ dữ liệu như giá trị điểm số, danh sách từ và từ hiện tại, bởi vì đây là dữ liệu sẽ hiển thị trên màn hình. GameViewModel
cũng chứa logic kinh doanh để thực hiện các phép tính đơn giản nhằm quyết định trạng thái hiện tại của dữ liệu.
Vé ViewModelFactory
ViewModelFactory
tạo thực thể ViewModel
cho đối tượng, có hoặc không có thông số hàm dựng.
Trong các lớp học lập trình sau này, bạn tìm hiểu về các Thành phần cấu trúc Android khác có liên quan đến bộ điều khiển giao diện người dùng và ViewModel
.
Lớp ViewModel
được thiết kế để lưu trữ và quản lý dữ liệu liên quan đến giao diện người dùng. Trong ứng dụng này, mỗi ViewModel
được liên kết với một mảnh.
Trong việc cần làm này, bạn thêm ViewModel
đầu tiên vào ứng dụng của mình, GameViewModel
cho GameFragment
. Bạn cũng hiểu được ý nghĩa của ViewModel
khi nhận biết vòng đời.
Bước 1: Thêm lớp GameViewModel
- Mở tệp
build.gradle(module:app)
. Bên trong khốidependencies
, hãy thêm phần phụ thuộc vào Gradle choViewModel
.
Nếu bạn dùng phiên bản mới nhất của thư viện, thì ứng dụng giải pháp sẽ biên dịch như dự kiến. Nếu cách này không giải quyết được, hãy thử giải quyết vấn đề hoặc quay lại phiên bản bên dưới.
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
- Trong thư mục
screens/game/
của gói, hãy tạo một lớp Kotlin mới có tên làGameViewModel
. - Đặt lớp
GameViewModel
mở rộng lớp trừu tượngViewModel
. - Để giúp bạn hiểu rõ hơn về cách
ViewModel
nhận biết vòng đời, hãy thêm một khốiinit
bằng câu lệnhlog
.
class GameViewModel : ViewModel() {
init {
Log.i("GameViewModel", "GameViewModel created!")
}
}
Bước 2: Ghi đè onCleared() và thêm nhật ký
ViewModel
bị hủy bỏ khi mảnh liên kết được tách ra hoặc khi hoạt động kết thúc. Ngay trước khi hủy lệnh gọi lại ViewModel
, lệnh gọi lại onCleared()
sẽ được gọi để dọn dẹp tài nguyên.
- Trong lớp
GameViewModel
, hãy ghi đè phương thứconCleared()
. - Thêm câu lệnh nhật ký vào trong
onCleared()
để theo dõi vòng đời củaGameViewModel
.
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
Bước 3: Liên kết GameViewModel với mảnh trò chơi
Bạn cần liên kết ViewModel
với một bộ điều khiển giao diện người dùng. Để liên kết cả hai, bạn tạo một mục tham chiếu đến ViewModel
bên trong bộ điều khiển giao diện người dùng.
Trong bước này, bạn tạo tệp tham chiếu đến GameViewModel
bên trong bộ điều khiển giao diện người dùng tương ứng, đó là GameFragment
.
- Trong lớp
GameFragment
, hãy thêm một trường thuộc loạiGameViewModel
ở cấp cao nhất làm biến lớp.
private lateinit var viewModel: GameViewModel
Bước 4: Khởi chạy ViewModel
Trong quá trình thay đổi cấu hình, chẳng hạn như xoay màn hình, các bộ điều khiển giao diện người dùng, chẳng hạn như mảnh, sẽ được tạo lại. Tuy nhiên, ViewModel
bản sao vẫn tồn tại. Nếu bạn tạo thực thể ViewModel
bằng lớp ViewModel
, thì một đối tượng mới sẽ được tạo mỗi khi mảnh được tạo lại. Thay vào đó, hãy tạo một phiên bản ViewModel
bằng cách sử dụng ViewModelProvider
.
Cách hoạt động của ViewModelProvider
:
ViewModelProvider
trả về mộtViewModel
hiện có nếu một mã tồn tại hoặc tạo một mã mới nếu mã đó chưa tồn tại.ViewModelProvider
tạo một thực thểViewModel
liên kết với một phạm vi nhất định (một hoạt động hoặc một mảnh).ViewModel
đã tạo vẫn được giữ lại chừng nào phạm vi vẫn còn. Ví dụ: nếu phạm vi là một mảnh, thìViewModel
sẽ được giữ lại cho đến khi mảnh được tách ra.
Khởi chạy ViewModel
, sử dụng phương thức ViewModelProviders.of()
để tạo ViewModelProvider
:
- Trong lớp
GameFragment
, hãy chạy biếnviewModel
. Đặt mã này bên trongonCreateView()
, sau định nghĩa của biến liên kết. Sử dụng phương thứcViewModelProviders.of()
và chuyển trong bối cảnhGameFragment
được liên kết và lớpGameViewModel
. - Phía trên quá trình khởi tạo đối tượng
ViewModel
, hãy thêm một câu lệnh nhật ký để ghi nhật ký lệnh gọi phương thứcViewModelProviders.of()
.
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
- Chạy ứng dụng. Trong Android Studio, hãy mở ngăn Logcat rồi lọc theo
Game
. Nhấn vào nút Phát trên thiết bị hoặc trình mô phỏng. Màn hình trò chơi sẽ mở ra.
Như đã nêu trong Logcat, phương thứconCreateView()
củaGameFragment
sẽ gọi phương thứcViewModelProviders.of()
để tạoGameViewModel
. Câu lệnh ghi nhật ký mà bạn thêm vàoGameFragment
vàGameViewModel
sẽ hiển thị trong Logcat.
- Bật chế độ tự động xoay trên thiết bị hoặc trình mô phỏng rồi thay đổi hướng màn hình một vài lần.
GameFragment
bị hủy bỏ và được tạo lại mỗi lần do đó,ViewModelProviders.of()
được gọi mỗi lần. Tuy nhiên,GameViewModel
chỉ được tạo một lần và sẽ không được tạo lại hoặc hủy bỏ cho mỗi cuộc gọi.
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of
- Thoát khỏi trò chơi hoặc di chuyển khỏi mảnh trò chơi.
GameFragment
bị hủy bỏ.GameViewModel
liên kết cũng bị hủy và lệnh gọi lạionCleared()
sẽ được gọi.
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel destroyed!
ViewModel
tồn tại sau khi thay đổi cấu hình, vì vậy, đây là vị trí tốt để dữ liệu cần tồn tại sau các thay đổi về cấu hình:
- Đặt dữ liệu hiển thị trên màn hình và mã để xử lý dữ liệu đó trong
ViewModel
. ViewModel
không được chứa các thông tin tham chiếu đến các mảnh, hoạt động hoặc chế độ xem vì các hoạt động, mảnh và chế độ xem không tồn tại để thay đổi cấu hình.
Để có thông tin so sánh, dưới đây là cách dữ liệu giao diện người dùng GameFragment
được xử lý trong ứng dụng dành cho người mới bắt đầu trước khi bạn thêm ViewModel
và sau khi bạn thêm ViewModel
:
- Trước khi bạn thêm
ViewModel
:
Khi ứng dụng trải qua một thay đổi về cấu hình, chẳng hạn như xoay màn hình, mảnh trò chơi sẽ bị hủy bỏ và được tạo lại. Dữ liệu bị mất. - Sau khi bạn thêm
ViewModel
và di chuyển dữ liệu giao diện người dùng của mảnh trò chơi vàoViewModel
:
Tất cả dữ liệu mà mảnh cần hiển thị hiện làViewModel
. Khi ứng dụng trải qua sự thay đổi về cấu hình,ViewModel
sẽ vẫn tồn tại và dữ liệu sẽ được giữ lại.
Trong tác vụ này, bạn sẽ di chuyển dữ liệu giao diện người dùng của ứng dụng sang lớp GameViewModel
, cùng với các phương thức để xử lý dữ liệu. Thao tác này được thực hiện để dữ liệu được giữ lại trong quá trình thay đổi cấu hình.
Bước 1: Di chuyển các trường dữ liệu và xử lý dữ liệu vào ViewModel
Di chuyển các trường dữ liệu và phương thức sau từ GameFragment
sang GameViewModel
:
- Di chuyển các trường dữ liệu
word
,score
vàwordList
. Hãy đảm bảoword
vàscore
không phải làprivate
.
Đừng di chuyển biến liên kếtGameFragmentBinding
vì biến này chứa các thông tin tham chiếu đến chế độ xem. Biến này được dùng để tăng cường bố cục, thiết lập trình xử lý lượt nhấp và hiển thị dữ liệu trên màn hình – trách nhiệm của mảnh. - Di chuyển phương thức
resetList()
vànextWord()
. Các phương thức này sẽ quyết định từ nào sẽ hiển thị trên màn hình. - Từ bên trong phương thức
onCreateView()
, hãy di chuyển các lệnh gọi phương thức đếnresetList()
vànextWord()
đến khốiinit
củaGameViewModel
.
Các phương thức này phải nằm trong khốiinit
vì bạn nên đặt lại danh sách từ khiViewModel
được tạo, chứ không phải mỗi khi mảnh được tạo. Bạn có thể xóa câu lệnh nhật ký trong khốiinit
củaGameFragment
.
Trình xử lý lượt nhấp onSkip()
và onCorrect()
trong GameFragment
chứa mã để xử lý dữ liệu và cập nhật giao diện người dùng. Mã cần cập nhật giao diện người dùng sẽ nằm trong mảnh, nhưng cần phải di chuyển mã để xử lý dữ liệu vào ViewModel
.
Hiện tại, hãy đặt các phương thức giống nhau ở cả hai nơi:
- Sao chép các phương thức
onSkip()
vàonCorrect()
từGameFragment
sangGameViewModel
. - Trong
GameViewModel
, hãy đảm bảo các phương thứconSkip()
vàonCorrect()
không phải làprivate
, vì bạn sẽ tham chiếu các phương thức này từ mảnh.
Sau đây là mã của lớp GameViewModel
sau khi tái cấu trúc:
class GameViewModel : ViewModel() {
// The current word
var word = ""
// The current score
var score = 0
// The list of words - the front of the list is the next word to guess
private lateinit var wordList: MutableList<String>
/**
* Resets the list of words and randomizes the order
*/
private fun resetList() {
wordList = mutableListOf(
"queen",
"hospital",
"basketball",
"cat",
"change",
"snail",
"soup",
"calendar",
"sad",
"desk",
"guitar",
"home",
"railway",
"zebra",
"jelly",
"car",
"crow",
"trade",
"bag",
"roll",
"bubble"
)
wordList.shuffle()
}
init {
resetList()
nextWord()
Log.i("GameViewModel", "GameViewModel created!")
}
/**
* Moves to the next word in the list
*/
private fun nextWord() {
if (!wordList.isEmpty()) {
//Select and remove a word from the list
word = wordList.removeAt(0)
}
updateWordText()
updateScoreText()
}
/** Methods for buttons presses **/
fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
}
Sau đây là mã của lớp GameFragment
sau khi tái cấu trúc:
/**
* Fragment where the game is played
*/
class GameFragment : Fragment() {
private lateinit var binding: GameFragmentBinding
private lateinit var viewModel: GameViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate view and obtain an instance of the binding class
binding = DataBindingUtil.inflate(
inflater,
R.layout.game_fragment,
container,
false
)
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
updateScoreText()
updateWordText()
return binding.root
}
/** Methods for button click handlers **/
private fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
private fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
/** Methods for updating the UI **/
private fun updateWordText() {
binding.wordText.text = word
}
private fun updateScoreText() {
binding.scoreText.text = score.toString()
}
}
Bước 2: Cập nhật các tệp tham chiếu đến trình xử lý lượt nhấp và các trường dữ liệu trong GameFragment
- Trong
GameFragment
, hãy cập nhật phương thứconSkip()
vàonCorrect()
. Hãy xóa mã này để cập nhật điểm số và gọi các phương thứconSkip()
vàonCorrect()
tương ứng trênviewModel
. - Vì bạn đã di chuyển phương thức
nextWord()
sangViewModel
, nên mảnh trò chơi không thể truy cập vào phương thức đó nữa.
TrongGameFragment
, trong phương thứconSkip()
vàonCorrect()
, hãy thay thế lệnh gọi thànhnextWord()
bằngupdateScoreText()
vàupdateWordText()
. Các phương thức này hiển thị dữ liệu trên màn hình.
private fun onSkip() {
viewModel.onSkip()
updateWordText()
updateScoreText()
}
private fun onCorrect() {
viewModel.onCorrect()
updateScoreText()
updateWordText()
}
- Trong
GameFragment
, hãy cập nhật các biếnscore
vàword
để sử dụng biếnGameViewModel
, vì các biến này hiện nằm trongGameViewModel
.
private fun updateWordText() {
binding.wordText.text = viewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.toString()
}
- Trong
GameViewModel
, bên trong phương thứcnextWord()
, hãy xóa các lệnh gọi đến phương thứcupdateWordText()
vàupdateScoreText()
. Các phương thức này hiện đang được gọi từGameFragment
. - Xây dựng ứng dụng và đảm bảo ứng dụng không có lỗi. Nếu bạn gặp lỗi, hãy dọn dẹp và xây dựng lại dự án.
- Chạy ứng dụng và chơi trò chơi thông qua một số từ. Khi bạn đang ở màn hình trò chơi, hãy xoay thiết bị. Xin lưu ý rằng điểm hiện tại và từ hiện tại được giữ lại sau khi có thay đổi về hướng.
Tuyệt vời! Giờ đây, tất cả dữ liệu của ứng dụng được lưu trữ trong ViewModel
, do đó, dữ liệu này sẽ được giữ lại trong quá trình thay đổi cấu hình.
Trong nhiệm vụ này, bạn triển khai trình xử lý lượt nhấp cho nút Trò chơi kết thúc.
- Trong
GameFragment
, hãy thêm một phương thức có tên làonEndGame()
. Phương thứconEndGame()
sẽ được gọi khi người dùng nhấn vào nút Trò chơi kết thúc.
private fun onEndGame() {
}
- Trong
GameFragment
, bên trong phương thứconCreateView()
, hãy tìm mã đặt trình xử lý lượt nhấp cho các nút Got và Bỏ qua. Ngay bên dưới hai dòng này, hãy đặt trình xử lý lượt nhấp cho nút Trò chơi kết thúc. Hãy dùng biến liên kếtbinding
. Bên trong trình nghe lượt nhấp, hãy gọi phương thứconEndGame()
.
binding.endGameButton.setOnClickListener { onEndGame() }
- Trong
GameFragment
, hãy thêm một phương thức có tên làgameFinished()
để chuyển ứng dụng đến màn hình điểm. Chuyển điểm số làm đối số, sử dụng Safe Args.
/**
* Called when the game is finished
*/
private fun gameFinished() {
Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
val action = GameFragmentDirections.actionGameToScore()
action.score = viewModel.score
NavHostFragment.findNavController(this).navigate(action)
}
- Trong phương thức
onEndGame()
, hãy gọi phương thứcgameFinished()
.
private fun onEndGame() {
gameFinished()
}
- Chạy ứng dụng, chơi trò chơi và đạp xe qua một số từ. Nhấn vào nút Kết thúc trò chơi. Xin lưu ý rằng ứng dụng sẽ chuyển đến màn hình điểm, nhưng điểm cuối cùng không hiển thị. Bạn khắc phục sự cố này trong tác vụ tiếp theo.
Khi người dùng kết thúc trò chơi, ScoreFragment
không hiển thị điểm số. Bạn muốn ViewModel
giữ điểm số do ScoreFragment
hiển thị. Bạn sẽ chuyển giá trị điểm trong quá trình khởi tạo ViewModel
bằng cách sử dụng mẫu phương thức gốc.
Mẫu phương thức nhà máy là mẫu thiết kế có tính sáng tạo sử dụng các phương thức ban đầu để tạo đối tượng. Phương thức gốc là một phương thức trả về một thực thể của cùng một lớp.
Trong tác vụ này, bạn tạo một ViewModel
có một hàm dựng đã tạo thông số cho mảnh điểm và một phương thức gốc để tạo thực thể cho ViewModel
.
- Trong gói
score
, hãy tạo một lớp Kotlin mới có tên làScoreViewModel
. Lớp này sẽ làViewModel
cho mảnh điểm. - Mở rộng lớp
ScoreViewModel
từViewModel.
Thêm thông số hàm dựng cho điểm số cuối cùng. Thêm một khốiinit
bằng một câu lệnh nhật ký. - Trong lớp
ScoreViewModel
, hãy thêm một biến có tên làscore
để lưu điểm cuối cùng.
class ScoreViewModel(finalScore: Int) : ViewModel() {
// The final score
var score = finalScore
init {
Log.i("ScoreViewModel", "Final score is $finalScore")
}
}
- Trong gói
score
, hãy tạo một lớp Kotlin khác có tên làScoreViewModelFactory
. Lớp này sẽ chịu trách nhiệm tạo đối tượngScoreViewModel
. - Mở rộng lớp
ScoreViewModelFactory
từViewModelProvider.Factory
. Thêm một tham số hàm dựng cho điểm số cuối cùng.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}
- Trong
ScoreViewModelFactory
, Android Studio hiển thị lỗi về một thành viên trừu tượng chưa được triển khai. Để khắc phục lỗi này, hãy ghi đè phương thứccreate()
. Trong phương thứccreate()
, hãy trả về đối tượngScoreViewModel
mới được tạo.
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
return ScoreViewModel(finalScore) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
- Trong
ScoreFragment
, hãy tạo các biến lớp choScoreViewModel
vàScoreViewModelFactory
.
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory
- Trong
ScoreFragment
, bên trongonCreateView()
, sau khi khởi tạo biếnbinding
, hãy khởi chạyviewModelFactory
. Hãy dùngScoreViewModelFactory
. Chuyển điểm số cuối cùng từ gói đối số làm thông số hàm dựng choScoreViewModelFactory()
.
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)
- Trong
onCreateView(
), sau khi khởi chạyviewModelFactory
, hãy khởi tạo đối tượngviewModel
. Gọi phương thứcViewModelProviders.of()
, chuyển bối cảnh của điểm số liên kết vàviewModelFactory
. Thao tác này sẽ tạo đối tượngScoreViewModel
bằng phương thức gốc được xác định trong lớpviewModelFactory
.
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ScoreViewModel::class.java)
- Trong phương thức
onCreateView()
, sau khi khởi chạyviewModel
, hãy đặt văn bản của chế độ xemscoreText
thành điểm số cuối cùng được xác định trongScoreViewModel
.
binding.scoreText.text = viewModel.score.toString()
- Chạy ứng dụng của bạn và chơi trò chơi. Chuyển qua một số hoặc tất cả các từ rồi nhấn vào Kết thúc trò chơi. Xin lưu ý rằng mảnh điểm hiện hiển thị điểm cuối cùng.
- Không bắt buộc: Kiểm tra nhật ký của
ScoreViewModel
trong Logcat bằng cách lọc trênScoreViewModel
. Giá trị điểm phải hiển thị.
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15
Trong nhiệm vụ này, bạn đã triển khai ScoreFragment
để sử dụng ViewModel
. Bạn cũng đã tìm hiểu cách tạo một hàm dựng đã tạo thông số cho ViewModel
bằng giao diện ViewModelFactory
.
Xin chúc mừng! Bạn đã thay đổi cấu trúc của ứng dụng để sử dụng một trong các Thành phần cấu trúc Android, ViewModel
. Bạn đã giải quyết được vấn đề trong vòng đời của ứng dụng và giờ đây, dữ liệu của trò chơi vẫn tồn tại sau khi thay đổi cấu hình. Bạn cũng đã tìm hiểu cách tạo một hàm dựng đã tạo thông số để tạo ViewModel
bằng cách sử dụng giao diện ViewModelFactory
.
Dự án Android Studio: GuessTheWord
- Nguyên tắc cấu trúc ứng dụng của Android khuyên bạn nên phân tách các lớp có trách nhiệm khác nhau.
- Bộ điều khiển giao diện người dùng là lớp dựa trên giao diện người dùng như
Activity
hoặcFragment
. Bộ điều khiển giao diện người dùng chỉ được chứa logic xử lý các lượt tương tác với giao diện người dùng và hệ điều hành; các bộ điều khiển đó không được chứa dữ liệu hiển thị trong giao diện người dùng. Bạn cần đặt dữ liệu đó trong mộtViewModel
. - Lớp
ViewModel
lưu trữ và quản lý dữ liệu liên quan đến giao diện người dùng. LớpViewModel
cho phép dữ liệu tồn tại sau các thay đổi về cấu hình, chẳng hạn như xoay màn hình. ViewModel
là một trong những Thành phần cấu trúc Android được đề xuất.ViewModelProvider.Factory
là giao diện mà bạn có thể sử dụng để tạo đối tượngViewModel
.
Bảng dưới đây so sánh các bộ điều khiển giao diện người dùng với các bản sao ViewModel
lưu giữ dữ liệu của bộ điều khiển:
Bộ điều khiển giao diện người dùng | ViewModel |
Một ví dụ về bộ điều khiển giao diện người dùng là | Ví dụ về |
Không chứa dữ liệu nào sẽ hiển thị trong giao diện người dùng. | Chứa dữ liệu mà bộ điều khiển giao diện người dùng hiển thị trong giao diện người dùng. |
Chứa mã để hiển thị dữ liệu và mã sự kiện người dùng, chẳng hạn như trình xử lý lượt nhấp. | Chứa mã để xử lý dữ liệu. |
Bị hủy bỏ và được tạo lại trong mỗi lần thay đổi cấu hình. | Chỉ bị hủy bỏ khi bộ điều khiển giao diện người dùng liên kết biến mất vĩnh viễn – cho một hoạt động, khi hoạt động kết thúc hoặc cho một mảnh, khi mảnh được tách ra. |
Chứa lượt xem. | Không được bao gồm mục tham chiếu đến hoạt động, mảnh hoặc chế độ xem vì chúng không tồn tại thay đổi cấu hình, nhưng |
Chứa mục tham chiếu đến | Không chứa bất kỳ tham chiếu nào đến bộ điều khiển giao diện người dùng được liên kết. |
Khóa học từ Udacity:
Tài liệu dành cho nhà phát triển Android:
- Tổng quan về ViewModel
- Điều khiển vòng đời bằng các yếu tố nhận biết vòng đời
- Hướng dẫn về cấu trúc ứng dụng
ViewModelProvider
ViewModelProvider.Factory
Các tài liệu khác:
- Mẫu cấu trúc MVVM (model-view-viewmodel).
- Tách biệt mối quan ngại (Nguyên tắc thiết kế SoC)
- Mẫu phương thức ban đầu
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 những câu hỏi này
Câu hỏi 1
Để tránh bị mất dữ liệu trong quá trình thay đổi cấu hình thiết bị, bạn nên lưu dữ liệu ứng dụng vào lớp nào?
ViewModel
LiveData
Fragment
Activity
Câu hỏi 2
ViewModel
không được chứa nội dung tham chiếu đến mảnh, hoạt động hoặc chế độ xem. Đúng hay sai?
- Đúng
- Sai
Câu hỏi 3
Khi nào ViewModel
bị huỷ bỏ?
- Khi trình điều khiển giao diện người dùng liên kết bị huỷ và được tạo lại trong lúc đổi hướng thiết bị.
- Trong lúc thay đổi hướng.
- Khi trình điều khiển giao diện người dùng liên kết đã hoàn tất (nếu là một hoạt động) hoặc được tách ra (nếu là một mảnh).
- Khi người dùng nhấn vào nút Quay lại.
Câu hỏi 4
Giao diện ViewModelFactory
dùng để làm gì?
- Tạo bản sao của đối tượng trong
ViewModel
. - Giữ lại dữ liệu trong khi thay đổi hướng.
- Làm mới dữ liệu đang hiển thị trên màn hình.
- Nhận thông báo khi dữ liệu ứng dụng thay đổi.
Bắt đầu 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.