ساخت اپلیکیشن اندروید با استفاده از کوتلین – قسمت سوم

با سلام مجدد، نمیدونم قبلا گفتم یا خیر، منبع آموزشی این دوره از ساخت اپلیکیشن با کوتلین از AndroidPub هست که همین سری آموزش رو پیش رفته و من سعی کردم ترجمه به فارسی رو انجام بدم و اگه جایی تونسته باشم مطالبی رو برای فهم بیشتر اضافه کرده باشم.

تو این قسمت از آموزش به ساخت یک فرگمنت جدید خواهیم پرداخت (NewsFragment fragment). و به مواردی مانند: مقادیر پیش فرض برای پارامترها، Bind کردن view و توابع xtension خواهیم پرداخت.

ساخت NewsFragment.kt :

خب تو اینجا به ساخت یه فرگمنت جدید می پردازیم که داخل اون از RecyclerView استفاده کرده ایم و این فرگمنت اخبار Reddit را نمایش خواهد داد.

در جاوا ما به صورت معمول یه فیلد Private می سازیم تا یک recyclerView را در آن به صورت لوکال ذخیره و نگهداری کنیم و هرگاه view را inflate می کنیم آن را تخصیص می دهیم (Assignمی کنیم) (بعضی از لغات تخصصی رو هر دفعه میخوام با جایگزین پارسیشون بنویسم ولی نمیشه ، لغات تخصصی رو خودشون رو به کار ببرم بهتره 🙂 ) . برای انجام این کار در کوتلین (Kotlin) چیزی شبیه زیر خواهیم داشت:

private var newsList: RecyclerView? = null

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
    val view = inflater.inflate(R.layout.news_fragment, container, false)
    newsList = view.findViewById(R.id.news_list) as RecyclerView?
    newsList?.setHasFixedSize(true) // use this setting to improve performance
    newsList?.layoutManager = LinearLayoutManager(context)

    return view
}

تکه کد بالا صحیح می باشد و کار هم می کند، اما! این کوتلین-یزه شده نیست ، پس بیاید تا بهترش کنیم. با این تکه کوچک کد به مفاهیم جدید این زبان خواهیم پرداخت:

  • توابع Extension
  • مقادیر پیش فرض برای پارامترها
  • bind کردن view
  • Delegated Properties

توابع Extention (کلاس کاربردی -Utility class ) :

توابع Extension به ما این اجازه را می دهند تا عاملیت یک کلاس را با افزودن توابع جدید، گسترش دهیم (extendکردن). کلاس لازم نیست متعلق به ما باشه (می توونه از یه کتابخانه سوم شخصی باشه)و همچنین نیازی نیست تا کلاس را به ارث ببریم.

این یه ویژگی فوق العاده حساب میشه! این که ما میتوانیم توابع جدی را به کلاس های موجود اضافه کنیم و این چیزیه که ما در ViewGroup بهش خواهیم پرداخت.

همانطور که می دانید، ViewGroupیه کلاس از Android SDK هست و برای inflateکردن آن نیاز است تا به صورت زیر عمل کنیم :

inflater.inflate(R.layout.news_fragment, container, false)

اما حقیقتا این روش مستقیم برای انجام این کار نیست.باید چیزی شبیه کاری که iewGroupانجام می دهد باشد، مانند کد زیر :

val view = container?.inflate(R.layout.news_fragment)

این شبیه این هست که ViewGroupمی تواند خودش، خودش را inflate کند. حُقه جالبیه! اما چطور باید این کار را انجام داد؟ بیاید تا اولین تابع Extension را بسازیم.

فایلی را با نام Extension.kt بسازید. و کد داخل آن به صورت زیر می باشد (در ادامه کامل توضیح می دم):

@file:JvmName("ExtensionsUtils")

package com.example.root.kotlinlearning

import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

fun ViewGroup.inflate(layoutId: Int, attachToRoot: Boolean = false): View {
    return LayoutInflater.from(context).inflate(layoutId, this, attachToRoot)
}


fun <T> androidLazy(initializer: () -> T) : Lazy<T> = lazy(LazyThreadSafetyMode.NONE, initializer)

الان با این تکه کد کار داریم :

fun ViewGroup.inflate(layoutId: Int): View {
    return LayoutInflater.from(context).inflate(layoutId, this, false)
}

کاری که ما در اینجا انجام می دهیم این هست که یک متد جدید به ViewGroup اضافه می کنیم (توجه کنید که چطوری ViewGroup را با یه «نقطه – dot» قبل از نام متد «inflate» اضافه کردیم) اما به اصلاح و تغییر در ViewGroup نمی پردازیم، بلکه تنها یک تابع جدید به آن اضافه می کنیم. این تابع ذاتاً یک متد استاتیک می باشد ، اما شما می توایند آن را از یک نمونه-instance از یک کلاس با استفاده از نقطه-dot فراخوانی کنید. در اینجا ما داریم:

