ה-codelab הזה הוא חלק מהקורס Android Kotlin Fundamentals. כדי להפיק את המרב מהקורס הזה, מומלץ לעבוד על ה-codelabs לפי הסדר. כל ה-codelab של הקורס מפורטים בדף הנחיתה של ה-codelab בנושא יסודות Kotlin ל-Android.
מסך – כותרת |
מסך המשחק |
מסך הציון |
מבוא
ב-codelab הזה תלמדו על אחד מרכיבי הארכיטקטורה של Android, ViewModel:
- משתמשים במחלקה
ViewModelכדי לאחסן ולנהל נתונים שקשורים לממשק המשתמש באופן שמתחשב במחזור החיים. הסיווגViewModelמאפשר לנתונים לשרוד שינויים בהגדרות המכשיר, כמו סיבוב המסך ושינויים בזמינות המקלדת. - משתמשים במחלקה
ViewModelFactoryכדי ליצור מופע של האובייקטViewModelולהחזיר אותו. האובייקט הזה שורד שינויים בהגדרות.
מה שכדאי לדעת
- איך ליצור אפליקציות בסיסיות ל-Android ב-Kotlin.
- איך משתמשים בתרשים הניווט כדי להטמיע ניווט באפליקציה.
- איך מוסיפים קוד כדי לנווט בין היעדים באפליקציה ולהעביר נתונים בין יעדי הניווט.
- איך מחזורי החיים של הפעילות והקטע פועלים.
- איך מוסיפים מידע לרישום ביומן לאפליקציה וקוראים את היומנים באמצעות Logcat ב-Android Studio.
מה תלמדו
- איך משתמשים בארכיטקטורת האפליקציה המומלצת ל-Android.
- איך להשתמש במחלקות
Lifecycle,ViewModelו-ViewModelFactoryבאפליקציה. - איך לשמור נתונים של ממשק משתמש כשמשנים את הגדרות המכשיר.
- מהו דפוס התכנון factory method ואיך משתמשים בו.
- איך יוצרים אובייקט
ViewModelבאמצעות הממשקViewModelProvider.Factory.
הפעולות שתבצעו:
- מוסיפים
ViewModelלאפליקציה, כדי לשמור את הנתונים של האפליקציה כך שהם לא יימחקו כשמשנים את ההגדרות. - משתמשים ב-
ViewModelFactoryובדפוס העיצוב של שיטת המפעל כדי ליצור מופע של אובייקטViewModelעם פרמטרים של בנאי.
בשיעור 5 של Codelab, מפתחים את האפליקציה GuessTheWord, החל מקוד התחלתי. GuessTheWord הוא משחק בסגנון ניחוש מילים לשני שחקנים, שבו השחקנים משתפים פעולה כדי להשיג את הניקוד הגבוה ביותר האפשרי.
השחקן הראשון מסתכל על המילים באפליקציה ומציג כל אחת מהן בתורו, תוך הקפדה לא להראות את המילה לשחקן השני. השחקן השני מנסה לנחש את המילה.
כדי לשחק במשחק, השחקן הראשון פותח את האפליקציה במכשיר ורואה מילה, למשל 'גיטרה', כמו שמוצג בצילום המסך שלמטה.
השחקן הראשון מדגים את המילה, ומשתדל לא לומר אותה.
- כשהשחקן השני מנחש את המילה נכון, השחקן הראשון לוחץ על הלחצן Got It (הבנתי), מה שמגדיל את הספירה באחד ומציג את המילה הבאה.
- אם השחקן השני לא מצליח לנחש את המילה, השחקן הראשון לוחץ על הכפתור דילוג, מה שמקטין את המספר באחד ומדלג למילה הבאה.
- כדי לסיים את המשחק, לוחצים על הלחצן סיום המשחק. (הפונקציונליות הזו לא מופיעה בקוד ההתחלתי של ה-codelab הראשון בסדרה).
במשימה הזו תורידו ותריצו את אפליקציית המתחילים ותבחנו את הקוד.
שלב 1: מתחילים
- מורידים את קוד ההתחלה של GuessTheWord ופותחים את הפרויקט ב-Android Studio.
- מריצים את האפליקציה במכשיר עם Android או באמולטור.
- מקישים על הלחצנים. שימו לב שהמילה הבאה מוצגת בלחיצה על הלחצן דילוג והניקוד יורד באחת, ובלחיצה על הלחצן הבנתי המילה הבאה מוצגת והניקוד עולה באחת. הלחצן סיום המשחק לא מיושם, ולכן לא קורה כלום כשמקישים עליו.
שלב 2: בדיקת הקוד
- ב-Android Studio, בודקים את הקוד כדי להבין איך האפליקציה פועלת.
- חשוב לבדוק את הקבצים שמתוארים בהמשך, כי הם חשובים במיוחד.
MainActivity.kt
הקובץ הזה מכיל רק קוד שנוצר כברירת מחדל על ידי תבנית.
res/layout/main_activity.xml
הקובץ הזה מכיל את הפריסה הראשית של האפליקציה. ה-NavHostFragment מארח את שאר הרכיבים כשהמשתמש עובר בין חלקי האפליקציה.
רכיבי UI
קוד ההתחלה כולל שלושה קטעים בשלוש חבילות שונות מתחת לחבילה com.example.android.guesstheword.screens:
-
title/TitleFragmentלמסך השם -
game/GameFragmentבמסך המשחק score/ScoreFragmentבמסך התוצאה
screens/title/TitleFragment.kt
הקטע של הכותרת הוא המסך הראשון שמוצג כשמפעילים את האפליקציה. הוגדר handler ללחיצה על הלחצן Play (הפעלה), כדי לנווט למסך המשחק.
screens/game/GameFragment.kt
זהו הפרגמנט הראשי, שבו מתרחשת רוב הפעולה במשחק:
- המשתנים מוגדרים למילה הנוכחית ולציון הנוכחי.
- ההגדרה
wordListבתוך השיטהresetList()היא רשימת מילים לדוגמה לשימוש במשחק. - השיטה
onSkip()היא handler של קליקים עבור הלחצן Skip (דילוג). הציון יופחת ב-1, ואז המילה הבאה תוצג באמצעות השיטהnextWord(). - השיטה
onCorrect()היא handler של קליקים עבור הלחצן Got It. השיטה הזו מיושמת באופן דומה לשיטהonSkip(). ההבדל היחיד הוא שבשיטה הזו מוסיפים 1 לציון במקום להחסיר.
screens/score/ScoreFragment.kt
ScoreFragment הוא המסך האחרון במשחק, ומוצג בו הניקוד הסופי של השחקן. ב-codelab הזה מוסיפים את ההטמעה כדי להציג את המסך הזה ואת הניקוד הסופי.
res/navigation/main_navigation.xml
בתרשים הניווט מוצג איך הפרגמנטים מקושרים באמצעות ניווט:
- מקטע הכותרת, המשתמש יכול לנווט לקטע המשחק.
- מקטע המשחק, המשתמש יכול לעבור לקטע הניקוד.
- מקטע הניקוד, המשתמש יכול לחזור לקטע המשחק.
במשימה הזו, תמצאו בעיות באפליקציית המתחילים GuessTheWord.
- מריצים את קוד ההתחלה ומשחקים במשחק כמה מילים, ומקישים על די או על הבנתי אחרי כל מילה.
- במסך המשחק מוצגת עכשיו מילה והניקוד הנוכחי. משנים את כיוון המסך על ידי סיבוב המכשיר או האמולטור. שימו לב שהניקוד הנוכחי יאבד.
- מריצים את המשחק עם עוד כמה מילים. כשהמסך של המשחק מוצג עם ניקוד מסוים, סוגרים את האפליקציה ופותחים אותה מחדש. שימו לב שהמשחק מופעל מחדש מההתחלה, כי מצב האפליקציה לא נשמר.
- משחקים במשחק עד שמגיעים לכמה מילים, ואז מקישים על הלחצן סיום המשחק. שימו לב שלא קורה כלום.
בעיות באפליקציה:
- אפליקציית המתחילים לא שומרת את מצב האפליקציה ולא משחזרת אותו במהלך שינויים בהגדרות, למשל כשמשנים את כיוון המכשיר או כשהאפליקציה נסגרת ומופעלת מחדש.
אפשר לפתור את הבעיה הזו באמצעות הקריאה החוזרתonSaveInstanceState(). עם זאת, השימוש בשיטהonSaveInstanceState()מחייב כתיבת קוד נוסף כדי לשמור את המצב בחבילה, והטמעה של הלוגיקה לאחזור המצב הזה. בנוסף, כמות הנתונים שאפשר לאחסן היא מינימלית. - כשמשתמש מקיש על הלחצן סיום המשחק, הוא לא מועבר ממסך המשחק למסך התוצאות.
כדי לפתור את הבעיות האלה, אפשר להשתמש ברכיבי ארכיטקטורת האפליקציה שמוסברים ב-codelab הזה.
ארכיטקטורת האפליקציה
ארכיטקטורת אפליקציה היא דרך לתכנן את המחלקות של האפליקציות ואת היחסים ביניהן, כך שהקוד יהיה מאורגן, יפעל בצורה טובה בתרחישים מסוימים ויהיה קל לעבוד איתו. בסדרה הזו של ארבעה codelab, השיפורים שתבצעו באפליקציית GuessTheWord יתבססו על ההנחיות של ארכיטקטורת אפליקציות ל-Android, ותשתמשו ב-Android Architecture Components. הארכיטקטורה של אפליקציות Android דומה לדפוס הארכיטקטוני MVVM (model-view-viewmodel).
אפליקציית GuessTheWord פועלת לפי העיקרון העיצובי של הפרדת נושאים ומחולקת למחלקות, כאשר כל מחלקה מטפלת בנושא נפרד. ב-Codelab הראשון הזה של השיעור, הכיתות שבהן תעבדו הן בקר ממשק משתמש, ViewModel ו-ViewModelFactory.
בקר ממשק משתמש
בקר ממשק משתמש הוא מחלקה שמבוססת על ממשק משתמש, כמו Activity או Fragment. בקר ממשק משתמש צריך להכיל רק לוגיקה שמטפלת באינטראקציות עם ממשק המשתמש ומערכת ההפעלה, כמו הצגת תצוגות וקליטת קלט משתמש. אל תכניסו לוגיקה של קבלת החלטות, כמו לוגיקה שקובעת את הטקסט שיוצג, לבקר של ממשק המשתמש.
בקוד ההתחלתי של GuessTheWord, הבקרים של ממשק המשתמש הם שלושת הפרגמנטים: GameFragment, ScoreFragment, ו-TitleFragment. בהתאם לעיקרון העיצוב 'הפרדת דאגות', רכיב GameFragment אחראי רק לציור של אלמנטים במשחק על המסך ולזיהוי מתי המשתמש מקיש על הלחצנים, ולא לשום דבר אחר. כשמשתמש מקיש על לחצן, המידע הזה מועבר אל GameViewModel.
ViewModel
אובייקט ViewModel מכיל נתונים שיוצגו בקטע או בפעילות שמשויכים ל-ViewModel. ViewModel יכול לבצע חישובים פשוטים ושינויים בנתונים כדי להכין אותם לתצוגה על ידי בקר ממשק המשתמש. בארכיטקטורה הזו, ViewModel מבצע את קבלת ההחלטות.GameViewModel מחזיק בנתונים כמו ערך הניקוד, רשימת המילים והמילה הנוכחית, כי אלה הנתונים שיוצגו במסך. בנוסף, GameViewModel מכיל את הלוגיקה העסקית לביצוע חישובים פשוטים כדי לקבוע מה המצב הנוכחי של הנתונים.
ViewModelFactory
הפונקציה ViewModelFactory יוצרת מופעים של אובייקטים מסוג ViewModel, עם או בלי פרמטרים של בנאי.

