‫Android Kotlin Fundamentals 05.2: LiveData and LiveData observers

ה-codelab הזה הוא חלק מהקורס Android Kotlin Fundamentals. כדי להפיק את המרב מהקורס הזה, מומלץ לעבוד על ה-codelabs לפי הסדר. כל ה-codelab של הקורס מפורטים בדף הנחיתה של ה-codelab בנושא יסודות Kotlin ל-Android.

מבוא

ב-codelab הקודם השתמשתם ב-ViewModel באפליקציית GuessTheWord כדי לאפשר לנתוני האפליקציה לשרוד שינויים בהגדרות המכשיר. ב-codelab הזה תלמדו איך לשלב את LiveData עם הנתונים בכיתות ViewModel. ‫LiveData, שהוא אחד מרכיבי הארכיטקטורה של Android, מאפשר ליצור אובייקטים של נתונים ששולחים הודעות לתצוגות כשיש שינויים במסד הנתונים הבסיסי.

כדי להשתמש במחלקה LiveData, מגדירים 'צופים' (לדוגמה, פעילויות או קטעים) שעוקבים אחרי שינויים בנתונים של האפליקציה. ‫LiveData מודע למחזור החיים, ולכן הוא מעדכן רק את רכיבי האפליקציה שנמצאים במצב פעיל במחזור החיים.

מה שכדאי לדעת

  • איך ליצור אפליקציות בסיסיות ל-Android ב-Kotlin.
  • איך עוברים בין יעדים באפליקציה.
  • מחזור החיים של פעילות ושל קטע.
  • איך משתמשים באובייקטים מסוג ViewModel באפליקציה.
  • איך יוצרים אובייקטים מסוג ViewModel באמצעות הממשק של ViewModelProvider.Factory.

מה תלמדו

  • מה הופך אובייקטים של LiveData לשימושיים.
  • איך מוסיפים LiveData לנתונים שמאוחסנים ב-ViewModel.
  • מתי ואיך להשתמש ב-MutableLiveData.
  • איך מוסיפים שיטות של תצפית כדי לעקוב אחרי שינויים ב-LiveData.
  • איך מבצעים אנקפסולציה של LiveData באמצעות מאפיין גיבוי.
  • איך מתקשרים בין בקר ממשק משתמש לבין ViewModel התואם שלו.

הפעולות שתבצעו:

  • משתמשים ב-LiveData למילה ולניקוד באפליקציית GuessTheWord.
  • מוסיפים משקיפים שישימו לב אם המילה או הניקוד משתנים.
  • עדכון תצוגות הטקסט שבהן מוצגים ערכים שהשתנו.
  • משתמשים בתבנית LiveData observer כדי להוסיף אירוע של סיום משחק.
  • מטמיעים את הלחצן הפעלה חוזרת.

בשיעור 5 של Codelab, מפתחים את האפליקציה GuessTheWord, החל מקוד התחלתי. ‫GuessTheWord הוא משחק בסגנון ניחוש מילים לשני שחקנים, שבו השחקנים משתפים פעולה כדי להשיג את הניקוד הגבוה ביותר האפשרי.

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

כדי לשחק במשחק, השחקן הראשון פותח את האפליקציה במכשיר ורואה מילה, למשל 'גיטרה', כמו שמוצג בצילום המסך שלמטה.

השחקן הראשון מדגים את המילה, ומשתדל לא לומר אותה.

  • כשהשחקן השני מנחש את המילה נכון, השחקן הראשון לוחץ על הלחצן Got It (הבנתי), מה שמגדיל את הספירה באחד ומציג את המילה הבאה.
  • אם השחקן השני לא מצליח לנחש את המילה, השחקן הראשון לוחץ על הכפתור דילוג, מה שמקטין את המספר באחד ומדלג למילה הבאה.
  • כדי לסיים את המשחק, לוחצים על הלחצן סיום המשחק. (הפונקציונליות הזו לא מופיעה בקוד ההתחלתי של ה-codelab הראשון בסדרה).

