هل Kotlin Multiplatform هي مستقبل التطوير عبر الأنظمة الأساسية؟ نصائح حول كيفية البدء

نشرت: 2021-01-29

في الوقت الحاضر ، يمكننا ملاحظة وجود اتجاه في تطوير الأجهزة المحمولة لإصدار التطبيقات بشكل أسرع. كانت هناك محاولات عديدة لتقليل وقت التطوير من خلال مشاركة أجزاء التعليمات البرمجية المشتركة بين الأنظمة الأساسية المختلفة مثل Android و iOS. اكتسبت بعض الحلول شعبية بالفعل ، بينما لا يزال البعض الآخر قيد التطوير. اليوم ، أود مناقشة أحد أحدث الأساليب من المجموعة الثانية - Kotlin Multiplatform Mobile (KMM للاختصار).

ما هو Kotlin Multiplatform Mobile؟

KMM عبارة عن حزمة SDK تهدف بشكل أساسي إلى مشاركة منطق الأعمال بين الأنظمة الأساسية - الجزء الذي يجب أن يكون هو نفسه في معظم الحالات على أي حال. يتم تحقيق ذلك بفضل مجموعة من المجمعين المتعددين لوحدة مشتركة. على سبيل المثال ، يستخدم هدف Android متغير Kotlin / JVM ، وبالنسبة لنظام iOS ، يوجد Kotlin / Native. يمكن بعد ذلك إضافة وحدة مشتركة إلى مشاريع التطبيقات الأصلية النموذجية ويمكن للمطورين المسؤولين عن واجهة المستخدم التركيز على تقديم أفضل تجربة للمستخدمين في بيئة مألوفة لهم - Android Studio لنظام Android و Xcode لنظام iOS.

Kotlin Multiplatform مقابل Flutter

حاليًا ، يعد Flutter أحد الحلول الأكثر شيوعًا لتطوير التطبيقات عبر الأنظمة الأساسية. إنه يركز على قاعدة "كتابة تطبيق واحد وتشغيله في كل مكان" - والتي تعمل ، ولكن فقط للتطبيقات البسيطة. في سيناريوهات الحالة الواقعية ، غالبًا ما يتعين على المطورين كتابة رمز أصلي لكل نظام أساسي على أي حال لملء الفجوات ، على سبيل المثال ، عند فقدان بعض المكونات الإضافية. باستخدام هذا النهج ، يبدو التطبيق متشابهًا على منصات مختلفة ، وهو أمر مرغوب فيه في بعض الأحيان ، ولكن في بعض الحالات ، يمكن أن يخالف إرشادات التصميم المحددة.

رمز خدمات التطوير عبر الأنظمة الأساسية

هل أنت جاهز لإنشاء تطبيقك الخاص؟

اختر Flutter

على الرغم من أنها قد تبدو متشابهة ، إلا أن Kotlin Multiplatform ليس حلاً متعدد المنصات - فهو لا يحاول إعادة اختراع العجلة. لا يزال بإمكان المطورين استخدام الأدوات التي يعرفونها ويحبونها. إنه يبسط فقط عملية إعادة استخدام أجزاء من التعليمات البرمجية التي كان يجب كتابتها سابقًا عدة مرات ، مثل تقديم طلبات الشبكة وتخزين البيانات ومنطق الأعمال الأخرى.

إيجابيات وسلبيات Kotlin Multiplatform

إيجابيات KMM :

  • التطبيق المطور أصلي بنسبة 100٪ لكل نظام أساسي - من السهل دمجه مع الكود المستخدم حاليًا ومكتبات الطرف الثالث
  • سهل الاستخدام - يستخدم جميع مطوري Android تقريبًا Kotlin ، لذلك هناك القليل جدًا من المعرفة الإضافية المطلوبة لهم للبدء
  • يمكن تقسيم واجهة المستخدم لكل نظام أساسي مستهدف - سيبدو التطبيق متسقًا مع أي نظام بيئي معين
  • يسمح المنطق المشترك للمطورين بإضافة ميزات جديدة وإصلاح الأخطاء على كلا نظامي التشغيل في نفس الوقت

سلبيات KMM :

  • لا تزال العديد من المكونات في مرحلة ألفا / بيتا ومن المحتمل أن تكون غير مستقرة أو تتغير في المستقبل

ما هي الشركات التي تستخدم KMM؟

وفقًا للموقع الرسمي ، تكتسب الشركات اهتمامًا متزايدًا بهذه التكنولوجيا والقائمة تطول باستمرار. من بينها ، هناك علامات تجارية مشهورة مثل Autodesk أو VMWare أو Netflix أو Yandex.

كيف تبدأ مع Kotlin Multiplatform؟