container.inflate(…)  نه ViewGroup.inflate() . به این دلیل است که کامپایلر یک Util Class برای ما خواهد ساخت. اگر شما می خواهید از این تابع Extension در جاوا استفاده کنید، به روش زیر باید این کار را انجام دهید:

// Java
ExtensionsKt.inflate(container, R.layout.news_fragment);
// Kotlin
container?.inflate(R.layout.news_fragment)

در دنیای کوتلین-kotlin این کار به روش مناسب تری انجام می شود. به یاد بیارید که ما از علامت سوال «؟» تنها به خاطر اینکه container مورد نظرمون امکان داره null باشه استفاده کردیم تا از رخداد NullPointerException جلوگیری کنیم.

نام کلاس Utility مشابه با اسم فایل به اضافه پسوند «kt» خواهد بود، یا آنکه میتوایند آن را با استفاده از خط زیر override کنید:

@file:JvmName(“ExtensionsUtils”)

@file:JvmName("ExtensionsUtils")

package com.droidcba.kedditbysteps.commons

import ...

fun ViewGroup.inflate(layoutId: Int): View {
    ...
}
// Use it in this way in Java:
ExtensionsUtils.inflate(container, R.layout.news_fragment);

اطلاعات بیشتر در مورد تعمیم و همکاری جاوا و کوتلین رو اینجا میتونید پیدا کنید.

اما برگردیم به تابعی که داشتیم در مودرش حرف می زدیم:

fun ViewGroup.inflate(layoutId: Int): View {
    return LayoutInflater.from(context).inflate(layoutId, this, false)
}

داخل بلاک کد کاملا شبیه این هست که شما دارید یه مِتُد جدید واقعی برای کلاس می نویسید و به همین دلیل است که شما با this می توانید به یک instance از کلاس دسترسی داشته باشید و به متغیر های لوکال (context) نیز دسترسی داشته باشید.

خیلی هم عالی، این اولین تابع extension ما محسوب میشه و در مورد این مفهموم اطلالعات بیشتری کسب کردیم، پس کد ما به صورت زیر بروز می شود:

// old code:
val view = inflater.inflate(R.layout.news_fragment, container, false)
// replaced with:
val view = container?.inflate(R.layout.news_fragment)

اما اینجا ما پارامتر attachToRoot که در مِتُد inflater inflate داشتیم را از دست داده ایم. بیاید تا به کدمان اضافه اش کنیم.

مقادیر پیشفرض برای پارامترها

در کوتلین-kotlin شما می توانید مقدار پیشفرض برای پارامترهای یک تابع در نظر بگیرید (همچنین در سازنده های کلاس – class constructor، اینجا بهش نخواهیم پرداخت). پس ما مقدار پیش فرض برای پارامتر attachToRoot اضافه خواهیم کرد:

fun ViewGroup.inflate(layoutId: Int, attachToRoot: Boolean = false): View {
    return LayoutInflater.from(context).inflate(layoutId, this, attachToRoot)
}

در مواردی که برای پارامتر attachToRoot مقداری را در نظر نگیرید، این پارامتر مقدار پیشفرض را برخواهد داشت، پس می توان به روش دیگری فراخوانی اش کنید:

container?.inflate(R.layout.news_fragment) // default: false
container?.inflate(R.layout.news_fragment, true)

Android Extensions

این روش ثانویه کوتلین-kotlin برای متد های مشهور “findViewById()” (یا دیگر کتابخانه های سوم شخص جهت bind کردن ویژگی هایشان به یک المان از view ) می باشد. Android Extensions تعدادی extension properties که به ما امکان دسترسی به المان های یک view را به عنوان ویژگی های خودش، درون activityمان یا fragment می دهد، اضافه می کند. (فکر کنم باید این متن رو دو بار بخوانید 😐 شرمنده بهتر از این نمیتونستم توضیح بدم، فکر کنم با مثال های جلوتر بهتر مفهوم رو درک کنید)

قبل از شروع به تغییرات باید کدمان رو تغیر بدهیم تا Android Extensions فعال شود. فایل build.gradle را باز کنید و kotlin-android-extensions را به آن اضافه کنید و -sync کنید. (خط سوم تکه کد زیر)

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

حله، بیاید findViewById را عوضش کنیم، فرگمنتمن به صورت زیر است:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/news_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

در حال حاضر ما امکان دسترسی به news_list RecyclerView از فرگمنت جدیدمان را داریم . باید import زیر را داخل کلاس include کنیم:

import kotlinx.android.synthetic.main.fragment_news_fragment.*

حال می توان به المان های layout که د راین import انتخاب کرده اید دسترسی داشته باشید و مستقیما درون کد از آن استفاده کنید. به کد های زیر توجه کنید :

// old code:
newsList = view?.findViewById(R.id.news_list) as RecyclerView?
newsList?.setHasFixedSize(true)
newsList?.layoutManager = LinearLayoutManager(context)
// new code:
news_list.setHasFixedSize(true)
news_list.layoutManager = LinearLayoutManager(context)

آموزش کوتلین ادامه دارد …