ב-codelab הזה, תשפרו את האפליקציה GuessTheWord על ידי הוספת אירוע לסיום המשחק כשמשתמש עובר על כל המילים באפליקציה. תוסיפו גם לחצן Play Again (משחק חוזר) בקטע של הניקוד, כדי שהמשתמש יוכל לשחק שוב.

מסך – כותרת

מסך המשחק

מסך הציון

במשימה הזו תאתרו את קוד ההתחלה של ה-codelab ותפעילו אותו. אתם יכולים להשתמש באפליקציית GuessTheWord שיצרתם ב-codelab הקודם כקוד התחלתי, או להוריד אפליקציית התחלה.

  1. (אופציונלי) אם אתם לא משתמשים בקוד מה-codelab הקודם, הורידו את קוד ההתחלה ל-codelab הזה. מחלצים את הקוד ופותחים את הפרויקט ב-Android Studio.
  2. מפעילים את האפליקציה ומשחקים במשחק.
  3. שימו לב שהמילה הבאה מוצגת בלחיצה על הלחצן דילוג והניקוד יורד באחת, ובלחיצה על הלחצן הבנתי המילה הבאה מוצגת והניקוד עולה באחת. המשחק מסתיים כשלוחצים על הכפתור סיום המשחק.

LiveData הוא מחזיק נתונים שניתן לצפייה בו, שמודע למחזור החיים של האפליקציה. לדוגמה, אפשר להוסיף תגי LiveData סביב הניקוד הנוכחי באפליקציית GuessTheWord. ב-codelab הזה תלמדו על כמה מאפיינים של LiveData:

  • LiveData הוא אובייקט שניתן לצפייה, כלומר, צופה מקבל הודעה כשהנתונים שמוחזקים באובייקט LiveData משתנים.
  • LiveData מחזיק נתונים; LiveData הוא wrapper שאפשר להשתמש בו עם כל נתון
  • LiveData מודע למחזור החיים, כלומר הוא מעדכן רק צופים שנמצאים במצב פעיל במחזור החיים, כמו STARTED או RESUMED.

במשימה הזו תלמדו איך להוסיף כל סוג נתונים לאובייקטים של LiveData על ידי המרת הנתונים של הניקוד הנוכחי והמילה הנוכחית ב-GameViewModel ל-LiveData. במשימה מאוחרת יותר, תוסיפו משקיף לאובייקטים האלה של LiveData ותלמדו איך לצפות ב-LiveData.

שלב 1: שינוי הניקוד והמילה כדי להשתמש ב-LiveData

  1. בקטע screens/game, פותחים את הקובץ GameViewModel.
  2. משנים את הסוג של המשתנים score ו-word ל-MutableLiveData.‫

    MutableLiveData הוא LiveData שאפשר לשנות את הערך שלו. ‫MutableLiveData הוא מחלקה גנרית, ולכן צריך לציין את סוג הנתונים שהיא מכילה.
// The current word
val word = MutableLiveData<String>()
// The current score
val score = MutableLiveData<Int>()
  1. ב-GameViewModel, בתוך הבלוק init, מאתחלים את score ואת word. כדי לשנות את הערך של משתנה LiveData, משתמשים בשיטה setValue() במשתנה. ב-Kotlin, אפשר לקרוא ל-setValue() באמצעות המאפיין value.
init {

   word.value = ""
   score.value = 0
  ...
}

שלב 2: מעדכנים את ההפניה לאובייקט LiveData

המשתנים score ו-word הם עכשיו מסוג LiveData. בשלב הזה, משנים את ההפניות למשתנים האלה באמצעות המאפיין value.

  1. ב-GameViewModel, בשיטה onSkip(), משנים את score ל-score.value. שימו לב לשגיאה לגבי score שאולי הוא null. בשלב הבא תתקנו את השגיאה הזו.
  2. כדי לפתור את השגיאה, מוסיפים null check to score.value ב-onSkip(). אחר כך קוראים לפונקציה minus() ב-score, שמבצעת את החיסור עם null-safety.