أفضل مكان للغطس للحصول على معلومات متعمقة هو الدليل الرسمي ، ولكن في هذه المقالة ، أود أن أعرض مثالًا بسيطًا إلى حد ما ، ولكنه أكثر إثارة للاهتمام من مجرد "Hello World" ، والذي سيكون جلب التطبيق وعرضه أحدث كوميدي من تأليف Randall Munroe (مرخص بموجب CC BY-NC 2.5) بعنوانه من xkcd.com API.

الميزات التي يجب تغطيتها:

  • إعداد مشروع
  • التواصل في الوحدة المشتركة
  • واجهة مستخدم بسيطة لكل من Android و iOS

ملاحظة: أردت أن يكون هذا النموذج سهل القراءة لكل من مطوري Android و iOS ، لذلك في بعض الأماكن ، حذفت عمدًا بعض الممارسات الجيدة الخاصة بالمنصة فقط لتوضيح ما يجري

إعداد مشروع

أولاً ، تأكد من تثبيت أحدث إصدارات Android Studio و Xcode لأن كلاهما سيكون ضروريًا لبناء هذا المشروع. بعد ذلك ، في Android Studio ، قم بتثبيت البرنامج المساعد KMM. يبسط هذا المكون الإضافي الكثير من الأشياء - لإنشاء مشروع جديد ، ما عليك سوى النقر فوق إنشاء مشروع جديد وتحديد تطبيق KMM.

أنشئ مشروع Kotlin Multiplatform Mobile الجديد

بعد إنشاء المشروع ، انتقل إلى ملف build.gradle.kts في الدليل المشترك . هنا عليك تحديد جميع التبعيات المطلوبة. في هذا المثال ، سنستخدم ktor لطبقة الشبكات ، و kotlinx.serialization لتحليل استجابات json من الواجهة الخلفية ، و coroutines kotlin للقيام بكل ذلك بشكل غير متزامن.

للتبسيط ، أقدم أدناه قوائم تعرض جميع التبعيات التي يجب إضافتها إلى تلك الموجودة بالفعل. عند إضافة التبعيات ، ما عليك سوى مزامنة المشروع (ستظهر مطالبة). أولاً ، أضف ملحق التسلسل إلى قسم الملحقات.

 المكونات الإضافية {
   kotlin ("plugin.serialization") الإصدار "1.4.0"
}

ثم أضف التبعيات.

 مجموعات المصدر {
   val CommonMain عن طريق الحصول على {
       التبعيات {
           التنفيذ ("org.jetbrains.kotlinx: kotlinx-serialization-json: 1.0.0")
           التنفيذ ("org.jetbrains.kotlinx: kotlinx-coroutines-core: 1.3.9-native-mt-2")

           التنفيذ ("io.ktor: ktor-client-core: 1.4.1")
           التنفيذ ("io.ktor: ktor-client-json: 1.4.1")
           التنفيذ ("io.ktor: ktor-client-serialization: 1.4.1")
       }
   }
   فال androidMain عن طريق الحصول على {
       التبعيات {
           التنفيذ ("io.ktor: ktor-client-android: 1.4.1")
       }
   }
   val iosMain بالحصول على {
       التبعيات {
           التنفيذ ("io.ktor: ktor-client-ios: 1.4.1")
       }
   }
}

من الجدير بالذكر أنه في وقت كتابة هذه المقالة ، كانت هناك بعض المشكلات في الإصدار الثابت من مكتبة coroutines على نظام التشغيل iOS - وهذا هو السبب في أن الإصدار المستخدم يحتوي على اللاحقة original-mt-2 (والتي تعني تعدد مؤشرات الترابط الأصلي). يمكنك التحقق من الوضع الحالي لهذه المشكلة هنا.

التواصل في الوحدة المشتركة

أولاً ، نحتاج إلى فئة تمثل الاستجابة - هذه الحقول موجودة في json تم إرجاعها بواسطة الخلفية.

 استيراد kotlinx.serialization.Serializable

تضمين التغريدة
فئة البيانات XkcdResponse (
   val img: سلسلة ،
   عنوان val: سلسلة ،
   يوم فال: كثافة العمليات ،
   شهر val: Int ،
   سنة val: Int ،
)

بعد ذلك ، نحتاج إلى إنشاء فئة تمثل API مع عميل HTTP . في حالة عدم توفير جميع الحقول الموجودة في json ، يمكننا استخدام خاصية ignoreUnknownKeys حتى يتمكن برنامج التسلسل من تجاهل الحقول المفقودة. يحتوي هذا المثال على نقطة نهاية واحدة فقط ممثلة بوظيفة معلقة. يخبر هذا المعدل المترجم أن هذه الوظيفة غير متزامنة. سأصفها أكثر برمز خاص بالمنصة.

 استيراد io.ktor.client. *
استيراد io.ktor.client.features.json. *
استيراد io.ktor.client.features.json.serializer. *
استيراد io.ktor.client.request. *