ב-codelabs מאוחרים יותר, תלמדו על רכיבים אחרים של ארכיטקטורת Android שקשורים לבקרי ממשק משתמש ול-ViewModel.
המחלקות ViewModel מיועדות לאחסון ולניהול של נתונים שקשורים לממשק המשתמש. באפליקציה הזו, כל ViewModel משויך לקטע אחד.
במשימה הזו מוסיפים את ViewModel הראשון לאפליקציה, את GameViewModel של GameFragment. בנוסף, תלמדו מה המשמעות של ViewModel עם מודעות למחזור החיים.
שלב 1: מוסיפים את המחלקה GameViewModel
- פותחים את הקובץ
build.gradle(module:app). בתוך הבלוקdependencies, מוסיפים את יחסי התלות של Gradle עבורViewModel.
אם משתמשים בגרסה האחרונה של הספרייה, קומפילציית אפליקציית הפתרון אמורה להתבצע כמצופה. אם לא, צריך לפתור את הבעיה או לחזור לגרסה שמוצגת למטה.
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'- בתיקייה
screens/game/של החבילה, יוצרים מחלקה חדשה של Kotlin בשםGameViewModel. - גורמים לכיתה
GameViewModelלהרחיב את הכיתה המופשטתViewModel. - כדי להבין טוב יותר איך
ViewModelמודע למחזור החיים, מוסיפים בלוקinitעם הצהרתlog.
class GameViewModel : ViewModel() {
init {
Log.i("GameViewModel", "GameViewModel created!")
}
}שלב 2: מחליפים את השיטה onCleared() ומוסיפים רישום ביומן
האובייקט ViewModel נהרס כשהקטע המשויך מנותק, או כשהפעילות מסתיימת. רגע לפני שהאובייקט ViewModel מושמד, מתבצעת קריאה חוזרת (callback) ל-onCleared() כדי לנקות את המשאבים.
- במחלקה
GameViewModel, מחליפים את השיטהonCleared(). - מוסיפים הצהרת יומן בתוך
onCleared()כדי לעקוב אחרי מחזור החיים שלGameViewModel.
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}שלב 3: משייכים את GameViewModel לקטע המשחק
צריך לשייך ViewModel לבקר ממשק המשתמש. כדי לשייך בין השניים, יוצרים הפניה אל ViewModel בתוך בקר ממשק המשתמש.
בשלב הזה יוצרים הפניה אל GameViewModel בתוך בקר ממשק המשתמש המתאים, שהוא GameFragment.
- בכיתה
GameFragment, מוסיפים שדה מהסוגGameViewModelברמה העליונה כמשתנה כיתתי.
private lateinit var viewModel: GameViewModelשלב 4: מאתחלים את ViewModel
במהלך שינויים בהגדרות, כמו סיבוב המסך, נוצרים מחדש רכיבי בקרה של ממשק המשתמש, כמו fragments. עם זאת, ViewModel מופעים ממשיכים להתקיים. אם יוצרים את מופע ViewModel באמצעות המחלקה ViewModel, נוצר אובייקט חדש בכל פעם שהקטע נוצר מחדש. במקום זאת, יוצרים את מופע ViewModel באמצעות ViewModelProvider.

