Lab Lab זה הוא חלק מקורס Android Kotlin Fundamentals. כדי להפיק את המקסימום מהקורס הזה, יש לפעול ברצף לפי קודי שיעור ה-Lab. כל שיעורי Lab של הקורסים מופיעים בדף הנחיתה של Lab Kotlin Fundamentals ל-Android Lab.
מסך – כותרת | מסך המשחק | מסך הניקוד |
מבוא
במעבדה הזו, תוכלו ללמוד על אחד מרכיבי הארכיטקטורה של Android ViewModel
:
- אנחנו משתמשים במחלקה
ViewModel
כדי לאחסן ולנהל נתונים הקשורים לממשק המשתמש, בדרך שמתאימה למחזור חיים. מחלקתViewModel
מאפשרת לנתונים לשרוד שינויים בתצורת המכשיר, כמו סיבובי מסך ושינויים בזמינות המקלדת. - אפשר להשתמש במחלקה
ViewModelFactory
כדי ליצור אובייקט ולהחזיר את האובייקטViewModel
ששרד את הגדרות התצורה.
דברים שחשוב לדעת
- איך ליצור אפליקציות בסיסיות ל-Android ב-Kotlin.
- איך משתמשים בתרשים הניווט להטמעת ניווט באפליקציה.
- איך להוסיף קוד לניווט בין יעדי האפליקציה והעברת הנתונים בין יעדי הניווט.
- כיצד פועלים מחזורי החיים של מחזורי החיים והמקטעים.
- איך להוסיף פרטי רישום לאפליקציה ולקרוא יומנים באמצעות Logcat ב-Android Studio.
מה תלמדו
- איך להשתמש בארכיטקטורה של אפליקציות ל-Android.
- איך משתמשים בכיתות
Lifecycle
,ViewModel
ו-ViewModelFactory
באפליקציה. - איך לשמור על הנתונים של ממשק המשתמש באמצעות שינויים בהגדרת המכשיר.
- מהי תבנית העיצוב של שיטת המדידה ואיך להשתמש בה.
- איך ליצור אובייקט
ViewModel
באמצעות הממשקViewModelProvider.Factory
הפעולות שתבצעו:
- צריך להוסיף
ViewModel
לאפליקציה כדי לשמור נתונים מאפליקציות כך שהנתונים יישמרו גם אחרי שינויים בהגדרות. - כדי ליצור אובייקט
ViewModel
עם פרמטרים של מבנה, יש להשתמש ב-ViewModelFactory
ובדוגמת העיצוב של שיטת הייצור.
במעבדות הקוד של שיעור 5, אתם מפתחים את האפליקציה GuessTheWord, שמתחילה בקוד למתחילים. GuessTheWord הוא משחק בסגנון שחקנים ששני שחקנים. השחקן משתף פעולה כדי להשיג את הניקוד הגבוה ביותר האפשרי.
השחקן הראשון בוחן את המילים באפליקציה ומתאים כל אחת מהן לשחקן אחר, ומבטיח שלא להציג את המילה לשחקן השני. השחקן השני מנסה לנחש את המילה.
כדי לשחק במשחק, השחקן הראשון פותח את האפליקציה במכשיר וצופה במילה, למשל "רצועה&לגיטרה; כפי שמוצג בצילום המסך שבהמשך.
השחקן הראשון מנגן את המילה, והיזהר לא לומר את המילה עצמה.
- כאשר השחקן השני מנחש את המילה כראוי, השחקן הראשון לוחץ על הלחצן הבנתי, שמגדיל את הספירה בכל פעם ומציג את המילה הבאה.
- אם הנגן השני לא יכול לנחש את המילה, הנגן הראשון לוחץ על הלחצן דילוג. פעולה זו מפחיתה את מספר הקולות ומדלגת למילה הבאה.
- כדי לסיים את המשחק, לוחצים על הלחצן סיום המשחק. (הפונקציונליות הזו לא נמצאת בקוד הפתיחה של מעבדת הקוד הראשונה בסדרה.)
במשימה זו אתם מורידים ומפעילים את האפליקציה למתחילים ובודקים את הקוד.
שלב 1: מתחילים
- מורידים את הקוד GuessTheWord Starter ופותחים את הפרויקט ב-Android Studio.
- יש להפעיל את האפליקציה במכשיר שפועל באמצעות Android או אמולטור.
- מקישים על הלחצנים. שימו לב שהלחצן דילוג מציג את המילה הבאה ומקטין את המילה במילה אחת, והלחצן הבנתי מציג את המילה הבאה ומגדיל את הציון במילה אחת. הלחצן סיום המשחק לא מוטמע, כך ששום דבר לא קורה כשמקישים עליו.
שלב 2: הדרכה מפורטת על קוד
- ב-Android Studio אפשר לעיין בקוד כדי להבין איך האפליקציה פועלת.
- חשוב לבדוק את הקבצים המתוארים בהמשך, שהם חשובים במיוחד.
CurrentActivity.kt
הקובץ הזה מכיל רק קוד ברירת מחדל שנוצר על ידי תבנית.
res/layout/main_activity.xml
הקובץ הזה מכיל את הפריסה הראשית של האפליקציה. השדה NavHostFragment
מארח את שאר קטעי הקוד כשהמשתמש מנווטים באפליקציה.
מקטעים של ממשק משתמש
הקוד למתחילים כולל שלושה מקטעים בשלושה חבילות שונות במסגרת החבילה com.example.android.guesstheword.screens
:
title/TitleFragment
למסך הכותרתgame/GameFragment
למסך המשחקscore/ScoreFragment
למסך הניקוד
Screen/title/TitleFragment.kt
מקטע הכותרת הוא המסך הראשון שמוצג כאשר האפליקציה מופעלת. handler של לחיצה מוגדר ללחצן הפעלה, כדי לנווט למסך המשחק.
Screen/game/GameFragment.kt
זהו הקטע העיקרי שבו מתבצעת רוב המשחק.
- משתנים מוגדרים למילה הנוכחית ולציון הנוכחי.
- ה-
wordList
שהוגדרו בשיטהresetList()
היא רשימה לדוגמה של מילים שבהן יש להשתמש במשחק. - השיטה
onSkip()
היא הגורם המטפל בקליקים עבור הלחצן דילוג. היא מקטינה את הציון ב-1 ולאחר מכן מציגה את המילה הבאה בשיטהnextWord()
. - השיטה של
onCorrect()
היא ה-handler של הקליקים על לחצן Got It (הבנתי). שיטה זו מיושמת באופן דומה לשיטהonSkip()
. ההבדל היחיד הוא שהשיטה הזו מוסיפה 1 לציון במקום לניכוי.
Screen/score/ניקודFragment.kt
ScoreFragment
הוא המסך האחרון במשחק, והוא מציג את התוצאה הסופית של השחקן. ב-Codelab הזה אפשר להוסיף את ההטמעה כדי להציג את המסך הזה ולהציג את הציון הסופי.
res/Navigation/main_Navigation.xml
תרשים הניווט מראה איך מחברים את הקטעים דרך ניווט:
- מקטע הטקסט, המשתמש יכול לנווט למקטע המשחק.
- מקטע המשחק, המשתמש יכול לנווט למקטע הניקוד.
- מקטע הניקוד, המשתמש יכול לנווט למקטע המשחק.
במשימה הזו תמצאו בעיות עם האפליקציה GuessTheWord Starter.
- מפעילים את הקוד למתחילים ומשחקים במשחק בכמה מילים, ואז מקישים על דילוג או על הבנתי אחרי כל מילה.
- מסך המשחק מציג עכשיו מילה ואת הניקוד הנוכחי. לשנות את כיוון המסך על ידי סיבוב המכשיר או האמולטור. לתשומת ליבכם: הניקוד הנוכחי אבד.
- רוצה לרוץ במשחק? יש רק עוד כמה מילים. כשמסך המשחק מוצג עם ציון מסוים, סוגרים את האפליקציה ופותחים אותה מחדש. חשוב לזכור שהמשחק מופעל מחדש מההתחלה, כי מצב האפליקציה לא נשמר.
- מפעילים את המשחק בכמה מילים ואז מקישים על הלחצן סיום המשחק. לתשומת ליבך.
בעיות באפליקציה:
- האפליקציה למתחילים לא שומרת ומשחזרת את מצב האפליקציה במהלך שינויי הגדרות, למשל כאשר הכיוון של המכשיר משתנה או כאשר האפליקציה נכבית ומופעלת מחדש.
ניתן לפתור את הבעיה באמצעות הקריאה החוזרת (call) שלonSaveInstanceState()
. עם זאת, כשמשתמשים בשיטהonSaveInstanceState()
, צריך לכתוב קוד נוסף כדי לשמור את המצב בחבילה, וליישם את הלוגיקה כדי לאחזר את המצב הזה. בנוסף, כמות הנתונים שאפשר לאחסן היא מינימלית. - מסך המשחק לא מנווט למסך הניקוד כאשר המשתמש מקיש על הלחצן סיום המשחק.
אפשר לפתור את הבעיות האלה באמצעות רכיבי ארכיטקטורת אפליקציות שלמדתם במעבדה זו.
ארכיטקטורת אפליקציות
ארכיטקטורת אפליקציות היא דרך לעצב את האפליקציות' הכיתות והקשרים ביניהן, כך שהקוד מאורגן, מניב ביצועים טובים בתרחישים מסוימים וקל לעבוד איתו. בקבוצה של ארבעה מעבדות הקוד האלה, השיפורים שתבצעו באפליקציית GuessTheWord פועלים בהתאם להנחיות לארכיטקטורה של אפליקציות ל-Android, ואתם משתמשים ברכיבי ארכיטקטורה של Android. הארכיטקטורה של אפליקציית Android דומה לארכיטקטורה הMVVM (מודל-view-viewmodel).
אפליקציית GuessTheWord מבוססת על העיקרון של עיצוב החששות ומחולקת לכיתות, וכל כיתה מטפלת בבעיה נפרדת. במעבדה הראשונה הזו של השיעור, הכיתות שאיתם אתה עובד הן בקר ממשק משתמש, ViewModel
וViewModelFactory
.
בקר ממשק משתמש
בקר ממשק משתמש הוא מחלקה המבוססת על ממשק המשתמש, כגון Activity
או Fragment
. בקר של ממשק משתמש צריך לכלול רק לוגיקה שמטפלת באינטראקציות של ממשק המשתמש ובמערכת ההפעלה, כמו הצגת צפיות ותיעוד קלט של משתמשים. אין לכלול לוגיקה של קבלת החלטות כמו לוגיקה שקובעת את הטקסט להצגה בממשק המשתמש.
בקוד למתחילים, GuessTheWord הם שלושת קטעי הקוד: GameFragment
, ScoreFragment,
ו-TitleFragment
. בהתאם ל&מירכאות;העיקרון של העיצוב והעיצוב, GameFragment
אחראי רק לציור רכיבי משחקים על המסך ולדעת מתי המשתמש מקיש על הלחצנים, ולא יותר. כשהמשתמש מקיש על לחצן, המידע הזה מועבר אל GameViewModel
.
מודל תצוגה מפורטת
ViewModel
מכיל נתונים להצגה בשבר או בפעילות המשויכים ל-ViewModel
. ViewModel
יכול לבצע חישובים פשוטים וטרנספורמציות בנתונים כדי להכין את הנתונים להצגת השלט הרחוק. בארכיטקטורה זו, ViewModel
מבצע את ההחלטות.
ה-GameViewModel
מכיל נתונים כמו ערך הציון, רשימת המילים והמילה הנוכחית, כי אלה הנתונים שיוצגו במסך. ה-GameViewModel
מכיל גם את הלוגיקה העסקית כדי לבצע חישובים פשוטים כדי לקבוע מהו המצב הנוכחי של הנתונים.
תצוגת דגם
ViewModelFactory
יוצר אובייקט ViewModel
עם, או עם פרמטרים של מבנה.
במעבדות מאוחרות יותר תוכלו ללמוד על רכיבי ארכיטקטורה אחרים של 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: משייכים את המשחק GameView לקטע הקוד של המשחק
יש לשייך ViewModel
לבקר של ממשק משתמש. כדי לשייך את שני הרכיבים, יוצרים הפניה אל ViewModel
בבקר של ממשק המשתמש.
בשלב זה, יש ליצור הפניה של GameViewModel
בתוך בקר ממשק המשתמש התואם, שהוא GameFragment
.
- בכיתה
GameFragment
, מוסיפים שדה מסוגGameViewModel
ברמה העליונה כמשתנה כיתה.
private lateinit var viewModel: GameViewModel
שלב 4: אתחול ה-ViewModel
במהלך שינויים בתצורה, כגון סיבובי מסך, נוצרים מחדש בקרי ממשק משתמש כמו מקטעים. עם זאת, ViewModel
מופעים נשארים בתוקף. אם יוצרים את המופע ViewModel
באמצעות המחלקה ViewModel
, אובייקט חדש נוצר בכל פעם שהקטע נוצר מחדש. במקום זאת, יש ליצור את המופע של ViewModel
באמצעות ViewModelProvider
.
איך ViewModelProvider
פועל:
- הפונקציה
ViewModelProvider
מחזירהViewModel
קיים, אם קיים. אם הוא לא קיים, המערכת יוצרת אותו. - הפונקציה
ViewModelProvider
יוצרת מופע שלViewModel
המשויך להיקף הנתון (פעילות או שבר). - ה-
ViewModel
שנוצר נשמר כל עוד ההיקף פעיל. לדוגמה, אם ההיקף הוא שבר, ה-ViewModel
נשמר עד שקטע הקוד מנותק.
מפעילים את ViewModel
באמצעות השיטה ViewModelProviders.of()
כדי ליצור ViewModelProvider
:
- בכיתה
GameFragment
, מפעילים את המשתנהviewModel
. צריך להציב את הקוד הזה בתוךonCreateView()
, אחרי שמגדירים את המשתנה הרלוונטי. משתמשים בשיטהViewModelProviders.of()
ומעבירים בהקשר שלGameFragment
ובסיווגGameViewModel
. - מעל לאתחול של האובייקט
ViewModel
, יש להוסיף הצהרת יומן כדי לתעד את הקריאה לשיטהViewModelProviders.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
מופיעים ברישומים.
- יש להפעיל את הגדרת הסיבוב האוטומטי במכשיר או באמולטור שלך ולשנות את כיוון המסך כמה פעמים. האפליקציה
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
כי הוא מכיל הפניות לתצוגות המפורטות. משתנה זה משמש לצורך ניפוח הפריסה, הגדרת פונקציות ה-click listener והצגת הנתונים על המסך - בקטע האחריות. - העברה של השיטות
resetList()
ו-nextWord()
. השיטות האלה קובעות איזו מילה תוצג על המסך. - מתוך השיטה
onCreateView()
, יש להעביר את הקריאות לשיטהresetList()
אלnextWord()
ולבלוקinit
שלGameViewModel
.
שיטות אלה חייבות להיות בבלוקinit
, כי יש לאפס את רשימת המילים עם יצירת ה-ViewModel
, ולא בכל פעם שהקטע נוצר. ניתן למחוק את הצהרת היומן בבלוקinit
שלGameFragment
.
גורמי ה-handler של onSkip()
ושל onCorrect()
ב-GameFragment
מכילים קוד לעיבוד הנתונים ולעדכון ממשק המשתמש. הקוד לעדכון ממשק המשתמש צריך להישאר בשבר, אבל הקוד לעיבוד הנתונים צריך לעבור אל ViewModel
.
בינתיים, יש להשתמש בשיטות זהות בשני המקומות:
- מעתיקים את השיטות
onSkip()
ו-onCorrect()
מGameFragment
אלGameViewModel
. - ב
GameViewModel
, צריך לוודא שהשיטותonSkip()
ו-onCorrect()
אינןprivate
, כי השיטות האלה יצוינו מקטע הקוד.
זהו הקוד של כיתה אחת (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: מעדכנים את ההפניות לרכיבי handler של קליקים ולשדות של נתונים ב-GameFragment
- ב
GameFragment
, יש לעדכן את השיטותonSkip()
ו-onCorrect()
. עליך להסיר את הקוד כדי לעדכן את הציון, ולהשתמש במקום זאת בשיטות המתאימותonSkip()
ו-onCorrect()
בתאריךviewModel
. - מכיוון שהעברת את השיטה
nextWord()
ל-ViewModel
, השיטות האלה מציגות את הנתונים במסך.
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
. - בונים את האפליקציה ומוודאים שאין שגיאות. אם נמצאו שגיאות, יש לנקות את הפרויקט ולבנות אותו מחדש.
- מפעילים את האפליקציה ומשחקים במשחק באמצעות כמה מילים. כשמסובבים את המסך, צריך לסובב את המכשיר. שימו לב שהציון הנוכחי והמילה הנוכחית נשמרים אחרי שינוי הכיוון.
מצוין! עכשיו כל נתוני האפליקציה מאוחסנים בקובץ ViewModel
, כך שהם נשמרים במהלך שינויים בהגדרות.
במשימה הזו מטמיעים את מאזין הקליק על הלחצן סיום המשחק.
- ב-
GameFragment
, יש להוסיף שיטה שנקראתonEndGame()
. תתבצע קריאה לשיטתonEndGame()
כשהמשתמש מקיש על הלחצן סיום המשחק.
private fun onEndGame() {
}
- ב
GameFragment
, בתוך השיטהonCreateView()
, מחפשים את הקוד שמגדיר את פונקציות הקליק עבור הלחצנים Got It ו-דילוג. מתחת לשתי השורות האלה, מגדירים לחצן להאזנה בלחיצה על הלחצן End Game (סיום המשחק). משתמשים במשתנה המחייב,binding
. בתוך הפונקציה event listener, יש להתקשר לשיטהonEndGame()
.
binding.endGameButton.setOnClickListener { onEndGame() }
- ב-
GameFragment
, מוסיפים שיטה שנקראתgameFinished()
כדי לנווט באפליקציה למסך הניקוד. אפשר להעביר את הציון כארגומנט באמצעות ארוחות בטוחות.
/**
* 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)
}
- בשיטה
onEndGame()
, יש להתקשר לשיטהgameFinished()
.
private fun onEndGame() {
gameFinished()
}
- מפעילים את האפליקציה, משחקים במשחק ועוברים על כמה מילים. מקישים על הלחצן סיום המשחק. שימו לב שהאפליקציה מנווטת למסך הניקוד, אבל הציון הסופי לא מוצג. ניתן לפתור את הבעיה במשימה הבאה.
כשהמשתמש יסיים את המשחק, ScoreFragment
לא יציג את הניקוד. צריך ViewModel
כדי שהציון יוצג ב-ScoreFragment
. יש להעביר את ערך הציון במהלך האתחול של ViewModel
באמצעות דפוס השיטה המקורית.
דוגמת העיצוב של שיטת הייצור היא דפוס עיצוב יצירתי שנעשה בו שימוש בשיטות יצרן כדי ליצור אובייקטים. שיטת אימות היא שיטה שמחזירה מופע של אותה מחלקה.
במשימה הזו, יוצרים ViewModel
עם קבלן עם פרמטר עבור קטע הקוד של השיטה ושיטת ייצור כדי ליצור את ViewModel
.
- במסגרת החבילה
score
, יש ליצור כיתה חדשה מ-Kotlin שנקראתScoreViewModel
. הכיתה הזו תהיהViewModel
עבור קטע הקוד של הציונים. - הארכת המחלקה
ScoreViewModel
מ-ViewModel.
הוספת פרמטר constructor לציון הסופי. הוספה של בלוקinit
באמצעות הצהרת יומן. - בכיתה
ScoreViewModel
, מוסיפים משתנה בשםscore
כדי לשמור את הציון הסופי.
class ScoreViewModel(finalScore: Int) : ViewModel() {
// The final score
var score = finalScore
init {
Log.i("ScoreViewModel", "Final score is $finalScore")
}
}
- מתחת לחבילה
score
, עליך ליצור כיתה נוספת מ-Kotlin שנקראתScoreViewModelFactory
. הכיתה הזו תשמש ליצירת אובייקטScoreViewModel
. - הארכה של הכיתה ב-
ScoreViewModelFactory
מ-ViewModelProvider.Factory
. מוסיפים פרמטר constructor לציון הסופי.
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
. קוראים לשיטהViewModelProviders.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
שמכילים נתונים עבורם:
בקר ממשק משתמש | מודל צפייה |
דוגמה לבקר של ממשק משתמש היא | דוגמה של |
לא מכיל נתונים להצגה בממשק המשתמש. | מכיל נתונים שהבקר של ממשק המשתמש מציג בממשק המשתמש. |
מכיל קוד להצגת נתונים, וקוד אירוע-משתמש כגון פונקציות מסוג listener של קליקים. | מכיל קוד לעיבוד נתונים. |
הריסה ונוצרה מחדש בכל שינוי של הגדרה. | נהרס רק כשבקר ממשק המשתמש המשויך מפסיק לפעול לצמיתות – עבור פעילות מסוימת, כשהפעילות מסתיימת, או עבור שבר של פריט, כשהקטע מנותק. |
מכיל תצוגות. | לעולם לא יכללו הפניות לפעילויות, למקטעים או לצפיות, מפני שהם לא שורדים של שינויים בהגדרות, אך |
מכיל הפניה אל | לא מכיל הפניה לבקר המשויך של ממשק המשתמש. |
קורס אוניברסיטה:
התיעוד של מפתח Android:
- סקירה כללית של ViewModel
- טיפול במחזורי חיים עם רכיבים הקשורים למחזור חיים
- מדריך לארכיטקטורת אפליקציות
ViewModelProvider
ViewModelProvider.Factory
אחר:
- דפוס אדריכלי של MVVM (מודל-view-view).
- עיקרון העיצוב של חששות (SoC)
- דוגמת שיטה של יצרן
בקטע הזה מפורטות מטלות שיעורי בית אפשריות לתלמידים שעובדים עם קוד Lab הזה, במסגרת קורס בהדרכת מורה. למורה יש אפשרות לבצע את הפעולות הבאות:
- אם צריך, מקצים שיעורי בית.
- ספרו לתלמידים איך מגישים מטלות בשיעורי בית.
- לתת ציונים למטלות שיעורי הבית.
המורים יכולים להשתמש בהצעות האלה כמה שפחות, ומומלץ להקצות להן כל שיעורי בית שדעתם מתאימה להם.
אם אתם עובדים בעצמכם על שיעור הקוד הזה, אתם מוזמנים להשתמש במטלות שיעורי הבית האלה כדי לבחון את הידע שלכם.
מענה על השאלות האלה
שאלה 1
כדי לא לאבד נתונים במהלך שינוי של הגדרת מכשיר, צריך לשמור את נתוני האפליקציה בכיתה.
ViewModel
LiveData
Fragment
Activity
שאלה 2
ViewModel
לא יכול לכלול אף הפניה אל מקטעים, פעילויות או צפיות. נכון או לא נכון?
- True
- לא נכון
שאלה 3
מתי ViewModel
נחרב?
- כאשר הבקר המשויך של ממשק המשתמש נהרס ונוצר מחדש במהלך שינוי הכיוון של המכשיר.
- משתנה בכיוון.
- כשהבקר של ממשק המשתמש המשויך מסתיים (אם מדובר בפעילות) או מנותק (אם הוא מקוטע).
- כשהמשתמש לוחץ על הלחצן 'הקודם'.
שאלה 4
למה מיועד הממשק ViewModelFactory
?
- יצירת אובייקט
ViewModel
מתבצעת. - שמירת הנתונים במהלך שינויי הכיוון.
- מתבצע רענון של הנתונים המוצגים במסך.
- קבלת התראות בזמן שינוי נתוני האפליקציה.
התחלת השיעור הבא:
קישורים למעבדות אחרות של הקוד בקורס הזה זמינים בדף הנחיתה של Android Kotlin Fundamentals Codelabs.