fun onSkip() {
   if (!wordList.isEmpty()) {
       score.value = (score.value)?.minus(1)
   }
   nextWord()
}
  1. מעדכנים את השיטה onCorrect() באותו אופן: מוסיפים בדיקה של null למשתנה score ומשתמשים בפונקציה plus().
fun onCorrect() {
   if (!wordList.isEmpty()) {
       score.value = (score.value)?.plus(1)
   }
   nextWord()
}
  1. ב-GameViewModel, בתוך ה-method‏ nextWord(), משנים את ההפניה word ל-word.value.
private fun nextWord() {
   if (!wordList.isEmpty()) {
       //Select and remove a word from the list
       word.value = wordList.removeAt(0)
   }
}
  1. ב-GameFragment, בתוך השיטה updateWordText(), משנים את ההפניה אל viewModel.word ל-viewModel.word.value.
/** Methods for updating the UI **/
private fun updateWordText() {
   binding.wordText.text = viewModel.word.value
}
  1. ב-GameFragment, בתוך השיטה updateScoreText(), משנים את ההפניה אל viewModel.score ל-viewModel.score.value.
private fun updateScoreText() {
   binding.scoreText.text = viewModel.score.value.toString()
}
  1. ב-GameFragment, בתוך ה-method‏ gameFinished(), משנים את ההפניה אל viewModel.score ל-viewModel.score.value. מוסיפים את null – הטיימר לדיווח אוטומטי.
private fun gameFinished() {
   Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
   val action = GameFragmentDirections.actionGameToScore()
   action.score = viewModel.score.value?:0
   NavHostFragment.findNavController(this).navigate(action)
}
  1. מוודאים שאין שגיאות בקוד. קומפילציה והפעלה של האפליקציה. הפונקציונליות של האפליקציה צריכה להיות זהה למה שהייתה קודם.

המשימה הזו קשורה קשר הדוק למשימה הקודמת, שבה המרתם את הציון ואת נתוני המילים לאובייקטים מסוג LiveData. במשימה הזו, תצרפו אובייקטים של Observer לאובייקטים של LiveData.

  1. ב-GameFragment, בתוך השיטה onCreateView(), מצרפים אובייקט Observer לאובייקט LiveData של הניקוד הנוכחי, viewModel.score. משתמשים בשיטה observe() ומציבים את הקוד אחרי האתחול של viewModel. שימוש בביטוי למבדה כדי לפשט את הקוד. (ביטוי למדא הוא פונקציה אנונימית שלא מוצהרת, אבל מועברת באופן מיידי כביטוי).
viewModel.score.observe(this, Observer { newScore ->
})

צריך לפתור את הבעיה שקשורה לקובץ העזר Observer. כדי לעשות זאת, לוחצים על Observer, מקישים על Alt+Enter (Option+Enter ב-Mac) ומייבאים את androidx.lifecycle.Observer.

  1. האובייקט observer שיצרתם מקבל אירוע כשהנתונים שמוחזקים באובייקט LiveData שנצפה משתנים. בתוך האובייקט observer, מעדכנים את הציון TextView עם הציון החדש.
/** Setting up LiveData observation relationship **/
viewModel.score.observe(this, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})
  1. מצרפים אובייקט Observer לאובייקט LiveData הנוכחי. מבצעים את הפעולה באותו אופן שבו צירפתם אובייקט Observer לציון הנוכחי.
/** Setting up LiveData observation relationship **/
viewModel.word.observe(this, Observer { newWord ->
   binding.wordText.text = newWord
})

כשערך score או word משתנה, הערך של score או word שמוצג במסך מתעדכן אוטומטית.

  1. ב-GameFragment, מוחקים את השיטות updateWordText() ו-updateScoreText() ואת כל ההפניות אליהן. אתם לא צריכים אותם יותר, כי תצוגות הטקסט מתעדכנות על ידי LiveData שיטות האובזרבר.
  2. מריצים את האפליקציה. אפליקציית המשחק אמורה לפעול בדיוק כמו קודם, אבל עכשיו היא משתמשת ב-LiveData וב-LiveData observers.

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