فئة XkcdApi {
   private val baseUrl = "https://xkcd.com"

   private val httpClient = HttpClient () {
       تثبيت (JsonFeature) {
           المتسلسل = KotlinxSerializer (
               kotlinx.serialization.json.Json {
                   ignoreUnknownKeys = صحيح
               }
           )
       }
   }

   تعليق متعة fetchLatestComic () =
       httpClient.get <XkcdResponse> ("$ baseUrl / info.0.json")

}

عندما تكون طبقة شبكتنا جاهزة ، يمكننا الانتقال إلى طبقة المجال وإنشاء فئة تمثل النموذج المحلي للبيانات. في هذا المثال ، تخطيت بعض الحقول الأخرى وتركت فقط العنوان الهزلي وعنوان URL للصورة.

 فئة البيانات ComicModel (
   val imageUrl: سلسلة ،
   عنوان val: String
)

يتمثل الجزء الأخير من هذه الطبقة في إنشاء حالة استخدام والتي ستؤدي إلى طلب شبكة ثم تعيين الاستجابة إلى النموذج المحلي.

 class GetLatestComicUseCase (private val xkcdApi: XkcdApi) {
   تعليق تشغيل المرح () = xkcdApi.fetchLatestComic ()
       .let {ComicModel (it.img، it.title)}
}

واجهة مستخدم بسيطة لنظام Android

حان الوقت للانتقال إلى دليل androidApp - هذا هو المكان الذي يتم فيه تخزين تطبيق Android الأصلي. أولاً ، نحتاج إلى إضافة بعض التبعيات الخاصة بنظام Android إلى ملف build.gradle.kts الآخر الموجود هنا. مرة أخرى ، لا تعرض القائمة أدناه سوى التبعيات التي يجب إضافتها إلى العناصر الموجودة بالفعل. سيستخدم هذا التطبيق بنية Model-View-ViewModel (أول سطرين) ، و Glide لتحميل صورة فكاهية من عنوان URL الذي تم إرجاعه (السطرين الثانيين)

 التبعيات {
   التنفيذ ("androidx.lifecycle: lifecycle-viewmodel-ktx: 2.2.0")
   التنفيذ ("androidx.lifecycle: lifecycle-livingata-ktx: 2.2.0")
   التنفيذ ("com.github.bumptech.glide: glide: 4.11.0")
   AnotationProcessor ("com.github.bumptech.glide: مترجم: 4.11.0")
}

بشكل افتراضي ، يجب أن يحتوي المشروع الذي تم إنشاؤه حديثًا على MainActivity وملف التخطيط الخاص به activity_main.xml . دعنا نضيف بعض المشاهدات إليها - عرض نص واحد TextView و ImageView واحد للكوميديا ​​نفسها.

 <؟ xml version = "1.0" encoding = "utf-8"؟>
<LinearLayout xmlns: andro
   ذكري المظهر:
   android: layout_width = "match_parent"
   android: layout_height = "match_parent"
   android: gravity = "center"
   android: orientation = "vertical">

   <TextView
       ذكري المظهر:
       android: layout_width = "wrap_content"
       android: layout_height = "wrap_content" />

   <ImageView
       ذكري المظهر:
       android: layout_width = "match_parent"
       android: layout_height = "wrap_content" />

</LinearLayout>

بعد ذلك ، سنحتاج إلى بعض التمثيل لحالة التطبيق - يمكن أن يكون تحميل فيلم فكاهي جديد أو عرضه أو قد نواجه خطأ أثناء التحميل.

 دولة فئة مختومة {
   تحميل الكائن: الحالة ()
   نجاح الفئة (نتيجة val: ComicModel): الحالة ()
   خطأ في الكائن: الحالة ()
}

الآن دعنا نضيف الحد الأدنى من ViewModel باستخدام منطق الأعمال الذي تم إنشاؤه مسبقًا . يمكن استيراد جميع الفئات. MutableLiveData هو حقل يمكن ملاحظته - سيراقب العرض التغييرات التي طرأت عليه ويحدث نفسه وفقًا لذلك. viewModelScope هو نطاق coroutine مرتبط بدورة حياة viewmodel - في حالة إغلاق التطبيق ، فإنه يلغي تلقائيًا المهام المعلقة.

 فئة MainViewModel: ViewModel () {
   private val getLatestComicUseCase = GetLatestComicUseCase (XkcdApi ())
   val comic = MutableLiveData <State <ComicModel>> ()

   fun fetchComic () {
       viewModelScope. إطلاق {
           comic.value = State.Loading ()
           runCatching {getLatestComicUseCase.run ()}
               .onSuccess {comic.value = State.Success (it)}
               .onFailure {comic.value = State.Error ()}
       }
   }
}

