נושא זה מתמקד בכמה מההיבטים השימושיים ביותר של שפת Kotlin במהלך הפיתוח ל-Android.
עבודה עם מקטעים
בקטעים הבאים נעשה שימוש ב-Fragment
דוגמאות כדי להדגיש חלק
את התכונות הכי טובות.
ירושה
אפשר להצהיר על כיתה ב-Kotlin באמצעות מילת המפתח class
. בתוך
לדוגמה, LoginFragment
הוא מחלקה משנית של Fragment
. אפשר לציין
ירושה באמצעות האופרטור :
בין מחלקה משנית לבין ההורה שלה:
class LoginFragment : Fragment()
בהצהרה לכיתה הזו, באחריות LoginFragment
לבצע קריאה ל
של מחלקת העל שלו, Fragment
.
תוך LoginFragment
, ניתן יהיה לבטל מספר קריאות חוזרות (callback) של מחזור החיים כדי
תגובה לשינויים במצב בFragment
. כדי לשנות פונקציה, משתמשים בפונקציה
מילת מפתח אחת (override
), כמו בדוגמה הבאה:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.login_fragment, container, false)
}
כדי להפנות לפונקציה במחלקה ההורה, יש להשתמש במילת המפתח super
, כפי שמוצג
בדוגמה הבאה:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
איפוס ואתחול
בדוגמאות הקודמות, חלק מהפרמטרים בשיטות שבוטלו
סוגים מסתיימים בסימן שאלה ?
. זה מציין שהארגומנטים
שהועברה עבור הפרמטרים האלה יכולה להיות null. חשוב
לטפל בבטחה ביכולת ה-null.
ב-Kotlin צריך לאתחל את מאפייני (properties) של אובייקט כשמצהירים על האובייקט.
המשמעות היא שכשמקבלים מופע של כיתה, אפשר מיד
להתייחס לכל אחד מהמאפיינים הנגישים שלו. האובייקטים View
ב-Fragment
,
עם זאת, לא מוכנים לניפוח עד שקוראים ל-Fragment#onCreateView
, לכן
צריכה להיות לך דרך לדחות אתחול נכס עבור View
.
בעזרת lateinit
אפשר לדחות את אתחול הנכס. כשמשתמשים ב-lateinit
,
עליכם לאתחל את הנכס בהקדם האפשרי.
הדוגמה הבאה ממחישה את השימוש ב-lateinit
כדי להקצות View
אובייקטים ב
onViewCreated
:
class LoginFragment : Fragment() {
private lateinit var usernameEditText: EditText
private lateinit var passwordEditText: EditText
private lateinit var loginButton: Button
private lateinit var statusTextView: TextView
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
usernameEditText = view.findViewById(R.id.username_edit_text)
passwordEditText = view.findViewById(R.id.password_edit_text)
loginButton = view.findViewById(R.id.login_button)
statusTextView = view.findViewById(R.id.status_text_view)
}
...
}
המרה של SAM
כדי להאזין לאירועי קליק ב-Android, אפשר להטמיע את
ממשק OnClickListener
. Button
אובייקטים מכילים setOnClickListener()
שלוקחת את ההטמעה של OnClickListener
.
ב-OnClickListener
יש שיטה מופשטת אחת, onClick()
, שצריך
להטמיע. כי setOnClickListener()
תמיד לוקחת OnClickListener
בתור
ארגומנט, ובגלל של-OnClickListener
יש תמיד אותו מופשט יחיד
את ההטמעה הזו אפשר לייצג באמצעות פונקציה אנונימית
Kotlin. התהליך הזה נקרא
Single Avestract Method conversion (המרה אחת בשיטה מופשטת יחידה),
או המרת SAM.
המרת SAM יכולה להפוך את הקוד שלכם לנקי יותר באופן משמעותי. הדוגמה הבאה
מראה איך להשתמש בהמרה של SAM כדי להטמיע OnClickListener
Button
:
loginButton.setOnClickListener {
val authSuccessful: Boolean = viewModel.authenticate(
usernameEditText.text.toString(),
passwordEditText.text.toString()
)
if (authSuccessful) {
// Navigate to next screen
} else {
statusTextView.text = requireContext().getString(R.string.auth_failed)
}
}
הקוד בפונקציה האנונימית שמועבר אל setOnClickListener()
מופעל כשמשתמש לוחץ על loginButton
.
אובייקטים נלווים
אובייקטים נלווים
מספקים מנגנון להגדרת משתנים או פונקציות שמקושרים
מבחינה רעיונית לסוג, אבל לא קשורים לאובייקט מסוים. זוגי
אובייקטים דומים לשימוש במילת המפתח static
של Java למשתנים ושיטות.
בדוגמה הבאה, TAG
הוא קבוע String
. לא צריך שם משתמש ייחודי
של String
בכל מופע של LoginFragment
, כך
מגדירים אותו באובייקט נלווה:
class LoginFragment : Fragment() {
...
companion object {
private const val TAG = "LoginFragment"
}
}
אפשר להגדיר את TAG
ברמה העליונה של הקובץ, אך
יכול להיות שיש גם מספר גדול של משתנים, פונקציות ומחלקות
שמוגדרים גם ברמה העליונה. אובייקטים נלווים עוזרים להתחבר
כולל משתנים, פונקציות ואת הגדרת המחלקה בלי להתייחס
במופע מסוים של המחלקה.
הענקת גישה לנכסים
במהלך אתחול המאפיינים, ייתכן לחזור על חלק
כמו גישה אל ViewModel
בתוך Fragment
. כדי להימנע ממצב של עודף
בקוד כפול, ניתן להשתמש בתחביר הענקת גישה למאפיינים של Kotlin.
private val viewModel: LoginViewModel by viewModels()
'הענקת גישה לנכסים' מספקת הטמעה משותפת שניתן לעשות בה שימוש חוזר
בכל רחבי האפליקציה. מערכת Android KTX מספקת לכם הרשאות גישה מסוימות לנכס.
לדוגמה, viewModels
, מאחזרת ViewModel
בהיקף של
Fragment
הנוכחי.
בהענקת גישה לנכסים נעשה שימוש בהשתקפות, מה שמוסיף תקורת ביצועים מסוימת. ההבדל הוא בתחביר תמציתי שחוסך זמן פיתוח.
ערך אפס
Kotlin עם כללי יכולת ביטול מחמירים ששומרים על בטיחות הסוג לאורך כל התהליך
באפליקציה שלך. ב-Kotlin, הפניות לאובייקטים לא יכולות להכיל ערכי null באמצעות
כברירת מחדל. כדי להקצות ערך null למשתנה, צריך להצהיר על null
מסוג המשתנה, מוסיפים את ?
בסוף סוג הבסיס.
לדוגמה, הביטוי הבא לא חוקי ב-Kotlin. name
הוא מסוג
String
ואי אפשר לבטל אותו:
val name: String = null
כדי לאפשר ערך null, עליך להשתמש בסוג String
(ללא null), String?
, כמו
שמוצגת בדוגמה הבאה:
val name: String? = null
יכולת פעולה הדדית
הכללים המחמירים של Kotlin הופכים את הקוד שלכם לבטוח ותמציתי יותר. הכללים האלה נמוכים יותר
מה הסיכוי לאפליקציה NullPointerException
לקרוס. בנוסף, הן מפחיתות את מספר בדיקות ה-null שצריך לבצע
לעיתים קרובות צריך גם לבצע קריאה לקוד שאינו קוטלין כשכותבים אפליקציה ל-Android, רוב ממשקי ה-API של Android נכתבים בשפת Java.
הערך של Nullability הוא תחום מרכזי שבו ההתנהגות של Java ו-Kotlin שונים. Java היא פחות מחמיר עם תחביר של יכולת null.
לדוגמה, למחלקה Account
יש כמה מאפיינים, כולל String
לנכס שנקרא name
. ל-Java אין את כללי Kotlin בנוגע ליכולת null,
במקום להסתמך על הערות אופציונליות בנושא יכולת null כדי להצהיר במפורש
האם ניתן להקצות ערך null.
מאחר ש-framework של Android כתוב בעיקר ב-Java, ייתכן שתיתקלו במקרה של קריאה לממשקי API ללא הערות לגבי יכולת null.
סוגי פלטפורמות
אם משתמשים ב-Kotlin כדי להפנות לחבר name
ללא הערה, שמוגדר
מחלקה Account
של Java, המהדר לא יודע אם ה-String
ממופה
String
או String?
ב-Kotlin. חוסר בהירות זה מיוצג באמצעות
platform type, String!
.
ל-String!
אין משמעות מיוחדת עבור המהדר של Kotlin. String!
יכול לייצג
String
או String?
, והמהדר מאפשר להקצות את הערך
מכל סוג. לתשומת ליבך, אתה עלול להשליך NullPointerException
אם
שמייצגים את הסוג בתור String
ולהקצות ערך null.
כדי לטפל בבעיה הזו, צריך להשתמש בהערות אפסיות בכל פעם שכותבים ב-Java. ההערות האלה עוזרות גם למפתחי Java וגם מפתחי Kotlin.
לדוגמה, זוהי המחלקה Account
כפי שהיא מוגדרת ב-Java:
public class Account implements Parcelable {
public final String name;
public final String type;
private final @Nullable String accessId;
...
}
לאחד ממשתני החברים, accessId
, נוספו הערות עם @Nullable
,
שמציין שהוא יכול להכיל ערך null. לאחר מכן, Kotlin יטפל ב-accessId
בתור String?
.
כדי לציין שמשתנה מסוים לא יכול להיות null, משתמשים בהערה @NonNull
:
public class Account implements Parcelable {
public final @NonNull String name;
...
}
בתרחיש הזה, name
נחשב כ-String
שאינו אפס (null) ב-Kotlin.
הערות לגבי ביטול נעילה נכללות בכל ממשקי ה-API החדשים של Android ובהרבה אפליקציות קיימות. ממשקי API של Android. ספריות Java רבות הוסיפו הערות עם אפשרות אפסית כדי לשפר את לתמוך במפתחי Kotlin ו-Java.
טיפול ב-null
אם אתם לא בטוחים לגבי סוג Java מסוים, צריך להתייחס אליו כאל null.
לדוגמה, למשתמש name
בכיתה Account
אין הערות, ולכן
צריך להניח שהוא String
מאפשר ערך null.
אם רוצים לחתוך את name
כך שהערך שלו לא יכלול את התחילית או את
בסוף, אפשר להשתמש בפונקציה trim
של Kotlin. אפשר לחתוך בבטחה
String?
בכמה דרכים שונות. אחת מהדרכים האלה היא להשתמש בפונקציה not-null
האופרטור של טענת נכוֹנוּת (assertion), !!
, כמו בדוגמה הבאה:
val account = Account("name", "type")
val accountName = account.name!!.trim()
האופרטור !!
מתייחס לכל מה שמופיע בצד שמאל שלו כאל ערך אפס,
במקרה הזה, את name
מתייחסת בתור String
שאינו null. אם התוצאה של
משמאל לביטוי הזה הוא null, ואז האפליקציה יקפיץ NullPointerException
.
אופרטור זה הוא מהיר וקל, אבל כדאי להשתמש בו באופן מדוד, מכיוון שהוא
להחזיר מופעים של NullPointerException
לקוד.
עדיף להשתמש באופרטור השיחה הבטוחה, ?.
, כמו שמוצג
בדוגמה הבאה:
val account = Account("name", "type")
val accountName = account.name?.trim()
באמצעות אופרטור Safe-call, אם הערך של name
הוא לא null, אז התוצאה של
name?.trim()
הוא ערך שם ללא רווחים לבנים בתחילתו או בסופה. אם המיקום
הערך name
הוא null, אז התוצאה של name?.trim()
היא null
. המשמעות היא
האפליקציה שלך אף פעם לא יכולה לזרוק NullPointerException
במהלך ביצוע ההצהרה הזו.
אופרטור השיחה הבטוחה חוסך לך מNullPointerException
אפשרי,
היא כן מעבירה ערך null להצהרה הבאה. במקום זאת, אפשר לטפל ב-null
במקרים מיידיים באמצעות אופרטור של Elvis (?:
), כפי שמוצג בהסבר הבא
דוגמה:
val account = Account("name", "type")
val accountName = account.name?.trim() ?: "Default name"
אם התוצאה של הביטוי בצד שמאל של האופרטור אלביס היא
null, אז הערך בצד ימין מוקצה ל-accountName
. הזה
כדאי להשתמש בה כדי לספק ערך ברירת מחדל, שאחרת יהיה null.
אפשר גם להשתמש באופרטור Elvis כדי לחזור מפונקציה בשלב מוקדם, כמו שמוצג כאן בדוגמה הבאה:
fun validateAccount(account: Account?) {
val accountName = account?.name?.trim() ?: "Default name"
// account cannot be null beyond this point
account ?: return
...
}
שינויים ב-Android API
ממשקי Android API הופכים יותר ויותר ידידותיים ל-Kotlin. הרבה ממכשירי Android
ממשקי ה-API הנפוצים ביותר, כולל AppCompatActivity
ו-Fragment
, כוללים
הערות לגבי ביטולי זמינות, ושיחות מסוימות כמו Fragment#getContext
יותר ידידותיות ל-Kotlin.
לדוגמה, גישה אל Context
של Fragment
היא כמעט תמיד לא null,
מכיוון שרוב השיחות שיבוצעו בFragment
מתרחשות כאשר Fragment
מצורף אל Activity
(תת-מחלקה של Context
). עם זאת,
Fragment#getContext
לא תמיד מחזירה ערך שאינו null, כי יש
במצבים שבהם Fragment
לא מצורף אל Activity
. כלומר, ההחזרה
הסוג של Fragment#getContext
יכול להיות null.
מכיוון שהערך של Context
שהוחזר מ-Fragment#getContext
הוא null (והוא גם
מופיע כ- @Nullable), עליכם להתייחס אליו בתור Context?
בקוד Kotlin.
כלומר, החלה של אחד מהאופרטורים שהוזכרו קודם לכן
להיות null לפני גישה למאפיינים ולפונקציות שלו. לגבי חלק מאלה
ב-Android יש ממשקי API חלופיים שמספקים את הנוחות הזו.
לדוגמה, Fragment#requireContext
, מחזירה Context
שאינו null ומחזירה
הפונקציה IllegalStateException
מקבלת קריאה כשהערך של Context
הוא null. כך,
אפשר להתייחס לערך של Context
שמתקבל כערך שאינו null ללא צורך
מפעילי שיחה בטוחה או דרכים לעקוף את הבעיה.
אתחול נכס
מאפיינים ב-Kotlin לא מאותחלים כברירת מחדל. צריך לאתחל אותן כשהכיתוב המצורף מאותחל.
יש כמה דרכים לאתחל נכסים. הדוגמה הבאה
שמראה איך לאתחל משתנה index
על ידי הקצאת ערך
הצהרת כיתה:
class LoginFragment : Fragment() {
val index: Int = 12
}
ניתן להגדיר את האתחול הזה גם בבלוק מאתחל:
class LoginFragment : Fragment() {
val index: Int
init {
index = 12
}
}
בדוגמאות שלמעלה, index
מאותחל כאשר LoginFragment
הוא
שנוצרו.
עם זאת, יכול להיות שיש מאפיינים מסוימים שלא ניתן לאתחל במהלך האובייקט
עבודות בדרך. לדוגמה, ייתכן שתרצו להפנות אל View
מתוך
Fragment
, כלומר הפריסה צריכה להיות מנופחת קודם. האינפלציה כן
לא קיימים כאשר Fragment
בנוי. במקום זאת, הוא מנופח כשמתקשרים
Fragment#onCreateView
אחת הדרכים לטפל בתרחיש הזה היא להצהיר שהתצוגה היא כ-null לאתחל אותה בהקדם האפשרי, כפי שמוצג בדוגמה הבאה:
class LoginFragment : Fragment() {
private var statusTextView: TextView? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
statusTextView = view.findViewById(R.id.status_text_view)
statusTextView?.setText(R.string.auth_failed)
}
}
הפעולה הזו עובדת כמו שצריך, אבל עכשיו צריך לנהל את יכולת ה-null של View
בכל פעם שמתייחסים אליהם. פתרון מועיל יותר הוא להשתמש ב-lateinit
בשביל View
כמו בדוגמה הבאה:
class LoginFragment : Fragment() {
private lateinit var statusTextView: TextView
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
statusTextView = view.findViewById(R.id.status_text_view)
statusTextView.setText(R.string.auth_failed)
}
}
מילת המפתח lateinit
מאפשרת לכם להימנע מאתחול נכס כאשר
של האובייקט. אם יש הפניה לנכס לפני האתחול,
Kotlin זורקים UninitializedPropertyAccessException
, אז כדאי לוודא
לאתחל את הנכס שלך בהקדם האפשרי.