בקוד הנוכחי, כל מחלקה חיצונית יכולה לשנות את המשתנים score ו-word באמצעות המאפיין value, לדוגמה באמצעות viewModel.score.value. יכול להיות שזה לא משנה באפליקציה שאתם מפתחים ב-codelab הזה, אבל באפליקציה בסביבת הייצור, אתם רוצים שליטה בנתונים באובייקטים ViewModel.

רק ViewModel צריך לערוך את הנתונים באפליקציה. אבל רכיבי בקרה של ממשק המשתמש צריכים לקרוא את הנתונים, ולכן שדות הנתונים לא יכולים להיות פרטיים לחלוטין. כדי להצפין את הנתונים של האפליקציה, משתמשים באובייקטים MutableLiveData ו-LiveData.

MutableLiveData נגד LiveData:

  • כמו שאפשר להבין מהשם, אפשר לשנות את הנתונים באובייקט MutableLiveData. בתוך ViewModel, הנתונים צריכים להיות ניתנים לעריכה, ולכן נעשה שימוש ב-MutableLiveData.
  • אפשר לקרוא את הנתונים באובייקט LiveData, אבל אי אפשר לשנות אותם. מחוץ ל-ViewModel, הנתונים צריכים להיות ניתנים לקריאה אבל לא לעריכה, ולכן הם צריכים להיות חשופים כ-LiveData.

כדי ליישם את האסטרטגיה הזו, משתמשים במאפיין גיבוי של Kotlin. נכס גיבוי מאפשר להחזיר מ-getter משהו שאינו האובייקט המדויק. במשימה הזו מטמיעים נכס תומך לאובייקטים score ו-word באפליקציה GuessTheWord.

הוספת מאפיין גיבוי לציון ולמילה

  1. ב-GameViewModel, מגדירים את האובייקט הנוכחי score כ-private.
  2. כדי לפעול לפי המוסכמה למתן שמות שמשמשת במאפייני הגיבוי, משנים את score ל-_score. המאפיין _score הוא עכשיו הגרסה הניתנת לשינוי של ניקוד המשחק, והוא מיועד לשימוש פנימי.
  3. יוצרים גרסה ציבורית של הסוג LiveData, שנקראת score.
// The current score
private val _score = MutableLiveData<Int>()
val score: LiveData<Int>
  1. מוצגת שגיאת אתחול. השגיאה הזו מתרחשת כי בתוך GameFragment, score הוא הפניה ל-LiveData, ול-score כבר אין גישה לשיטת ה-setter שלו. מידע נוסף על getters ו-setters ב-Kotlin זמין במאמר Getters and Setters.

    כדי לפתור את השגיאה, צריך לבטל את השיטה get() לאובייקט score ב-GameViewModel ולהחזיר את מאפיין הגיבוי, _score.
val score: LiveData<Int>
   get() = _score
  1. ב-GameViewModel, משנים את ההפניות של score לגרסה הפנימית שניתנת לשינוי, _score.
init {
   ...
   _score.value = 0
   ...
}

...
fun onSkip() {
   if (!wordList.isEmpty()) {
       _score.value = (score.value)?.minus(1)
   }
  ...
}

fun onCorrect() {
   if (!wordList.isEmpty()) {
       _score.value = (score.value)?.plus(1)
   }
   ...
}
  1. משנים את השם של האובייקט word ל-_word ומוסיפים לו מאפיין גיבוי, כמו שעשיתם לאובייקט score.
// The current word
private val _word = MutableLiveData<String>()
val word: LiveData<String>
   get() = _word
...
init {
   _word.value = ""
   ...
}
...
private fun nextWord() {
   if (!wordList.isEmpty()) {
       //Select and remove a word from the list
       _word.value = wordList.removeAt(0)
   }
}

כל הכבוד, ביצעת את האנקפסולציה של האובייקטים LiveData ו-word.score

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

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

דפוס התצפית

תבנית Observer היא תבנית עיצוב תוכנה. הוא מגדיר תקשורת בין אובייקטים: אובייקט שניתן לצפייה (ה'נושא' של הצפייה) ואובייקטים צופים. משתנה ניתן לצפייה הוא אובייקט שמודיע לצופים על השינויים במצב שלו.

