‫Android Kotlin Fundamentals 02.4: Data-binding basics

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

מבוא

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

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

ב-Codelab הזה תלמדו איך להשתמש ב-data binding כדי לבטל את הצורך ב-findViewById(). בנוסף, מוסבר איך להשתמש בקשירת נתונים כדי לגשת לנתונים ישירות מתצוגה.

מה שכדאי לדעת

חשוב שתכירו את:

  • מהי פעילות ואיך מגדירים פעילות עם פריסה ב-onCreate().
  • יצירת תצוגת טקסט והגדרת הטקסט שיוצג בתצוגת הטקסט.
  • שימוש ב-findViewById() כדי לקבל הפניה לתצוגה.
  • יצירה ועריכה של פריסת XML בסיסית לתצוגה.

מה תלמדו

  • איך משתמשים בספריית Data Binding כדי לבטל קריאות לא יעילות ל-findViewById().
  • איך ניגשים לנתוני האפליקציה ישירות מ-XML.

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

  • לשנות אפליקציה כך שתשתמש בקשירת נתונים במקום ב-findViewById(), ותגשת לנתונים ישירות מקובצי ה-XML של הפריסה.

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

אלה הפעולות שאפשר לבצע באמצעות האפליקציה 'הכרטיס שלי':

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


אפשר להשתמש בקוד שיצרתם ב-codelab הקודם, או להוריד את הקוד AboutMeDataBinding-Starter מ-GitHub.

הקוד שכתבתם ב-codelabs הקודמים משתמש בפונקציה findViewById() כדי לקבל הפניות לתצוגות.

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

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

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

קישור נתונים מספק את היתרונות הבאים:

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

במשימה הזו מגדירים קשירת נתונים, ומשתמשים בקשירת נתונים כדי להחליף קריאות ל-findViewById() בקריאות לאובייקט הקשירה.

שלב 1: הפעלת קשירת נתונים

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

  1. אם אין לכם את אפליקציית AboutMe מ-codelab קודם, תוכלו לקבל את הקוד AboutMeDataBinding-Starter מ-GitHub. פותחים אותו ב-Android Studio.
  2. פותחים את הקובץ build.gradle (Module: app).
  3. בתוך הקטע android, לפני הסוגר המסולסל הסוגר, מוסיפים קטע dataBinding ומגדירים את enabled ל-true.
dataBinding {
    enabled = true
}
  1. כשמוצגת בקשה, מסנכרנים את הפרויקט. אם לא מוצגת בקשה, בוחרים באפשרות File > Sync Project with Gradle Files (קובץ > סנכרון הפרויקט עם קובצי Gradle).
  2. תוכלו להפעיל את האפליקציה, אבל לא תראו שינויים.

שלב 2: משנים את קובץ הפריסה כך שאפשר יהיה להשתמש בו עם קישור נתונים

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

  1. פותחים את הקובץ activity_main.xml.
  2. עוברים לכרטיסייה טקסט.
  3. מוסיפים את התג <layout></layout> כתג החיצוני ביותר מסביב לתג <LinearLayout>.
<layout>
   <LinearLayout ... >
   ...
   </LinearLayout>
</layout>
  1. כדי לתקן את ההזחה של הקוד, בוחרים באפשרות Code > Reformat code.

    הצהרות מרחב השמות של פריסת דפים צריכות להיות בתג החיצוני ביותר.
  1. גוזרים את ההצהרות של מרחבי השמות מהתג <LinearLayout> ומדביקים אותן בתג <layout>. תג הפתיחה <layout> צריך להיראות כמו בדוגמה שלמטה, ותג הסגירה <LinearLayout> צריך להכיל רק מאפייני תצוגה.
<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
  1. כדי לוודא שהפעולה בוצעה בצורה נכונה, צריך לבנות את האפליקציה ולהפעיל אותה.

