بيئات تطوير أندرويد التي تستخدم التأشير Annotation Processing في KOTLIN


محمد بغات

في هذا الدرس سنتعلّم كيفية استخدام بيئات التطوير ومكتبات أندرويد الشهيرة التي تستخدم التأشير annotation processing في Kotlin.
يوجد في عالم الأندرويد العديد من بيئات التطوير الشهيرة التي تبسّط التطوير. يمكنك استخدام نفس البيئات لأجل التطوير في Kotlin كما تفعل في جافا. يقدم هذا الدرس بعض الأمثلة ويسلّط الضوء على الفروقات في الإعدادات.
سنلقي نظرة على Dagger, Butterknife, Data Binding, Auto-parcel و DBFlow (بقية بيئات التطوير يمكن إعدادها بشكل مماثل). كل بيئات التطوير هذه تعمل بنظام التأشير annotation processing: حيث تقوم بالتأشير على الشيفرة البرمجيّة وسيتمّ توليد أكواد جاهزة لأجلك. تقلّل التأشيرات من الإسهاب غير الضروري وتبسّط الشيفرة البرمجية، وإن كنت تريد أن تفهم ما الذي يحدث فعلا في وقت التنفيذ runtime، يمكنك إلقاء نظرة على الشيفرة التي تم إنشاؤها. تذكّر أن كل بيئات التطوير هذه تولّد الأكواد في جافا، وليس Kotlin.
في Kotlin تقوم بتحديد الارتباطات (dependencies بطريقة مماثلة لجافا باستخدام Kotlin Annotation processing tool (kapt بدلاً من annotationProcessor.

Dagger

Dagger هو بيئة تطوير تحقن الارتباطات dependency injection. إن لم يكن مألوفًا لديك، يمكنك القراءة عنه في دليل المستخدم. لقد حوّلنا مثال القهوة المذكور في هذا الدليل إلى Kotlin، ويمكنك إيجاد النتيجة هنا. الشيفرة البرمجية لـ Kotlin تبدو مشابهة إلى حد كبير للشيفرة الأصليّة. يمكنك تصفح المثال كله في ملف واحد.
كما هو الحال في جافا، يمكنك استخدامInject@ للتأشير على المنشئ constructor المستخدم من قبل Dagger لإنشاء عيّنات instancesمن صنف ما. لدى Kotlin أسلوب مختصر للإعلان عن خاصية ما وعن معامل (parameter) منشئ ما في نفس الوقت. ولكي تؤشّر على المنشئ، استخدم الكلمة constructor بشكل صريح وضَع التأشير Inject@ قبله:

class Thermosiphon 
@Inject constructor(
        private val heater: Heater
) : Pump {
    // ...
} 

التأشير على الوظائف methods يبدو تمامًا بنفس الشكل. في المثال التالي Binds@ تحدّد أنّ الكائن Thermosiphon سيستخدم حيثما كان Pump مطلوبًا، تحدّد Provides@ طريقة بناء Heater، كما توضّح Singleton@ أنّ Heater نفسه ينبغي أن يستخدم في كل مكان:

@Module
abstract class PumpModule {
    @Binds
    abstract fun providePump(pump: Thermosiphon): Pump
}

@Module(includes = arrayOf(PumpModule::class))
class DripCoffeeModule {
    @Provides @Singleton
    fun provideHeater(): Heater = ElectricHeater()
}

الأصناف المؤشّرة بـ Module@ تحدّد كيفية توفير مختلف الكائنات. انتبه إلى أنه عند تمرير معامل تأشير (annotation argument) كمعامل vararg، فسيكون عليك أن تغلّفه بـ arrayOf، كما هو الحال في (Module(includes = arrayOf(PumpModule::class)@ أعلاه.
لكي يتمّ توليد تطبيق محقون الارتباط (dependency-injected implementation) لأجل نوع ما, قم بالتأشير عليه بـ Component@. الصّنف المُولّد سيكون له اسم هذا النوع مسبوقًا بـ Dagger، مثل DaggerCoffeeShop
أدناه:

@Singleton
@Component(modules = arrayOf(DripCoffeeModule::class))
interface CoffeeShop {
    fun maker(): CoffeeMaker
}

fun main(args: Array<String>) {
    val coffee = DaggerCoffeeShop.builder().build()
    coffee.maker().brew()
}

يقوم Dagger بتوليد تطبيق لـ CoffeeShop والذي يسمح لك بالحصول على صنف CoffeeMaker محقون بالكامل. يمكنك التنقل ومشاهدة تطبيق DaggerCoffeeShop إذا قمت بفتح المشروع في بيئة التطوير.
لاحظنا أن التأشير على الأكواد البرمجية لم يتغيّر تقريبًا عند التحوّل إلى Kotlin. الآن دعونا نرى التغييرات التي ينبغي إدخالها على نص البناء البرمجي.
في جافا تقوم بربط Dagger بـ annotationProcessor (أو apt):

dependencies {
  ...
  annotationProcessor "com.google.dagger:dagger-compiler:$dagger-version"
}

في Kotlin عليك إضافة ملحقة kotlin-kapt لإتاحة kapt, ثم قم باستبدال annotationProcessor بـ kapt:

apply plugin: 'kotlin-kapt'
dependencies {
    ...
    kapt "com.google.dagger:dagger-compiler:$dagger-version"
}

هذا كل شيء! لاحظ أن kapt يعتني بملفات جافا كذلك، لذلك لا تحتاج الإبقاء على الارتباط بـ annotationProcessor.
النص البرمجي الكامل لبناء لهذا المشروع يمكن العثور عليه هنا . يمكنك أيضا الاطّلاع على الكود البرمجي المحوَّل لأندرويد.

ButterKnife

يسمح ButterKnife بربط العروض views بالحقول مباشرة بدلاً من استدعاء findViewById.
لاحظ أن ملحقات أندرويد الإضافية لـ Kotlin (مُدمجة تلقائيًا في ملحقة Kotlin في أندرويد ستوديو) تحل المسألة نفسها: استبدال findViewById بكود برمجي موجز وواضح. ربّما عليك استخدامه إلا إذا كنت تستخدم ButterKnife سلفًا ولا تنوي الهجرة.
يمكنك استخدام ButterKnife مع Kotlin بنفس طريقة استخدامه مع جافا. دعونا نرى أوّلا التغيرات التي طرأت على نص البناء البرمجي في Gradle، ومن ثم تسليط الضوء على بعض الاختلافات في الكود البرمجي.
في ارتباطات Gradle استخدم الملحقة kotlin-kapt وقم باستبدال annotationProcessor بـ kapt:

apply plugin: 'kotlin-kapt'

dependencies {
    ...
    compile "com.jakewharton:butterknife:$butterknife-version"
    kapt "com.jakewharton:butterknife-compiler:$butterknife-version"
}

لقد قمنا بتحويل عيّنة ButterKnife إلى Kotlin. يمكن العثور على الكود الناتج هنا.
دعونا نلقي نظرة أكثر عليه لمعرفة ما الذي تغيّر. في جافا تؤشّر على الحقل وتربطه بالعرض view المقابل:

@BindView(R2.id.title) TextView title;

في Kotlin لا يمكنك العمل مع الحقول مباشرة، ولكن بدل ذلك يمكنك العمل مع الخصائص. حيث تؤشّر على الخاصّية:

@BindView(R2.id.title)
lateinit var title: TextView

يتم تعريف التأشير BindView@ ليتم تطبيقه على الحقول فقط، مُترجم Kotlin يدرك هذا ويقوم بالتأشير على الحقل المقابل تلقائيا عندما تقوم بالتأشير على كامل الخاصيّة.
لاحظ كيف أن مُعدِّل lateinit يسمح بالتصريح بأنواع غير معدومةnon-null مُبتدَأً (initialized) بعد إنشاء الكائن (بعد استدعاء المنشئ). بدون lateinit سيكون عليك أن تُصرّح بنوع ذي قيمة معدومةً (nullable type) وتقوم بإضافة تحقيقات إضافية لاختبار العدميّة nullability.
يمكنك أيضًا إعداد الوظائف كمُصغيات listeners، باستخدام تأشيرات ButterKnife:

@OnClick(R2.id.hello)
internal fun sayHello() {
    Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show()
}

يحدد هذا الكود إجراءً ليتم تنفيذه عند النقر على الزر “hello”. لاحظ أنه باستخدام صيغة lambdas فإن الكود يبدو موجزًا عند كتابته مباشرةً في Kotlin:

hello.setOnClickListener {
    toast("Hello, views!")
}

الدالّة toast مُعرّفة في المكتبة Anko .

ربط البيانات

مكتبة ربط البيانات تسمح لك بربط بيانات تطبيقك بالخُطاطةlayouts بطريقة موجزة.
يمكنك إتاحة المكتبة باستخدام نفس الإعدادات في جافا:

android {
    ...
    dataBinding {
        enabled = true
    }
}

لجعله يعمل مع أصناف Kotlin قم بإضافة الارتباط kapt:

apply plugin: 'kotlin-kapt'
dependencies {
    kapt "com.android.databinding:compiler:$android_plugin_version"
} 

عند التبديل إلى Kotlin، لا تتغير ملفات خطاطةxml على الإطلاق. على سبيل المثال، يمكنك استخدام variableفي data لوصف متغير يمكن استخدامه في الخطاطة. يمكنك تعريف متغير من نوع Kotlin:

<data>
    <variable name="data" type="org.example.kotlin.databinding.WeatherData"/>
</data>

يمكنك استخدام الصيغة {}@ لكتابة العبارات، بحيث يمكن الإحالة إلى خصائص Kotlin :

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@{data.imageUrl}"
    android:contentDescription="@string/image" />

لاحظ أن لغة التعبير عن ربط البيانات تستخدم نفس القواعد للإحالة إلى الخصائص كما هو الحال في Kotlin، أي: data.imageUrl . في Kotlin يمكنك كتابة v.prop بدلا من ()v.getProp حتى لو كانت ()getProp من وظائف جافا. وبالمثل، بدلًا من استدعاء مُحدِّدٍ setter مباشرة، فيمكنك استخدام الإحالة:

class MainActivity : AppCompatActivity() {
    // ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityMainBinding =
                DataBindingUtil.setContentView(this, R.layout.activity_main)

        binding.data = weather
        // the same as
        // binding.setData(weather)
    }
}

يمكنك ربط مُصغي لإطلاق إجراء ما عندما يحدث حدث معين:

<Button
    android:text="@string/next"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="startOtherActivity" />

هنا startOtherActivity هي وظيفة محددة في النشاط الرئيسي MainActivity:

class MainActivity : AppCompatActivity() {
    // ...
    fun startOtherActivity(view: View) = startActivity<OtherActivity>()
}

يستخدم هذا المثال الدالة startActivity التي تنشئ نيّة intent بدون بيانات وتبدأ نشاطًا جديدًا، والذي يأتي من المكتبة Anko. لأجل تمرير البيانات يمكنك كتابة (startActivity<OtherActivity>("KEY" to "VALUE".

لاحظ أنه بدلًا من الإعلان باستخدام lambdas في xml كما في المثال التالي، يمكنك ربط الإجراءات مباشرةً في الكود البرمجي:

<Button 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content"
    android:onClick="@{() -> presenter.onSaveClick(task)}" />

 

// the same logic written in Kotlin code
button.setOnClickListener { presenter.onSaveClick(task) }

في السّطر الأخير تم تحديد button بالرّجوع إلى رقم تعريفها id باستخدام ملحقة أندرويد لـ Kotlin. يمكنك استخدام هذه الملحقة كبديل وهو ما سيسمح لك بالحفاظ على منطق الربط في الكود البرمجي والحصول على نص برمجي موجز في نفس الوقت.
يمكنك العثور على مشروع تمثيليّ هنا.

DBFlow

DBFlow هي مكتبةSQLite تسمح بتبسيط التفاعل مع قواعد البيانات. كما تعتمد بشكل كبير على التأشير.
لاستخدامها مع Kotlin قم بإعداد ارتباطات التأشير باستخدام kapt:

apply plugin: 'kotlin-kapt'

dependencies {
    kapt "com.github.raizlabs.dbflow:dbflow-processor:$dbflow_version"
    compile "com.github.raizlabs.dbflow:dbflow-core:$dbflow_version"
    compile "com.github.raizlabs.dbflow:dbflow:$dbflow_version"
}

إن كان تطبيقك يستخدم بالفعل DBFlow، فيمكنك إقحام Kotlin في المشروع بدون مشاكل. يمكنك التّحويل التّدريجي لأكوادك البرمجية الموجودة إلى Kotlin (لضمان ترجمة كل شيء بشكل متناسق). الكود البرمجي المُحوّل لا يختلف كثيرا عن نظيره في جافا. على سبيل المثال، التصريح بجدول يبدو مشابهًا لجافا مع فارق صغير يتمثّل في أنّ القيم الافتراضية للخصائص يجب تحديدها صراحة:

@Table(name="users", database = AppDatabase::class)
class User : BaseModel() {

    @PrimaryKey(autoincrement = true)
    @Column(name = "id")
    var id: Long = 0

    @Column
    var name: String? = null
}

بالإضافة إلى تحويل الوظائف الموجودة إلى Kotlin، يمكنك أيضًا الاستفادة من الدعم الخاص بـ Kotlin. على سبيل المثال، يمكن أن تقوم بتعريف الجداول كـ أصناف بيانات data classes.

@Table(database = KotlinDatabase::class)
data class User(@PrimaryKey var id: Long = 0, @Column var name: String? = null)

يعرّف DBFlow مجموعة من الإضافات لجعل استخدامه في Kotlin أكثر سلاسةً، بحيث يمكنك إدراجه في الارتباطات خاصتك:

dependencies {
    compile "com.github.raizlabs.dbflow:dbflow-kotlinextensions:$dbflow_version"
}

هذا سيمنحك وسيلة للتعبير عن الاستعلامات بأسلوب مشابه لـ C#/LINQ ، استخدم lambdas لكتابة أكواد أبسط بكثير لأجل العمليّات المتزامنة وغيرها.

التّحزيم التلقائي Auto-Parcel

يسمح التحزيم التلقائي بتوليد قيم محزومة Parcelable من الأصناف المؤشّرة بـ AutoValue@.
عند تحديد الارتباطات dependency استخدم مرةً أخرى kapt كمُؤشّر لمعالجة ملفات Kotlin:

apply plugin: 'kotlin-kapt'

dependencies {
    ...
    kapt "frankiesardo:auto-parcel:$latest-version"
}

يمكنك العثور على النّموذج المُحوّل من هنا.
يمكنك التأشير على أصناف Kotlin بـ AutoValue@. دعونا نلقي نظرة على الصّنف المحوّلAddress الذي سيتمّ توليد صياغته المحزومة Parcelable implementation :

@AutoValue
abstract class Address : Parcelable {
    abstract fun coordinates(): DoubleArray
    abstract fun cityName(): String

    companion object {
        fun create(coordinates: DoubleArray, cityName: String): Address {
            return builder().coordinates(coordinates).cityName(cityName).build()
        }
        
        fun builder(): Builder = `$AutoValue_Address`.Builder()
    }
    
    @AutoValue.Builder
    interface Builder {
        fun coordinates(x: DoubleArray): Builder
        fun cityName(x: String): Builder
        fun build(): Address
    }
}

ليس لـ Kotlin وظائف ثابتة static methods، لذلك ينبغي تضمينها داخل كائن مرافق. إذا كنت مصرًّا على استخدامها من داخل الأكواد البرمجية لجافا، قم بالتأشير عليهم بـ JvmStatic.
إن كنت ترغب في الوصول إلى صنف أو وظيفة من جافا باسم غير صالح في Kotlin، يمكنك تمرير الاسم داخل علامتي تنصيص (``)، كما هو الحال مع الصّنف التي تم إنشاؤه AutoValue_Address$.
عمومًا الكود البرمجي المُحوّل يبدو مشابها كثيرا لكود جافا الأصلي.

ترجمة -وبتصرّف- للمقال Android Frameworks Using Annotation Processing من توثيقيات KOTLIN

هذا المقال منشور تحت رخصة  Apache 2 license





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن