از الگوهای رایج Kotlin در اندروید استفاده کنید

این مبحث بر روی برخی از مفیدترین جنبه های زبان کاتلین در هنگام توسعه برای اندروید تمرکز دارد.

با قطعات کار کنید

بخش‌های زیر از نمونه‌های 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 می اندازد، بنابراین مطمئن شوید که ویژگی خود را در اسرع وقت مقداردهی اولیه کنید.