שלב 3: יוצרים אובייקט binding בפעילות הראשית

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

  1. פותחים את הקובץ MainActivity.kt.
  2. לפני onCreate(), ברמה העליונה, יוצרים משתנה לאובייקט הקישור. המשתנה הזה נקרא בדרך כלל binding.

    הקומפיילר יוצר את הסוג של binding, המחלקה ActivityMainBinding, במיוחד עבור הפעילות הראשית הזו. השם נגזר מהשם של קובץ הפריסה, כלומר activity_main + Binding.
private lateinit var binding: ActivityMainBinding
  1. אם מוצגת הנחיה ב-Android Studio, מייבאים את ActivityMainBinding. אם לא מוצגת לכם הנחיה, לוחצים על ActivityMainBinding ומקישים על Alt+Enter (או על Option+Enter ב-Mac) כדי לייבא את הכיתה החסרה. (מקשי קיצור נוספים מפורטים במאמר מקשי קיצור).

    ההצהרה import אמורה להיראות כמו ההצהרה שמוצגת בהמשך.
import com.example.android.aboutme.databinding.ActivityMainBinding

לאחר מכן, מחליפים את הפונקציה הנוכחית setContentView() בהוראה שמבצעת את הפעולות הבאות:

  • יוצר את אובייקט הקישור.
  • משתמש בפונקציה setContentView() מהמחלקה DataBindingUtil כדי לשייך את הפריסה activity_main ל-MainActivity. הפונקציה setContentView() הזו מטפלת גם בהגדרה של חלק מהקישור של הנתונים לתצוגות.
  1. ב-onCreate(), מחליפים את הקריאה setContentView() בשורת הקוד הבאה.
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
  1. ייבוא של DataBindingUtil
import androidx.databinding.DataBindingUtil

שלב 4: משתמשים באובייקט הקישור כדי להחליף את כל הקריאות ל-findViewById()

עכשיו אפשר להחליף את כל הקריאות ל-findViewById() בהפניות לתצוגות שנמצאות באובייקט הקישור. כשיוצרים את אובייקט הקישור, הקומפיילר יוצר את שמות התצוגות באובייקט הקישור ממזהי התצוגות בפריסה, וממיר אותם ל-camel case. לדוגמה, done_button הופך ל-doneButton באובייקט הקישור, nickname_edit הופך ל-nicknameEdit ו-nickname_text הופך ל-nicknameText.

  1. ב-onCreate(), מחליפים את הקוד שמשתמש ב-findViewById() כדי למצוא את done_button בקוד שמפנה לכפתור באובייקט הקישור.

    מחליפים את הקוד הזה: findViewById<Button>(R.id.done_button)
    בזה: binding.doneButton

    הקוד הסופי להגדרת מאזין הקליקים ב-onCreate() צריך להיראות כך.
binding.doneButton.setOnClickListener {
   addNickname(it)
}
  1. מבצעים את אותה פעולה לכל הקריאות אל findViewById() בפונקציה addNickname().
    מחליפים את כל המופעים של findViewById<View>(R.id.id_view) ב-binding.idView. כך עושים זאת:
  • מוחקים את ההגדרות של המשתנים editText ו-nicknameTextView, ואת הקריאות שלהם אל findViewById(). במקרה כזה יוצגו שגיאות.
  • כדי לתקן את השגיאות, צריך לקבל את התצוגות nicknameText, nicknameEdit ו-doneButton מהאובייקט binding במקום מהמשתנים (שנמחקו).
  • מחליפים את view.visibility ב-binding.doneButton.visibility. השימוש ב-binding.doneButton במקום ב-view שעבר הופך את הקוד לעקבי יותר.

    התוצאה היא הקוד הבא:
binding.nicknameText.text = binding.nicknameEdit.text
binding.nicknameEdit.visibility = View.GONE
binding.doneButton.visibility = View.GONE
binding.nicknameText.visibility = View.VISIBLE
  • אין שינוי בפונקציונליות. אם רוצים, אפשר להסיר את הפרמטר view ולעדכן את כל השימושים ב-view כך שישתמשו ב-binding.doneButton בתוך הפונקציה הזו.
  1. השדה nicknameText דורש String, והשדה nicknameEdit.text הוא Editable. כשמשתמשים בקישור נתונים, צריך להמיר את Editable ל-String באופן מפורש.