איך ViewModelProvider עובד:
- הפונקציה
ViewModelProviderמחזירהViewModelקיים אם יש כזה, או יוצרתViewModelחדש אם הוא לא קיים. -
ViewModelProviderיוצר מופע שלViewModelבשיוך להיקף הנתון (פעילות או קטע). - ה-
ViewModelשנוצר נשמר כל עוד ההיקף פעיל. לדוגמה, אם ההיקף הוא קטע, ה-ViewModelנשמר עד שהקטע מנותק.
מפעילים את ViewModel באמצעות ה-method ViewModelProviders.of() כדי ליצור ViewModelProvider:
- במחלקה
GameFragment, מאתחלים את המשתנהviewModel. מציבים את הקוד הזה בתוךonCreateView(), אחרי ההגדרה של משתנה הקישור. משתמשים בשיטהViewModelProviders.of()ומעבירים את ההקשר המשויךGameFragmentואת המחלקהGameViewModel. - מעל האתחול של האובייקט
ViewModel, מוסיפים הצהרת יומן כדי לרשום ביומן את הקריאה ל-methodViewModelProviders.of().
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)- מריצים את האפליקציה. ב-Android Studio, פותחים את החלונית Logcat ומסננים לפי
Game. מקישים על לחצן ההפעלה במכשיר או באמולטור. מסך המשחק נפתח.
כפי שמוצג ב-Logcat, השיטהonCreateView()שלGameFragmentקוראת לשיטהViewModelProviders.of()כדי ליצור אתGameViewModel. הצהרות הרישום ביומן שהוספתם ל-GameFragmentול-GameViewModelמופיעות ב-Logcat.