شيء أخير - MainActivity لتوصيل كل شيء.

 فئة MainActivity: AppCompatActivity (R.layout.activity_main) {
   عرض فال خاصالنموذج: MainViewModel by lazy {
      ViewModelProvider (this) .get (MainViewModel :: class.java)
   }

   تجاوز متعة onCreate (saveInstanceState: Bundle؟) {
      super.onCreate (saveInstanceState)
      viewModel.comic.observe (هذا) {
         عندما تكون) {
            هو State.Loading -> {
               findViewById <TextView> (R.id.titleLabel) .text = "Loading"
            }
            هو State.Success -> {
               findViewById <TextView> (R.id.titleLabel) .text = it.result.title
               مع (هذا)
                  .load (it.result.img)
                  .into (findViewById (R.id.image))
            }
            هو State.Error -> {
               findViewById <TextView> (R.id.titleLabel) .text = "خطأ"
            }
         }
      }
      viewModel.fetchComic ()
   }
}

هذا كل شيء ، تطبيق Android جاهز!

تطبيق Android تم تطويره باستخدام KMM

واجهة مستخدم بسيطة لنظام iOS

تم إجراء كل شيء أعلاه في Android Studio ، لذلك دعنا ننتقل في هذا الجزء إلى Xcode لجعله أكثر ملاءمة. للقيام بذلك ، افتح Xcode وحدد دليل iosApp - يحتوي على مشروع Xcode مُهيأ مسبقًا. بشكل افتراضي ، يستخدم هذا المشروع SwiftUI لـ GUI ، لذلك دعونا نلتزم به من أجل البساطة.

أول شيء يجب فعله هو إنشاء منطق أساسي لجلب البيانات المصورة. تمامًا كما في السابق ، نحتاج إلى شيء ما لتمثيل الدولة.

 دولة التعداد {
    تحميل القضية
    حالة النجاح (ComicModel)
    خطأ حالة
}

بعد ذلك ، دعنا نجهز ViewModel مرة أخرى

 class ViewModel: ObservableObject {
    دعونا getLatesteComicUseCase = GetLatestComicUseCase (xkcdApi: XkcdApi ())
        
    Published var comic = State.loading
        
    فيه() {
        self.comic = تحميل
        getLatestComicUseCase.run {fetchedComic ، خطأ في
            إذا تم جلبه Comic! = لا شيء {
                self.comic = .success (fetchedComic!)
            } آخر {
                self.comic =. خطأ
            }
        }
    }
}

وأخيرا ، المنظر.

ملاحظة: من أجل البساطة ، استخدمت مكون SwiftUI RemoteImage لعرض الصورة ، تمامًا مثلما استخدمت Glide على Android.

 منظم ContentView: عرض {
 
    ObservedObject خاص (مجموعة) var viewModel: ViewModel
    
    var body: some View {
        عرض هزلي ()
    }
    -
    خاص func comicView () -> بعض طرق العرض {
        تبديل viewModel.comic {
        حالة. تحميل:
            إرجاع AnyView (نص ("تحميل"))
        حالة. نتيجة (دع فكاهي):
            إرجاع AnyView (VStack {
                نص (comic.title)
                RemoteImage (url: comic.img)
            })
        حالة. خطأ:
            إرجاع AnyView (نص ("خطأ"))
        }
    }
}

وهذا كل شيء ، تطبيق iOS جاهز أيضًا!

تطبيق iOS تم تطويره باستخدام KMM

ملخص

أخيرًا ، للإجابة على السؤال من العنوان - هل Kotlin Multiplatform هي مستقبل التطوير عبر الأنظمة الأساسية؟ - كل هذا يتوقف على الاحتياجات. إذا كنت ترغب في إنشاء تطبيق صغير متطابق لكلا النظامين الأساسيين للجوّال في نفس الوقت ، فمن المحتمل ألا يكون كذلك ، لأنك بحاجة إلى المعرفة المطلوبة حول التطوير لكلا النظامين الأساسيين.

تحرير رمز المنتج

طوّر تطبيقك التالي مع خبرائنا

إقتبس

ومع ذلك ، إذا كان لديك بالفعل فريق من مطوري Android و iOS وترغب في تقديم أفضل تجربة للمستخدم ، فيمكنه تقليل وقت التطوير بشكل كبير . كما هو الحال في المثال المقدم ، بفضل الوحدة النمطية المشتركة ، تم تنفيذ منطق التطبيق مرة واحدة فقط وتم إنشاء واجهة المستخدم بطريقة خاصة بالنظام الأساسي بالكامل. فلماذا لا تجربها؟ كما ترى ، من السهل البدء.

هل تشعر بالفضول حيال التطوير عبر الأنظمة الأساسية من منظور الأعمال؟ تحقق من مقالتنا حول مزايا التطوير عبر المنصات.