binding.nicknameText.text = binding.nicknameEdit.text.toString()
  1. אפשר למחוק את הייבואים שמוצגים באפור.
  2. משתמשים ב-apply{} כדי להפוך את הפונקציה ל-Kotlin.
binding.apply {
   nicknameText.text = nicknameEdit.text.toString()
   nicknameEdit.visibility = View.GONE
   doneButton.visibility = View.GONE
   nicknameText.visibility = View.VISIBLE
}
  1. מריצים את האפליקציה… והיא אמורה להיראות ולפעול בדיוק כמו קודם.

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

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

שלב 1: יוצרים את סיווג הנתונים MyName

  1. ב-Android Studio, בספרייה java, פותחים את הקובץ MyName.kt. אם הקובץ הזה לא קיים, צריך ליצור קובץ Kotlin חדש ולקרוא לו MyName.kt.
  2. מגדירים סיווג נתונים לשם ולכינוי. משתמשים במחרוזות ריקות כערכי ברירת המחדל.
data class MyName(var name: String = "", var nickname: String = "")

שלב 2: מוסיפים נתונים לפריסה

בקובץ activity_main.xml, השם מוגדר כרגע ב-TextView ממקור מחרוזת. צריך להחליף את ההפניה לשם בהפניה לנתונים במחלקת הנתונים.

  1. פותחים את activity_main.xml בכרטיסייה טקסט.
  2. בחלק העליון של הפריסה, בין התגים <layout> ו-<LinearLayout>, מוסיפים תג <data></data>. כאן מקשרים את התצוגה לנתונים.
<data>
  
</data>

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

  1. בתוך התג <data>, מוסיפים תג <variable>.
  2. מוסיפים פרמטר name כדי לתת למשתנה את השם "myName". מוסיפים פרמטר type ומגדירים את הסוג לשם מלא של מחלקת הנתונים MyName (שם החבילה + שם המשתנה).
<variable
       name="myName"
       type="com.example.android.aboutme.MyName" />

עכשיו, במקום להשתמש במשאב המחרוזת לשם, אפשר להפנות למשתנה myName.

  1. מחליפים את android:text="@string/name" בקוד שמופיע למטה.

@={} היא הנחיה לקבלת הנתונים שמצוינים בתוך הסוגריים המסולסלים.

myName מפנה למשתנה myName שהגדרתם קודם, שמפנה למחלקת הנתונים myName ומאחזר את המאפיין name מהמחלקה.

android:text="@={myName.name}"

שלב 3: יוצרים את הנתונים

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

  1. פותחים את הקובץ MainActivity.kt.
  2. מעל onCreate(), יוצרים משתנה פרטי, שנקרא גם myName לפי המוסכמה. מקצים למשתנה מופע של מחלקת הנתונים MyName ומעבירים את השם.
private val myName: MyName = MyName("Aleks Haecky")
  1. ב-onCreate(), מגדירים את הערך של המשתנה myName בקובץ הפריסה לערך של המשתנה myName שהצהרתם עליו קודם. אי אפשר לגשת למשתנה ישירות ב-XML. צריך לגשת אליו דרך אובייקט הקישור.
binding.myName = myName
  1. יכול להיות שתופיע שגיאה, כי צריך לרענן את אובייקט הקישור אחרי שמבצעים שינויים. צריך לבנות את האפליקציה, ואז השגיאה תיעלם.

שלב 4: שימוש במחלקת הנתונים לכינוי ב-TextView

השלב האחרון הוא להשתמש בסיווג הנתונים גם לכינוי ב-TextView.

  1. פתיחת activity_main.xml.
  2. בתצוגת הטקסט nickname_text, מוסיפים נכס text. אפשר להיעזר בnickname בכיתת הנתונים, כמו שמוצג בהמשך.
android:text="@={myName.nickname}"
  1. ב-ActivityMain, מחליפים את
    nicknameText.text = nicknameEdit.text.toString()
    בקוד להגדרת הכינוי במשתנה myName.