במקרה של LiveData באפליקציה הזו, האובייקט שאפשר לצפות בו (הנושא) הוא אובייקט LiveData, והצופים הם השיטות בבקרי ממשק המשתמש, כמו fragments. שינוי במצב מתרחש בכל פעם שהנתונים שמוקפים בתגי LiveData משתנים. המחלקות LiveData חיוניות לתקשורת מ-ViewModel אל ה-Fragment.

שלב 1: שימוש ב-LiveData כדי לזהות אירוע של סיום משחק

במשימה הזו משתמשים בתבנית LiveData observer כדי ליצור מודל של אירוע סיום משחק.

  1. ב-GameViewModel, יוצרים אובייקט Boolean MutableLiveData בשם _eventGameFinish. האובייקט הזה יכיל את אירוע סיום המשחק.
  2. אחרי שמאתחלים את האובייקט _eventGameFinish, יוצרים ומאתחלים נכס תומך בשם eventGameFinish.
// Event which triggers the end of the game
private val _eventGameFinish = MutableLiveData<Boolean>()
val eventGameFinish: LiveData<Boolean>
   get() = _eventGameFinish
  1. ב-GameViewModel, מוסיפים אמצעי תשלום onGameFinish(). בשיטה, מגדירים את האירוע של סיום המשחק, eventGameFinish, לערך true.
/** Method for the game completed event **/
fun onGameFinish() {
   _eventGameFinish.value = true
}
  1. ב-GameViewModel, בתוך השיטה nextWord(), מסיימים את המשחק אם רשימת המילים ריקה.
private fun nextWord() {
   if (wordList.isEmpty()) {
       onGameFinish()
   } else {
       //Select and remove a _word from the list
       _word.value = wordList.removeAt(0)
   }
}
  1. ב-GameFragment, בתוך onCreateView(), אחרי הפעלת viewModel, מצרפים משקיף ל-eventGameFinish. משתמשים בשיטה observe(). בתוך פונקציית ה-lambda, קוראים לשיטה gameFinished().
// Observer for the Game finished event
viewModel.eventGameFinish.observe(this, Observer<Boolean> { hasFinished ->
   if (hasFinished) gameFinished()
})
  1. מריצים את האפליקציה, משחקים במשחק ועוברים על כל המילים. האפליקציה עוברת למסך התוצאות באופן אוטומטי, במקום להישאר בקטע של המשחק עד שמקישים על סיום המשחק.

    אחרי שרשימת המילים ריקה, הערך של eventGameFinish מוגדר, שיטת הצפייה המשויכת בקטע של המשחק נקראת, והאפליקציה עוברת לקטע של המסך.
  2. הקוד שהוספת גרם לבעיה במחזור החיים. כדי להבין את הבעיה, בכיתה GameFragment, מוסיפים הערה לקוד הניווט בשיטה gameFinished(). חשוב להשאיר את ההודעה Toast בשיטה.
private fun gameFinished() {
       Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
//        val action = GameFragmentDirections.actionGameToScore()
//        action.score = viewModel.score.value?:0
//        NavHostFragment.findNavController(this).navigate(action)
   }
  1. מריצים את האפליקציה, משחקים במשחק ועוברים על כל המילים. הודעה קצרה שמופיעה בתחתית מסך המשחק ובה כתוב 'המשחק הסתיים' – זהו ההתנהגות הצפויה.

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

שלב 2: מאפסים את האירוע game-finished

בדרך כלל, LiveData מעביר עדכונים לצופים רק כשיש שינויים בנתונים. יוצא מן הכלל הוא שגם משתמשים עם הרשאת צפייה מקבלים עדכונים כשהם משנים את הסטטוס שלהם מ'לא פעיל' ל'פעיל'.

זו הסיבה לכך שההודעה הקופצת על סיום המשחק מופעלת שוב ושוב באפליקציה. כשיוצרים מחדש את קטע המשחק אחרי סיבוב המסך, הוא עובר ממצב לא פעיל למצב פעיל. ה-observer בקטע מתחבר מחדש ל-ViewModel הקיים ומקבל את הנתונים הנוכחיים. השיטה gameFinished() מופעלת מחדש, וההודעה הקופצת מוצגת.