- מפעילים את הגדרת הסיבוב האוטומטי במכשיר או באמולטור ומשנים את כיוון המסך כמה פעמים. ה-
GameFragmentמושמד ונוצר מחדש בכל פעם, ולכן הפונקציהViewModelProviders.of()מופעלת בכל פעם. אבל האובייקטGameViewModelנוצר רק פעם אחת, והוא לא נוצר מחדש או נמחק בכל קריאה.
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of
- יוצאים מהמשחק או עוברים אל מחוץ לקטע המשחק. האובייקט
GameFragmentמושמד. גםGameViewModelהמשויך מושמד, וההתקשרות חזרהonCleared()מופעלת.
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 שורד שינויים בהגדרות, ולכן הוא מקום טוב לנתונים שצריכים לשרוד שינויים בהגדרות:
- מכניסים את הנתונים שרוצים להציג על המסך ואת הקוד לעיבוד הנתונים ב-
ViewModel. - המחלקות
ViewModelלא יכולות להכיל הפניות לקטעים, לפעילויות או לתצוגות, כי פעילויות, קטעים ותצוגות לא שורדים שינויים בהגדרות.

לשם השוואה, כך מטופלים נתוני ממשק המשתמש של GameFragment באפליקציית המתחילים לפני שמוסיפים את ViewModel, ואחרי שמוסיפים את ViewModel:
- לפני שמוסיפים את
ViewModel:
כשהאפליקציה עוברת שינוי בהגדרה, כמו סיבוב המסך, קטע המשחק נמחק ונוצר מחדש. הנתונים אבדו. - אחרי שמוסיפים את
ViewModelומעבירים את נתוני ממשק המשתמש של קטע המשחק אלViewModel:
כל הנתונים שצריך להציג בקטע הם עכשיוViewModel. כשהאפליקציה עוברת שינוי בהגדרות, ה-ViewModelנשמר והנתונים נשארים.