myName?.nickname = nicknameEdit.text.toString()

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

  1. מוסיפים invalidateAll() אחרי שמגדירים את הכינוי, כדי שממשק המשתמש יתרענן עם הערך באובייקט הקישור המעודכן.
binding.apply {
   myName?.nickname = nicknameEdit.text.toString()
   invalidateAll()
   ...
}
  1. מפתחים ומריצים את האפליקציה, והיא אמורה לפעול בדיוק כמו קודם.

פרויקט Android Studio: ‏ AboutMeDataBinding

שלבים לשימוש בקשירת נתונים כדי להחליף קריאות ל-findViewById():

  1. מפעילים את קישור הנתונים בקטע android בקובץ build.gradle:
    dataBinding { enabled = true }
  2. משתמשים ב-<layout> כתצוגת הבסיס בפריסת ה-XML.
  3. הגדרת משתנה של קישור:
    private lateinit var binding: ActivityMainBinding
  4. יוצרים אובייקט קישור ב-MainActivity, ומחליפים את setContentView:
    binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
  5. מחליפים את הקריאות ל-findViewById() בהפניות לתצוגה באובייקט הקישור. לדוגמה:
    findViewById<Button>(R.id.done_button) ⇒ binding.doneButton
    (בדוגמה, השם של התצוגה נוצר בפורמט CamelCase מתוך id של התצוגה ב-XML).

שלבים לקישור תצוגות לנתונים:

  1. יוצרים סיווג נתונים לנתונים.
  2. מוסיפים בלוק <data> בתוך התג <layout>.
  3. מגדירים <variable> עם שם וסוג שהוא מחלקת הנתונים.
<data>
   <variable
       name="myName"
       type="com.example.android.aboutme.MyName" />
</data>
  1. ב-MainActivity, יוצרים משתנה עם מופע של מחלקת הנתונים. לדוגמה:
    private val myName: MyName = MyName("Aleks Haecky")
  1. באובייקט הקישור, מגדירים את המשתנה למשתנה שיצרתם זה עתה:
    binding.myName = myName
  1. בקובץ ה-XML, מגדירים את התוכן של התצוגה למשתנה שהגדרתם בבלוק <data>. משתמשים בסימון נקודות כדי לגשת לנתונים בתוך מחלקת הנתונים.
    android:text="@={myName.name}"

קורס ב-Udacity:

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

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

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

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

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

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

שאלה 1

למה כדאי למזער קריאות מפורשות ומשתמעות ל-findViewById()?

  • בכל פעם שמתבצעת קריאה ל-findViewById(), המערכת עוברת על היררכיית התצוגה.
  • findViewById() פועל ב-thread הראשי או ב-thread של ממשק המשתמש.
  • השיחות האלה עלולות להאט את ממשק המשתמש.
  • הסיכוי שהאפליקציה תקרוס נמוך יותר.

שאלה 2

איך היית מתאר/ת את קישור הנתונים?

לדוגמה, הנה כמה דברים שאפשר לומר על קישור נתונים:

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

שאלה 3

איזו מהאפשרויות הבאות היא לא יתרון של קישור נתונים?

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

שאלה 4

מה התפקיד של התג <layout>?

  • עוטפים את תצוגת הבסיס בפריסה.
  • נוצרים Binding לכל התצוגות בפריסה.
  • הוא מציין את התצוגה ברמה העליונה בפריסת XML שמשתמשת בקשירת נתונים.
  • אפשר להשתמש בתג <data> בתוך <layout> כדי לקשור משתנה למחלקת נתונים.

שאלה 5

מהי הדרך הנכונה להפנות לנתונים שמוגדרים בפריסת ה-XML?

  • android:text="@={myDataClass.property}"
  • android:text="@={myDataClass}"
  • android:text="@={myDataClass.property.toString()}"
  • android:text="@={myDataClass.bound_data.property}"

עוברים לשיעור הבא: 3.1: יצירת מקטע

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