במשימה הזו אתם פותרים את הבעיה ומציגים את ההודעה הקופצת רק פעם אחת, על ידי איפוס הדגל eventGameFinish ב-GameViewModel.

  1. ב-GameViewModel, מוסיפים method onGameFinishComplete() כדי לאפס את אירוע סיום המשחק, _eventGameFinish.
/** Method for the game completed event **/

fun onGameFinishComplete() {
   _eventGameFinish.value = false
}
  1. בGameFragment, בסוף gameFinished(), תתקשר אל onGameFinishComplete() באובייקט viewModel. (בינתיים, משאירים את קוד הניווט ב-gameFinished() כהערה).
private fun gameFinished() {
   ...
   viewModel.onGameFinishComplete()
}
  1. מפעילים את האפליקציה ומשחקים במשחק. קוראים את כל המילים, ואז משנים את כיוון המסך של המכשיר. ההודעה הקופצת מוצגת רק פעם אחת.
  2. ב-GameFragment, בתוך השיטה gameFinished(), מבטלים את ההערה של קוד הניווט.

    כדי לבטל את ההערה ב-Android Studio, בוחרים את השורות שהוגדרו כהערה ומקישים על Control+/ (Command+/ ב-Mac).
private fun gameFinished() {
   Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
   val action = GameFragmentDirections.actionGameToScore()
   action.score = viewModel.score.value?:0
   findNavController(this).navigate(action)
   viewModel.onGameFinishComplete()
}

אם מוצגת הנחיה ב-Android Studio, מייבאים את androidx.navigation.fragment.NavHostFragment.findNavController.

  1. מפעילים את האפליקציה ומשחקים במשחק. מוודאים שהאפליקציה עוברת אוטומטית למסך הניקוד הסופי אחרי שמעבירים את כל המילים.

מעולה! האפליקציה משתמשת ב-LiveData כדי להפעיל אירוע של סיום המשחק, כדי להעביר מה-GameViewModel אל קטע המשחק שהרשימה של המילים ריקה. לאחר מכן, קטע המשחק עובר לקטע התוצאה.

במשימה הזו, משנים את הציון לאובייקט LiveData ב-ScoreViewModel ומצרפים אליו צופה. המשימה הזו דומה למה שעשית כששילבת את LiveData בGameViewModel.

אתם מבצעים את השינויים האלה ב-ScoreViewModel כדי שהנתונים יהיו מלאים, כך שכל הנתונים באפליקציה ישתמשו ב-LiveData.

  1. ב-ScoreViewModel, משנים את סוג המשתנה score ל-MutableLiveData. משנים את השם שלו בהתאם למוסכמה ל-_score ומוסיפים נכס תומך.
private val _score = MutableLiveData<Int>()
val score: LiveData<Int>
   get() = _score
  1. ב-ScoreViewModel, בתוך הבלוק init, מאתחלים את _score. אתם יכולים להסיר את היומן או להשאיר אותו בחסימה init לפי הצורך.
init {
   _score.value = finalScore
}
  1. ב-ScoreFragment, בתוך onCreateView(), אחרי הפעלת viewModel, מצרפים אובייקט של צופה לציון LiveData. בתוך ביטוי ה-lambda, מגדירים את ערך הניקוד לתצוגת הטקסט של הניקוד. מסירים את הקוד שמשייך ישירות את תצוגת הטקסט לערך הניקוד מ-ViewModel.

הקוד שצריך להוסיף:

// Add observer for score
viewModel.score.observe(this, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})

קוד להסרה:

binding.scoreText.text = viewModel.score.toString()

כשמופיעה בקשה מ-Android Studio, מייבאים את androidx.lifecycle.Observer.

  1. מפעילים את האפליקציה ומשחקים במשחק. האפליקציה אמורה לפעול כמו קודם, אבל עכשיו היא משתמשת ב-LiveData וב-observer כדי לעדכן את הניקוד.

