این مبحث بر روی برخی از مفیدترین جنبه های زبان کاتلین در هنگام توسعه برای اندروید تمرکز دارد.
با قطعات کار کنید
بخشهای زیر از نمونههای Fragment
برای برجسته کردن برخی از بهترین ویژگیهای کاتلین استفاده میکنند.
ارث
شما می توانید یک کلاس در کاتلین با کلمه کلیدی 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)
}
پوچ پذیری و مقداردهی اولیه
در مثالهای قبلی، برخی از پارامترها در روشهای رد شده دارای انواعی هستند که با علامت سوال پسوند ?
. این نشان می دهد که آرگومان های ارسال شده برای این پارامترها می توانند null باشند. مطمئن شوید که با خیال راحت از قابلیت بی اعتباری آنها استفاده کنید .
در 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
با پیاده سازی رابط 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
جاوا برای متغیرها و متدها هستند.
در مثال زیر، TAG
یک ثابت String
است. برای هر نمونه از LoginFragment
به یک نمونه منحصر به فرد از String
نیاز ندارید، بنابراین باید آن را در یک شیء همراه تعریف کنید:
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
برای اجازه دادن به یک مقدار null، باید از یک نوع String
با قابلیت nullable، String?
، همانطور که در مثال زیر نشان داده شده است:
val name: String? = null
قابلیت همکاری
قوانین سختگیرانه کاتلین کد شما را ایمن تر و مختصرتر می کند. این قوانین شانس داشتن NullPointerException
را کاهش می دهد که باعث خرابی برنامه شما می شود. علاوه بر این، تعداد بررسیهای تهی که باید در کد خود انجام دهید را کاهش میدهند.
اغلب، هنگام نوشتن یک برنامه اندرویدی باید کدهای غیر Kotlin را نیز فراخوانی کنید، زیرا اکثر API های اندروید به زبان برنامه نویسی جاوا نوشته شده اند.
پوچ پذیری یک حوزه کلیدی است که جاوا و کاتلین در رفتار متفاوت هستند. جاوا نسبت به نحو پوچ پذیری سخت گیری کمتری دارد.
به عنوان مثال، کلاس Account
دارای چند ویژگی است، از جمله یک ویژگی String
به نام name
. جاوا قوانین کاتلین را در مورد پوچ پذیری ندارد، در عوض بر روی حاشیه نویسی های تهی اختیاری تکیه می کند تا به صراحت اعلام کند که آیا می توانید یک مقدار تهی را اختصاص دهید یا خیر.
از آنجایی که فریم ورک اندروید اساساً در جاوا نوشته شده است، ممکن است هنگام فراخوانی API ها بدون حاشیه نویسی پوچ، با این سناریو مواجه شوید.
انواع پلت فرم
اگر از Kotlin برای ارجاع به یک عضو name
بدون حاشیه استفاده کنید که در کلاس Account
جاوا تعریف شده است، کامپایلر نمی داند که آیا String
به یک String
یا یک String?
در کاتلین این ابهام از طریق یک نوع پلت فرم ، String!
.
String!
برای کامپایلر Kotlin معنای خاصی ندارد. String!
می تواند نشان دهنده یک String
یا یک String?
، و کامپایلر به شما امکان می دهد مقداری از هر نوع را اختصاص دهید. توجه داشته باشید که اگر نوع را به عنوان یک String
نمایش دهید و یک مقدار تهی را اختصاص دهید، خطر ایجاد یک NullPointerException
را دارید.
برای رفع این مشکل، هر زمان که کدی را در جاوا می نویسید، باید از حاشیه نویسی پوچ پذیری استفاده کنید. این حاشیه نویسی به توسعه دهندگان جاوا و کاتلین کمک می کند.
به عنوان مثال، در اینجا کلاس Account
همانطور که در جاوا تعریف شده است:
public class Account implements Parcelable {
public final String name;
public final String type;
private final @Nullable String accessId;
...
}
یکی از متغیرهای عضو، accessId
، با @Nullable
حاشیه نویسی شده است، که نشان می دهد می تواند مقدار تهی داشته باشد. سپس کاتلین با accessId
به عنوان یک String?
.
برای نشان دادن اینکه یک متغیر هرگز نمی تواند null باشد، از حاشیه نویسی @NonNull
استفاده کنید:
public class Account implements Parcelable {
public final @NonNull String name;
...
}
در این سناریو، name
یک String
غیر قابل تهی در Kotlin در نظر گرفته می شود.
حاشیهنویسیهای پوچپذیری در همه APIهای اندرویدی جدید و بسیاری از APIهای اندروید موجود گنجانده شدهاند. بسیاری از کتابخانههای جاوا حاشیهنویسیهای پوچپذیری را برای پشتیبانی بهتر از توسعهدهندگان Kotlin و Java اضافه کردهاند.
مدیریت پوچ پذیری
اگر در مورد نوع جاوا مطمئن نیستید، باید آن را پوچ بدانید. به عنوان مثال، name
عضو کلاس Account
حاشیه نویسی نشده است، بنابراین باید آن را یک String
باطل فرض کنید.
اگر می خواهید name
طوری برش دهید که مقدار آن شامل فضای سفید پیشرو یا انتهایی نباشد، می توانید از تابع trim
کاتلین استفاده کنید. آیا می توانید با خیال راحت یک String?
به چند روش مختلف یکی از این راه ها استفاده از عملگر not-null assertion ، !!
، همانطور که در مثال زیر نشان داده شده است:
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
null باشد، نتیجه name?.trim()
null
است. این بدان معنی است که برنامه شما هرگز نمی تواند هنگام اجرای این عبارت NullPointerException
را پرتاب کند.
در حالی که اپراتور تماس ایمن شما را از یک NullPointerException
بالقوه نجات می دهد، یک مقدار تهی را به عبارت بعدی ارسال می کند. همانطور که در مثال زیر نشان داده شده است، می توانید بلافاصله با استفاده از عملگر Elvis ( ?:
)، موارد تهی را مدیریت کنید:
val account = Account("name", "type")
val accountName = account.name?.trim() ?: "Default name"
اگر نتیجه عبارت سمت چپ عملگر الویس تهی باشد، مقدار سمت راست به accountName
اختصاص داده می شود. این تکنیک برای ارائه یک مقدار پیش فرض مفید است که در غیر این صورت تهی می شود.
همانطور که در مثال زیر نشان داده شده است می توانید از عملگر Elvis برای بازگشت زودهنگام از یک تابع استفاده کنید:
fun validateAccount(account: Account?) {
val accountName = account?.name?.trim() ?: "Default name"
// account cannot be null beyond this point
account ?: return
...
}
Android API تغییر می کند
API های اندروید به طور فزاینده ای دوستدار Kotlin می شوند. بسیاری از متداولترین APIهای اندروید، از جمله AppCompatActivity
و Fragment
، حاوی حاشیهنویسیهای پوچپذیری هستند و فراخوانهای خاصی مانند Fragment#getContext
جایگزینهای مناسبتری برای Kotlin دارند.
به عنوان مثال، دسترسی به Context
یک Fragment
تقریباً همیشه غیر پوچ است، زیرا بیشتر فراخوانیهایی که در یک Fragment
انجام میدهید زمانی اتفاق میافتند که Fragment
به یک Activity
(یک زیر کلاس از Context
) متصل است. همانطور که گفته شد، Fragment#getContext
همیشه یک مقدار غیر تهی را بر نمی گرداند، زیرا سناریوهایی وجود دارد که در آن یک Fragment
به یک Activity
متصل نیست. بنابراین، نوع بازگشتی Fragment#getContext
قابل تهی است.
از آنجایی که Context
برگردانده شده از Fragment#getContext
قابل تهی است (و به صورت @Nullable حاشیه نویسی می شود)، باید آن را به عنوان یک Context?
در کد کاتلین شما این به این معنی است که قبل از دسترسی به خصوصیات و توابع آن، یکی از عملگرهای ذکر شده قبلی را برای آدرس دادن به nullability اعمال کنید. برای برخی از این سناریوها، Android حاوی APIهای جایگزین است که این راحتی را فراهم می کند. به عنوان مثال، Fragment#requireContext
، یک Context
غیر تهی را برمیگرداند و اگر زمانی که یک Context
تهی میشود فراخوانی شود، یک IllegalStateException
ایجاد میکند. به این ترتیب، میتوانید Context
حاصل را بدون نیاز به اپراتورهای تماس ایمن یا راهحلهایی غیر پوچ در نظر بگیرید.
مقداردهی اولیه ویژگی
خواص در Kotlin به طور پیش فرض مقداردهی اولیه نمی شوند. زمانی که کلاس احاطه کننده آنها مقداردهی اولیه می شود، باید مقداردهی اولیه شوند.
شما می توانید خواص را به چند روش مختلف مقداردهی اولیه کنید. مثال زیر نشان می دهد که چگونه می توان یک متغیر index
را با اختصاص مقداری به آن در اعلان کلاس مقداردهی اولیه کرد:
class LoginFragment : Fragment() {
val index: Int = 12
}
این مقداردهی اولیه را می توان در یک بلوک اولیه نیز تعریف کرد:
class LoginFragment : Fragment() {
val index: Int
init {
index = 12
}
}
در مثال های بالا، زمانی که یک LoginFragment
ساخته می شود، index
مقداردهی اولیه می شود.
با این حال، ممکن است برخی از خصوصیات را داشته باشید که نتوان آنها را در طول ساخت شیء مقداردهی اولیه کرد. به عنوان مثال، ممکن است بخواهید به یک View
از داخل یک Fragment
ارجاع دهید، به این معنی که ابتدا طرح باید پر شود. هنگامی که یک Fragment
ساخته می شود تورم رخ نمی دهد. درعوض، هنگام فراخوانی Fragment#onCreateView
زیاد میشود.
یکی از راههای رسیدگی به این سناریو این است که View را بهعنوان nullable اعلام کنید و آن را در اسرع وقت مقداردهی اولیه کنید، همانطور که در مثال زیر نشان داده شده است:
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 را ارجاع میدهید، پوچپذیری 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
می اندازد، بنابراین مطمئن شوید که ویژگی خود را در اسرع وقت مقداردهی اولیه کنید.