يركز هذا الموضوع على بعض الجوانب الأكثر فائدة في لغة Kotlin عند تطوير التطبيقات لنظام Android.
التعامل مع الأجزاء
تستخدم الأقسام التالية أمثلة Fragment
لتسليط الضوء على بعض أفضل ميزات لغة Kotlin.
الاكتساب
يمكنك الإعلان عن فئة في Kotlin باستخدام الكلمة الرئيسية class
. في المثال التالي، LoginFragment
هي فئة فرعية من Fragment
. يمكنك الإشارة إلى الوراثة باستخدام عامل التشغيل :
بين الفئة الفرعية وعنصرها الرئيسي:
class LoginFragment : Fragment()
في تعريف الفئة هذا، تكون LoginFragment
مسؤولة عن استدعاء الدالة الإنشائية للفئة الفائقة Fragment
الخاصة بها.
ضمن LoginFragment
، يمكنك إلغاء عدد من عمليات معاودة الاتصال خلال مراحل النشاط للاستجابة إلى تغييرات الحالة في 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)
}
قابلية الإبطال والإعداد
في الأمثلة السابقة، تتضمّن بعض المعلمات في الطرق التي تم تجاوزها أنواعًا لاحقة بعلامة استفهام ?
. يشير هذا إلى أن الوسيطات التي تم تمريرها لهذه المعلمات يمكن أن تكون فارغة. احرص على
التعامل مع قيم إبطال القيم بأمان.
في لغة Kotlin، يجب إعداد خصائص الكائن عند الإعلان عن الكائن.
وهذا يعني أنه عند الحصول على مثيل من فئة، يمكنك على الفور الرجوع إلى أي من خصائصها التي يمكن الوصول إليها. ومع ذلك، لا يمكن تضخيم عناصر 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. تُعرف هذه العملية باسم
الإحالة الناجحة باستخدام طريقة ملخّصة فردية
أو تحويل أداة 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
في المستوى الأعلى من الملف، ولكن قد يحتوي الملف أيضًا على عدد كبير من المتغيرات والدوال والفئات
التي يتم تحديدها أيضًا في المستوى الأعلى. تساعد كائنات المرافق على ربط
المتغيرات والدوال وتعريف الفئة دون الإشارة إلى أي مثيل معين من تلك الفئة.
تفويض الموقع
عند إعداد الخصائص، قد تكرر بعض الأنماط الأكثر شيوعًا في Android، مثل الوصول إلى ViewModel
داخل Fragment
. لتجنُّب زيادة عدد الرموز المكرّرة، يمكنك استخدام بنية تفويض المواقع في Kotlin.
private val viewModel: LoginViewModel by viewModels()
يوفّر تفويض الموقع عملية تنفيذ شائعة يمكنك إعادة استخدامها في
جميع أنحاء تطبيقك. ويوفّر Android KTX بعض تفويضات الموقع لك.
على سبيل المثال، تسترد الدالة viewModels
عنصر ViewModel
الذي تم تحديد نطاقه على Fragment
الحالي.
تستخدِم عملية تفويض الموقع الانعكاس، ما يضيف بعض النفقات العامة على الأداء. المقايضة هي بناء جملة موجز يوفر وقت التطوير.
قابلية إلغاء
توفّر لغة Kotlin قواعد صارمة بشأن إمكانية القيم الفارغة، وتحافظ على أمان النوع في تطبيقك. وفي لغة Kotlin، لا يمكن أن تحتوي الإشارات إلى الكائنات على قيم فارغة تلقائيًا. لتخصيص قيمة فارغة لمتغير، يجب إعلان نوع متغير null
عن طريق إضافة ?
إلى نهاية النوع الأساسي.
على سبيل المثال، التعبير التالي غير قانوني في Kotlin. name
من النوع String
ولا يمكن عرضه قيمة فارغة:
val name: String = null
للسماح بقيمة فارغة، يجب استخدام نوع String
قابل للقيم، String?
، كما هو موضّح في المثال التالي:
val name: String? = null
إمكانية التشغيل التفاعلي
تجعل القواعد الصارمة في Kotlin رمزك أكثر أمانًا واختصارًا. تقلل هذه القواعد من احتمالات وجود NullPointerException
قد يؤدي إلى تعطُّل تطبيقك. علاوة على ذلك، فهي تقلل من عدد عمليات التحقق الفارغة التي تحتاج إلى إجرائها في التعليمات البرمجية الخاصة بك.
في كثير من الأحيان، يجب عليك أيضًا استدعاء تعليمات برمجية غير Kotlin عند كتابة تطبيق Android، لأنّ معظم واجهات برمجة تطبيقات Android مكتوبة بلغة برمجة Java.
تُعدّ القيم غير القابلة للاستبدال (Nullability) من المجالات الرئيسية التي تختلف فيها لغة Java ولغة Kotlin في السلوك. تعد Java أقل تعقيدًا مع بناء جملة إمكانية القيم الفارغة.
على سبيل المثال، تتضمّن الفئة Account
بعض السمات، من بينها سمة String
التي تُسمى name
. لا تتضمَّن لغة Java قواعد لغة البرمجة Kotlin بشأن إمكانية إبطال القيم الفارغة،
بدلاً من الاعتماد على التعليقات التوضيحية بشأن قابلية القيم الفارغة الاختيارية لتوضيح ما إذا كان بإمكانك تعيين قيمة فارغة.
بما أنّ إطار عمل Android مكتوب بشكلٍ أساسي بلغة Java، قد تواجه هذا السيناريو عند استدعاء واجهات برمجة التطبيقات بدون تعليقات توضيحية بشأن القيم الفارغة.
أنواع الأنظمة الأساسية
إذا كنت تستخدم لغة Kotlin للإشارة إلى عضو name
بدون تعليقات توضيحية تم تعريفه في فئة Java Account
، لن يعرف المحول البرمجي ما إذا كانت String
يتم الربط بـ String
أم String?
في Kotlin. ويمثل هذا الغموض من خلال نوع النظام الأساسي، String!
.
إنّ String!
ليس له معنى خاص في المحول البرمجي بلغة Kotlin. وتستطيع اللغة String!
أن تمثِّل String
أو String?
، وتتيح لك أداة التجميع تحديد قيمة من أيّ نوع. تجدر الإشارة إلى أنّك تخاطر بعرض السمة NullPointerException
إذا كنت
تمثل النوع على أنّه String
وعيّنت قيمة فارغة.
لمعالجة هذه المشكلة، يجب عليك استخدام التعليقات التوضيحية لقيم القيم الفارغة عند كتابة تعليمة برمجية في 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
،
مما يشير إلى أنه يمكن أن يحتوي على قيمة فارغة. في هذه الحالة ستتعامل لغة Kotlin مع accessId
على أنّه String?
.
للإشارة إلى أنّه لا يمكن أن يكون المتغيّر فارغًا مطلقًا، استخدِم التعليق التوضيحي @NonNull
:
public class Account implements Parcelable {
public final @NonNull String name;
...
}
في هذا السيناريو، يتم اعتبار السمة name
على أنّها String
غير قابلة للقيم الفارغة في لغة Kotlin.
يتم تضمين التعليقات التوضيحية التي لا تتضمّن أي قيم في جميع واجهات برمجة تطبيقات Android الجديدة والعديد من واجهات برمجة تطبيقات Android الحالية. أضافت العديد من مكتبات Java تعليقات توضيحية بشأن القيم الفارغة لدعم مطوّري برامج Kotlin وJava بشكل أفضل.
التعامل مع القيم الفارغة
إذا لم تكن متأكدًا من نوع Java، فيجب أن تعتبره قابلاً للقيم الفارغة.
على سبيل المثال، لم يتم إضافة تعليقات توضيحية إلى العنصر name
في الفئة Account
، وبالتالي يجب أن تفترض أنّه String
قابل للقيم الفارغة.
إذا كنت تريد قطع name
بحيث لا تشمل قيمتها مسافة بيضاء بادئة أو
لاحقة، يمكنك استخدام دالة trim
في Kotlin. يمكنك قصّ
String?
بأمان ببضع طرق مختلفة. تتمثل إحدى هذه الطرق في استخدام عامل تشغيل التأكيد
not-null، !!
، كما هو موضّح في المثال التالي:
val account = Account("name", "type")
val accountName = account.name!!.trim()
يتعامل عامل التشغيل !!
مع كل شيء على جانبه الأيسر على أنه غير فارغ، لذلك في
هذه الحالة، تتعامل مع name
على أنه String
غير فارغ. إذا كانت نتيجة التعبير على يساره فارغة، سيعرض تطبيقك NullPointerException
.
وعامل التشغيل هذا سريع وسهل، ولكن يجب استخدامه باعتدال، لأنّه يمكنه إعادة تقديم مثيلات NullPointerException
في الرمز البرمجي الخاص بك.
من الخيارات الأكثر أمانًا استخدام عامل تشغيل الاتصال الآمن، ?.
، كما هو موضّح في المثال التالي:
val account = Account("name", "type")
val accountName = account.name?.trim()
باستخدام عامل تشغيل الاتصال الآمن، إذا لم تكن قيمة name
فارغة، تكون نتيجة name?.trim()
هي قيمة اسم بدون مسافة بيضاء سابقة أو لاحقة. إذا كانت قيمة name
فارغة، تكون نتيجة name?.trim()
هي null
. وهذا يعني أنّه لا يمكن لتطبيقك إطلاقًا استخدام NullPointerException
عند تنفيذ هذه العبارة.
ينقذك عامل تشغيل الاتصال الآمن من NullPointerException
محتمَل،
إلّا أنّه يمرِّر قيمة فارغة إلى العبارة التالية. يمكنك بدلاً من ذلك التعامل مع الحالات الفارغة على الفور باستخدام عامل التشغيل Elvis (?:
)، كما هو موضّح في المثال التالي:
val account = Account("name", "type")
val accountName = account.name?.trim() ?: "Default name"
إذا كانت نتيجة التعبير على الجانب الأيسر من عامل تشغيل Elvis فارغة، يتم تعيين القيمة في الجانب الأيمن إلى accountName
. هذا الأسلوب مفيد لتوفير قيمة افتراضية ستكون فارغة.
يمكنك أيضًا استخدام عامل Elvis للعودة من دالة مبكرًا، كما هو موضح في المثال التالي:
fun validateAccount(account: Account?) {
val accountName = account?.name?.trim() ?: "Default name"
// account cannot be null beyond this point
account ?: return
...
}
التغييرات في واجهة برمجة تطبيقات Android
أصبحت واجهات برمجة تطبيقات Android متوافقة مع لغة Kotlin بشكل متزايد. تحتوي العديد من واجهات برمجة التطبيقات الأكثر شيوعًا على نظام التشغيل Android، بما في ذلك AppCompatActivity
وFragment
، على تعليقات توضيحية حول إمكانية القيم الفارغة، كما أن بعض الاستدعاءات مثل Fragment#getContext
لديها المزيد من البدائل المتوافقة مع لغة Kotlin.
على سبيل المثال، يكون الوصول إلى Context
الخاص بـ Fragment
دائمًا غير فارغ،
لأن معظم الطلبات التي تجريها في Fragment
تحدث بينما Fragment
تكون مرتبطة بالفئة Activity
(فئة فرعية من Context
). ومع ذلك، لا تعرض Fragment#getContext
دائمًا قيمة غير فارغة، لأنّ هناك سيناريوهات لا يكون فيها Fragment
مرتبطًا بـ Activity
. وبالتالي، يكون نوع الإرجاع Fragment#getContext
فارغًا.
بما أنّ قيمة Context
التي يتم عرضها من Fragment#getContext
تكون قيمة فارغة (ويُسمّى
تعليق توضيحيها @Nullable)، يجب التعامل معها على أنّها Context?
في رمز Kotlin.
هذا يعني تطبيق أحد العوامل المذكورة سابقًا لمعالجة
قابلية إبطال القيم قبل الوصول إلى خصائصها ودوالها. في بعض هذه السيناريوهات، يحتوي Android
على واجهات برمجة تطبيقات بديلة توفّر هذه الراحة.
على سبيل المثال، تعرض Fragment#requireContext
قيمة Context
غير فارغة وتعرِض IllegalStateException
إذا تم استدعائها عندما تكون قيمة Context
فارغة. بهذه الطريقة، يمكنك التعامل مع Context
الناتج على أنّه غير فارغ بدون الحاجة إلى عوامل تشغيل الاتصال الآمن أو الحلول البديلة.
إعداد الموقع
ولا يتم إعداد الخصائص في 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
.
تتمثل إحدى طرق معالجة هذا السيناريو في إعلان العرض على أنه قابل للقيم الفارغة وتهيئته في أقرب وقت ممكن، كما هو موضح في المثال التالي:
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)
}
}
على الرغم من أنّ هذا الإجراء يُتوقّع، عليك الآن إدارة إمكانية إبطال القيمة لـ 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
، لذا احرص على إعداد موقعك في أقرب وقت ممكن.