במשימה הזו מוסיפים לחצן Play Again למסך התוצאה ומטמיעים את ה-click listener שלו באמצעות אירוע LiveData. הלחצן מפעיל אירוע שגורם למעבר ממסך הניקוד למסך המשחק.

קוד ההתחלה של האפליקציה כולל את הלחצן Play Again, אבל הלחצן מוסתר.

  1. ב-res/layout/score_fragment.xml, בלחצן play_again_button, משנים את הערך של מאפיין visibility ל-visible.
<Button
   android:id="@+id/play_again_button"
...
   android:visibility="visible"
 />
  1. ב-ScoreViewModel, מוסיפים אובייקט LiveData כדי להחזיק Boolean בשם _eventPlayAgain. האובייקט הזה משמש לשמירת האירוע LiveData כדי לנווט ממסך הניקוד למסך המשחק.
private val _eventPlayAgain = MutableLiveData<Boolean>()
val eventPlayAgain: LiveData<Boolean>
   get() = _eventPlayAgain
  1. ב-ScoreViewModel, מגדירים שיטות להגדרה ולאיפוס של האירוע, _eventPlayAgain.
fun onPlayAgain() {
   _eventPlayAgain.value = true
}
fun onPlayAgainComplete() {
   _eventPlayAgain.value = false
}
  1. ב-ScoreFragment, מוסיפים משקיף ל-eventPlayAgain. ממקמים את הקוד בסוף onCreateView(), לפני ההצהרה return. בתוך ביטוי ה-lambda, חוזרים למסך המשחק ומאפסים את eventPlayAgain.
// Navigates back to game when button is pressed
viewModel.eventPlayAgain.observe(this, Observer { playAgain ->
   if (playAgain) {
      findNavController().navigate(ScoreFragmentDirections.actionRestart())
       viewModel.onPlayAgainComplete()
   }
})

מייבאים את androidx.navigation.fragment.findNavController כשמוצגת הנחיה ב-Android Studio.

  1. ב-ScoreFragment, בתוך onCreateView(), מוסיפים מאזין לקליקים ללחצן PlayAgain וקוראים ל-viewModel.onPlayAgain().
binding.playAgainButton.setOnClickListener {  viewModel.onPlayAgain()  }
  1. מפעילים את האפליקציה ומשחקים במשחק. בסיום המשחק, במסך התוצאות מוצגת התוצאה הסופית והלחצן Play Again (משחק חוזר). מקישים על הלחצן PlayAgain (משחק חוזר), והאפליקציה עוברת למסך המשחק כדי שתוכלו לשחק שוב.

כל הכבוד! שינית את הארכיטקטורה של האפליקציה כדי להשתמש באובייקטים של LiveData ב-ViewModel, וצירפת משתנים מסוג Observer לאובייקטים של LiveData. ‫LiveData מודיע לאובייקטים של הצופה כשהערך שמוחזק על ידי LiveData משתנה.

פרויקט Android Studio: ‏ GuessTheWord

LiveData

  • LiveData הוא מחזיק נתונים שניתן לצפייה בו, מודע למחזור החיים ואחד מרכיבי הארכיטקטורה של Android.
  • אתם יכולים להשתמש ב-LiveData כדי לאפשר לממשק המשתמש להתעדכן באופן אוטומטי כשהנתונים מתעדכנים.
  • אפשר לצפות ב-LiveData, כלומר אפשר להודיע לצופה כמו פעילות או קטע כשהנתונים שמוחזקים על ידי אובייקט LiveData משתנים.
  • LiveData מחזיק נתונים; הוא עוטף נתונים שאפשר להשתמש בהם עם כל נתון.
  • LiveData מודע למחזור החיים, כלומר הוא מעדכן רק צופים שנמצאים במצב פעיל במחזור החיים, כמו STARTED או RESUMED.

כדי להוסיף LiveData

  • משנים את הסוג של משתני הנתונים ב-ViewModel ל-LiveData או ל-MutableLiveData.