במשימה הזו תעבירו את נתוני ממשק המשתמש של האפליקציה אל המחלקה GameViewModel, יחד עם השיטות לעיבוד הנתונים. הפעולה הזו מבטיחה שהנתונים יישמרו במהלך שינויים בהגדרות.
שלב 1: מעבירים את שדות הנתונים ואת עיבוד הנתונים אל ViewModel
העברת השדות והשיטות הבאים של נתונים מ-GameFragment אל GameViewModel:
- מעבירים את שדות הנתונים
word,scoreו-wordList. חשוב לוודא ש-wordו-scoreלא שווים ל-private.
אל תעבירו את משתנה הקישור,GameFragmentBinding, כי הוא מכיל הפניות לתצוגות. המשתנה הזה משמש להרחבת הפריסה, להגדרת מאזיני הקליקים ולהצגת הנתונים במסך – אלה האחריות של ה-fragment. - מעבירים את השיטות
resetList()ו-nextWord(). השיטות האלה קובעות איזו מילה תוצג על המסך. - בתוך השיטה
onCreateView(), מעבירים את קריאות השיטה אלresetList()ואלnextWord()אל הבלוקinitשלGameViewModel.
השיטות האלה צריכות להיות בבלוקinit, כי צריך לאפס את רשימת המילים כשיוצרים אתViewModel, ולא בכל פעם שיוצרים את המקטע. אפשר למחוק את הצהרת היומן בבלוקinitשלGameFragment.
ה-handlers של הקליקים onSkip() ו-onCorrect() ב-GameFragment מכילים קוד לעיבוד הנתונים ולעדכון ממשק המשתמש. הקוד לעדכון ממשק המשתמש צריך להישאר ב-fragment, אבל הקוד לעיבוד הנתונים צריך לעבור אל ViewModel.
בשלב הזה, צריך להוסיף את אותן שיטות בשני המקומות:
- מעתיקים את השיטות
onSkip()ו-onCorrect()מה-GameFragmentאל ה-GameViewModel. - ב-
GameViewModel, מוודאים שהשיטותonSkip()ו-onCorrect()לאprivate, כי תהיה הפניה לשיטות האלה מה-fragment.
הנה הקוד לכיתה GameViewModel אחרי רפקטורינג:
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!")
}
}הנה הקוד של הכיתה GameFragment אחרי רפקטורינג:
/**
* 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()
}
}שלב 2: מעדכנים את ההפניות ל-click handlers ולשדות נתונים ב-GameFragment
- ב-
GameFragment, מעדכנים את אמצעי התשלוםonSkip()וonCorrect(). מסירים את הקוד לעדכון הניקוד ובמקום זאת קוראים לשיטות המתאימותonSkip()ו-onCorrect()ב-viewModel. - העברת את השיטה
nextWord()אלViewModel, ולכן אין יותר גישה אליה מהקטע של המשחק.
ב-GameFragment, בשיטותonSkip()ו-onCorrect(), מחליפים את הקריאה ל-nextWord()ב-updateScoreText()וב-updateWordText(). השיטות האלה מציגות את הנתונים במסך.
private fun onSkip() {
viewModel.onSkip()
updateWordText()
updateScoreText()
}
private fun onCorrect() {
viewModel.onCorrect()
updateScoreText()
updateWordText()
}- ב-
GameFragment, מעדכנים את המשתניםscoreו-wordכך שישתמשו במשתניםGameViewModel, כי המשתנים האלה נמצאים עכשיו ב-GameViewModel.
private fun updateWordText() {
binding.wordText.text = viewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.toString()
}- ב-
GameViewModel, בתוך השיטהnextWord(), מסירים את הקריאות לשיטותupdateWordText()ו-updateScoreText(). השיטות האלה נקראות עכשיו מ-GameFragment. - מבצעים Build לאפליקציה ומוודאים שאין שגיאות. אם יש שגיאות, צריך לנקות את הפרויקט ולבנות אותו מחדש.
- מפעילים את האפליקציה ומשחקים במשחק באמצעות כמה מילים. במסך המשחק, מסובבים את המכשיר. שימו לב שהניקוד הנוכחי והמילה הנוכחית נשמרים אחרי שינוי הכיוון.
מעולה! עכשיו כל הנתונים של האפליקציה מאוחסנים ב-ViewModel, ולכן הם נשמרים במהלך שינויים בהגדרות.
במשימה הזו, מטמיעים את מאזין הקליקים של הלחצן End Game (סיום המשחק).
- ב-
GameFragment, מוסיפים שיטה בשםonEndGame(). השיטהonEndGame()תופעל כשהמשתמש יקיש על הלחצן סיום המשחק.
private fun onEndGame() {
}- ב-
GameFragment, בתוך השיטהonCreateView(), מאתרים את הקוד שמגדיר את מאזיני הקליקים ללחצנים Got It (הבנתי) ו-Skip (דילוג). מתחת לשתי השורות האלה, מגדירים מאזין ללחיצות על הלחצן End Game. משתמשים במשתנה הקישור,binding. בתוך מאזין הקליקים, קוראים לשיטתonEndGame().
binding.endGameButton.setOnClickListener { onEndGame() }- ב-
GameFragment, מוסיפים שיטה בשםgameFinished()כדי לנווט באפליקציה למסך התוצאות. מעבירים את הניקוד כארגומנט באמצעות 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)
}- ב-method
onEndGame(), מפעילים את ה-methodgameFinished().
private fun onEndGame() {
gameFinished()
}- מפעילים את האפליקציה, משחקים במשחק ועוברים בין כמה מילים. מקישים על הלחצן סיום המשחק . שימו לב שהאפליקציה עוברת למסך הניקוד, אבל הניקוד הסופי לא מוצג. תפתרו את הבעיה הזו במשימה הבאה.
|
|
כשהמשתמש מסיים את המשחק, הציון לא מוצג ב-ScoreFragment. רוצים שViewModel יכיל את הניקוד שיוצג על ידי ScoreFragment. תעבירו את ערך הניקוד במהלך האתחול של ViewModel באמצעות דפוס שיטת היצירה.
תבנית factory method היא תבנית עיצוב ליצירת אובייקטים שמשתמשת בשיטות ליצירת אובייקטים. שיטת factory היא שיטה שמחזירה מופע של אותה מחלקה.
במשימה הזו יוצרים ViewModel עם בנאי פרמטריזציה בשביל קטע הניקוד ושיטת factory ליצירת מופע של ViewModel.
- בקטע
scorepackage, יוצרים מחלקה חדשה של Kotlin בשםScoreViewModel. הכיתה הזו תהיהViewModelשל קטע הציון. - מרחיבים את המחלקה
ScoreViewModelמ-ViewModel.מוסיפים פרמטר של בנאי לציון הסופי. מוסיפים בלוקinitעם הצהרת יומן. - במחלקת
ScoreViewModel, מוסיפים משתנה בשםscoreכדי לשמור את הציון הסופי.
class ScoreViewModel(finalScore: Int) : ViewModel() {
// The final score
var score = finalScore
init {
Log.i("ScoreViewModel", "Final score is $finalScore")
}
}- בקטע
scorepackage, יוצרים עוד מחלקה של Kotlin בשםScoreViewModelFactory. הכיתה הזו תהיה אחראית ליצירת מופע של האובייקטScoreViewModel. - הארכת השיעור
ScoreViewModelFactoryמ-ViewModelProvider.Factory. מוסיפים פרמטר של בנאי לציון הסופי.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}- ב-
ScoreViewModelFactory, Android Studio מציג שגיאה לגבי חבר מופשט שלא הוטמע. כדי לפתור את השגיאה, צריך לבטל את השיטהcreate(). בשיטהcreate(), מחזירים את האובייקטScoreViewModelשנבנה לאחרונה.
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")
}- ב-
ScoreFragment, יוצרים משתני כיתה ל-ScoreViewModelול-ScoreViewModelFactory.
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory- ב-
ScoreFragment, בתוךonCreateView(), אחרי אתחול המשתנהbinding, מאתחלים אתviewModelFactory. משתמשים ב-ScoreViewModelFactory. מעבירים את הניקוד הסופי מחבילת הארגומנטים כפרמטר של בנאי אלScoreViewModelFactory().
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)- ב-
onCreateView(), אחרי אתחולviewModelFactory, מאתחלים את האובייקטviewModel. מפעילים את methodViewModelProviders.of(), מעבירים את ההקשר של קטע הניקוד המשויך ואתviewModelFactory. ייווצר אובייקטScoreViewModelבאמצעות שיטת היצירה שהוגדרה במחלקהviewModelFactory.
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ScoreViewModel::class.java)- בשיטה
onCreateView(), אחרי שמפעילים אתviewModel, מגדירים את הטקסט של התצוגהscoreTextלציון הדירוג הסופי שמוגדר ב-ScoreViewModel.
binding.scoreText.text = viewModel.score.toString()- מפעילים את האפליקציה ומשחקים במשחק. מעיינים בחלק מהמילים או בכולן ומקישים על סיום המשחק. שימו לב שקטע הניקוד מציג עכשיו את הניקוד הסופי.

- אופציונלי: בודקים את היומנים
ScoreViewModelב-Logcat על ידי סינון לפיScoreViewModel. ערך הניקוד צריך להיות מוצג.
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15
במשימה הזו הטמעתם את ScoreFragment כדי להשתמש ב-ViewModel. למדתם גם איך ליצור בנאי עם פרמטרים עבור ViewModel באמצעות הממשק ViewModelFactory.
מעולה! שיניתם את הארכיטקטורה של האפליקציה כדי להשתמש באחד מרכיבי הארכיטקטורה של Android, ViewModel. פתרתם את הבעיה במחזור החיים של האפליקציה, ועכשיו הנתונים של המשחק נשמרים גם אחרי שינויים בהגדרות. למדתם גם איך ליצור בנאי עם פרמטרים כדי ליצור ViewModel באמצעות הממשק ViewModelFactory.
פרויקט Android Studio: GuessTheWord
- בהנחיות לגבי ארכיטקטורת אפליקציות ל-Android מומלץ להפריד בין מחלקות עם תחומי אחריות שונים.
- בקר ממשק משתמש הוא מחלקה שמבוססת על ממשק משתמש, כמו
ActivityאוFragment. רכיבי בקרה של ממשק המשתמש צריכים להכיל רק לוגיקה שמטפלת באינטראקציות עם ממשק המשתמש ומערכת ההפעלה. הם לא צריכים להכיל נתונים שיוצגו בממשק המשתמש. מכניסים את הנתונים ל-ViewModel. - הכיתה
ViewModelמאחסנת ומנהלת נתונים שקשורים לממשק המשתמש. הסיווגViewModelמאפשר לנתונים לשרוד שינויים בהגדרות, כמו סיבוב המסך. -
ViewModelהוא אחד מרכיבי הארכיטקטורה המומלצים של Android. -
ViewModelProvider.Factoryהוא ממשק שאפשר להשתמש בו כדי ליצור אובייקטViewModel.
בטבלה שלמטה מוצגת השוואה בין אמצעי בקרה בממשק המשתמש לבין מופעי ViewModel שמכילים את הנתונים שלהם:
UI controller | ViewModel |
דוגמה לבקר ממשק משתמש היא | דוגמה ל- |
לא מכיל נתונים להצגה בממשק המשתמש. | מכיל נתונים שמוצגים בממשק המשתמש על ידי בקר ממשק המשתמש. |
מכיל קוד להצגת נתונים וקוד של אירועים שקשורים למשתמש, כמו click listeners (מאזינים לקליקים). | מכיל קוד לעיבוד נתונים. |
הם נהרסים ונוצרים מחדש בכל שינוי בהגדרות. | האובייקט מושמד רק כשהבקר של ממשק המשתמש המשויך נעלם באופן סופי – בפעילות, כשהפעילות מסתיימת, או בקטע, כשהקטע מנותק. |
כולל צפיות. | לעולם לא צריך להכיל הפניות לפעילויות, לקטעים או לתצוגות, כי הם לא שורדים שינויים בהגדרות, אבל |
מכיל הפניה אל | לא מכיל הפניה לבקר ממשק המשתמש המשויך. |
קורס ב-Udacity:
מסמכי תיעוד למפתחי Android:
- סקירה כללית של ViewModel
- טיפול במחזורי חיים באמצעות רכיבים שמודעים למחזור החיים
- מדריך לארכיטקטורת אפליקציות
ViewModelProviderViewModelProvider.Factory
אחר:
- MVVM (model-view-viewmodel) architectural pattern.
- עקרון התכנון הפרדת בעיות (SoC)
- תבנית של שיטת יצירה
בקטע הזה מפורטות אפשרויות למשימות ביתיות לתלמידים שעובדים על ה-Codelab הזה כחלק מקורס בהנחיית מדריך. המורה צריך:
- אם צריך, מקצים שיעורי בית.
- להסביר לתלמידים איך להגיש מטלות.
- בודקים את שיעורי הבית.
אנשי ההוראה יכולים להשתמש בהצעות האלה כמה שרוצים, ומומלץ להם להקצות כל שיעורי בית אחרים שהם חושבים שמתאימים.
אם אתם עובדים על ה-codelab הזה לבד, אתם יכולים להשתמש במשימות האלה כדי לבדוק את הידע שלכם.
עונים על השאלות הבאות
שאלה 1
כדי למנוע אובדן נתונים במהלך שינוי בהגדרת המכשיר, באיזה סוג צריך לשמור את נתוני האפליקציה?
ViewModelLiveDataFragmentActivity
שאלה 2
מחלקת ViewModel אף פעם לא צריכה להכיל הפניות ל-fragments, לפעילויות או לתצוגות. נכון או לא נכון?
- True
- לא נכון
שאלה 3
מתי ViewModel נהרס?
- כשהבקר של ממשק המשתמש המשויך נהרס ונוצר מחדש במהלך שינוי של כיוון המכשיר.
- בשינוי הכיוון.
- כשהבקר של ממשק המשתמש המשויך מסתיים (אם מדובר בפעילות) או מנותק (אם מדובר בקטע).
- כשהמשתמש לוחץ על הלחצן 'הקודם'.
שאלה 4
למה משמש הממשק של ViewModelFactory?
- יצירת מופע של אובייקט
ViewModel. - שמירת הנתונים במהלך שינויים בכיוון המסך.
- רענון של הנתונים שמוצגים במסך.
- קבלת התראות כשנתוני האפליקציה משתנים.
עוברים לשיעור הבא:
קישורים ל-codelabs אחרים בקורס הזה מופיעים בדף הנחיתה של ה-codelabs בנושא יסודות Android Kotlin.