MutableLiveData הוא אובייקט LiveData שאפשר לשנות את הערך שלו. ‫MutableLiveData הוא מחלקה גנרית, ולכן צריך לציין את סוג הנתונים שהיא מכילה.

  • כדי לשנות את ערך הנתונים שמוחזק על ידי LiveData, משתמשים בשיטה setValue() במשתנה LiveData.

כדי להשתמש ב-LiveData

  • אפשר לערוך את LiveData בתוך ViewModel. מחוץ ל-ViewModel, צריך להיות אפשר לקרוא את LiveData. אפשר להטמיע את זה באמצעות נכס גיבוי של Kotlin.
  • מאפיין גיבוי ב-Kotlin מאפשר להחזיר מ-getter משהו שאינו האובייקט המדויק.
  • כדי להוסיף את LiveData, משתמשים ב-private MutableLiveData בתוך ViewModel ומחזירים מאפיין גיבוי של LiveData מחוץ ל-ViewModel.

‫LiveData שניתן לצפייה

  • LiveData פועל לפי תבנית observer. האובייקט LiveData הוא ה-observable, וה-observers הם השיטות בבקרי ממשק המשתמש, כמו fragments. בכל פעם שהנתונים שמוקפים בתגי LiveData משתנים, שיטות האובייקט המתבונן בבקרי ממשק המשתמש מקבלות הודעה.
  • כדי להפוך את LiveData לניתן לצפייה, צריך לצרף אובייקט צופה להפניה LiveData בצופים (כמו פעילויות וקטעים) באמצעות השיטה observe().
  • אפשר להשתמש בדפוס התצפית LiveData הזה כדי לתקשר מה-ViewModel לבקרי ממשק המשתמש.

קורס ב-Udacity:

מסמכי תיעוד למפתחי Android:

אחר:

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

  • אם צריך, מקצים שיעורי בית.
  • להסביר לתלמידים איך להגיש מטלות.
  • בודקים את שיעורי הבית.

אנשי ההוראה יכולים להשתמש בהצעות האלה כמה שרוצים, ומומלץ להם להקצות כל שיעורי בית אחרים שהם חושבים שמתאימים.

אם אתם עובדים על ה-codelab הזה לבד, אתם יכולים להשתמש במשימות האלה כדי לבדוק את הידע שלכם.

עונים על השאלות הבאות

שאלה 1

איך אפשר להצפין את LiveData שמאוחסן ב-ViewModel כך שאובייקטים חיצוניים יוכלו לקרוא את הנתונים בלי אפשרות לעדכן אותם?

  • באובייקט ViewModel, משנים את סוג הנתונים של הנתונים ל-private LiveData. משתמשים בנכס תומך כדי לחשוף נתונים לקריאה בלבד מהסוג MutableLiveData.
  • באובייקט ViewModel, משנים את סוג הנתונים של הנתונים ל-private MutableLiveData. משתמשים בנכס תומך כדי לחשוף נתונים לקריאה בלבד מהסוג LiveData.
  • בתוך בקר ממשק המשתמש, משנים את סוג הנתונים של הנתונים ל-private MutableLiveData. משתמשים בנכס תומך כדי לחשוף נתונים לקריאה בלבד מהסוג LiveData.
  • באובייקט ViewModel, משנים את סוג הנתונים של הנתונים ל-LiveData. משתמשים בנכס תומך כדי לחשוף נתונים לקריאה בלבד מהסוג LiveData.

שאלה 2

LiveData מעדכן בקר ממשק משתמש (כמו fragment) אם בקר ממשק המשתמש נמצא באחד מהמצבים הבאים:

  • המשכת הפעולה של
  • ברקע
  • בהשהיה
  • נעצר

שאלה 3

בLiveData תבנית Observer, מהו הפריט שניתן לצפייה (מה נצפה)?

  • שיטת הצופה
  • הנתונים באובייקט LiveData
  • הבקר של ממשק המשתמש
  • האובייקט ViewModel

עוברים לשיעור הבא: 5.3: Data binding with ViewModel and LiveData

קישורים ל-codelabs אחרים בקורס הזה מופיעים בדף הנחיתה של ה-codelabs בנושא יסודות Android Kotlin.