Kotlin Multiplatform はクロスプラットフォーム開発の未来ですか? 始めるためのヒント

公開: 2021-01-29

最近では、モバイル開発でアプリをより速くリリースする傾向が見られます。 Android や iOS などの異なるプラットフォーム間で共通のコード部分を共有することで、開発時間を短縮する試みが数多く行われました。 一部のソリューションはすでに人気を博していますが、他のソリューションはまだ開発中です。 今日は、2 番目のグループの最新のアプローチの 1 つであるKotlin Multiplatform Mobile (略して KMM) について説明したいと思います。

Kotlin マルチプラットフォーム モバイルとは?

KMM は、プラットフォーム間でビジネス ロジックを共有することを主な目的とする SDKです。ほとんどの場合、ビジネス ロジックはとにかく同じでなければなりません。 これは、共有モジュール用の複数のコンパイラのセットのおかげで達成されます。 たとえば、Android ターゲットは Kotlin/JVM バリアントを使用し、iOS には Kotlin/Native バリアントがあります。 その後、共有モジュールを一般的なネイティブ アプリ プロジェクトに追加できます。UI を担当する開発者は、ユーザーにとって使い慣れた環境 (Android 用の Android Studio と iOS 用の Xcode) で、ユーザーに最高のエクスペリエンスを提供することに集中できます。

Kotlin マルチプラットフォーム vs Flutter

現在、クロスプラットフォーム アプリ開発で最も人気のあるソリューションの 1 つは Flutter です。 「1 つのアプリを作成してどこでも実行する」というルールに焦点を当てています。これは機能しますが、単純なアプリに限られます。 実際のシナリオでは、開発者は多くの場合、プラグインが不足している場合など、ギャップを埋めるためにプラットフォームごとにネイティブ コードを作成する必要があります。 このアプローチでは、アプリは異なるプラットフォームで同じように見えます。これは望ましい場合もありますが、場合によっては、特定の設計ガイドラインに違反する可能性があります。

クロスプラットフォーム開発サービスのアイコン

独自のアプリを構築する準備はできましたか?

フラッターを選択

似ているように聞こえるかもしれませんが、Kotlin Multiplatform はクロスプラットフォーム ソリューションではありません。 開発者は、使い慣れた好きなツールを引き続き使用できます。 ネットワーク リクエストの作成、データの保存、その他のビジネス ロジックなど、以前は複数回記述する必要があったコードの一部を再利用するプロセスが簡素化されます。

Kotlin Multiplatform の長所と短所

KMM の長所:

  • 開発されたアプリは、各プラットフォームで 100% ネイティブです。現在使用されているコードやサード パーティのライブラリと簡単に統合できます。
  • 使いやすい– ほぼすべての Android 開発者がすでに Kotlin を使用しているため、開始するために必要な追加の知識はほとんどありません。
  • ターゲット プラットフォームごとに UI を分割できます。アプリは、特定のエコシステムと一貫性があると感じられます。
  • 共有ロジックにより、開発者は新しい機能を追加し、両方のオペレーティング システムで同時にバグを修正できます

KMM の短所:

  • 多くのコンポーネントはまだアルファ/ベータ段階にあり、将来的に不安定になったり変更されたりする可能性があります

KMM を使用している企業は?

公式サイトによると、企業はこの技術にますます関心を寄せており、リストはますます長くなっています。 その中には、Autodesk、VMWare、Netflix、Yandex などの有名なブランドがあります。

Kotlin Multiplatform の使用を開始するには?

詳細な情報を得るのに最適な場所は公式ガイドですが、この記事では、アプリのフェッチと表示である「Hello World」だけでなく、かなり単純で興味深い例を示したいと思います。 Randall Munroe による最新のコミック (CC BY-NC 2.5 でライセンス供与) は、xkcd.com API からタイトルを取得しています。

対象となる機能:

  • プロジェクトのセットアップ
  • 共有モジュールでのネットワーキング
  • Android と iOS の両方でシンプルな UI

注: このサンプルは、Android と iOS の両方の開発者にとって読みやすいものにしたかったため、何が起こっているのかを明確にするために、プラットフォーム固有の推奨事項を意図的に省略した箇所があります。

プロジェクトのセットアップ

最初に、最新バージョンの Android Studio と Xcode がインストールされていることを確認してください。このプロジェクトのビルドには両方が必要になるからです。 次に、Android Studio で KMM プラグインをインストールします。 このプラグインは多くのことを簡素化します。新しいプロジェクトを作成するには、[新しいプロジェクトを作成] をクリックして [KMM アプリケーション] を選択するだけです。

新しい Kotlin Multiplatform Mobile プロジェクトを作成する

プロジェクトが作成されたら、共有ディレクトリ内build.gradle.ktsファイルに移動します。 ここでは、必要なすべての依存関係を指定する必要があります。 この例では、ネットワーク層にkotlinx.serializationを使用し、バックエンドからの json 応答を解析するために kotlinx.serialization を使用し、kotlin コルーチンを使用してすべてを非同期に実行します。

簡単にするために、既存のものに追加する必要があるすべての依存関係を示すリストを以下に示します。 依存関係を追加するときは、プロジェクトを同期するだけです (プロンプトが表示されます)。 まず、シリアル化プラグインを plugins セクションに追加します。

 プラグイン {
   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")
       }
   }
   {を取得してval androidMain
       依存関係 {
           実装("io.ktor:ktor-client-android:1.4.1")
       }
   }
   {を取得してval iosMain
       依存関係 {
           実装 ("io.ktor:ktor-client-ios:1.4.1")
       }
   }
}

この記事を書いている時点で、iOS のコルーチン ライブラリの安定版にはいくつか問題があることに注意してください。使用されているバージョンには、native-mt-2 サフィックス (ネイティブ マルチスレッドを表す) が付いているのはそのためです。 この問題の現在のステータスは、こちらで確認できます。

共有モジュールでのネットワーキング

まず、応答を表すクラスが必要です。これらのフィールドは、バックエンドによって返される json に存在します。

 インポート kotlinx.serialization.Serializable

@Serializable
データクラス XkcdResponse(
   val img: 文字列、
   val タイトル: 文字列、
   有効日: 整数、
   有効な月: Int,
   有効年: 整数、
)

次に、HTTP クライアントで API を表すクラスを作成する必要があります。 json に存在するすべてのフィールドを提供しなかった場合は、 ignoreUnknownKeysプロパティを使用して、シリアライザーが不足しているフィールドを無視できるようにします。 この例では、中断された関数によって表されるエンドポイントが 1 つだけあります。 この修飾子は、この関数が非同期であることをコンパイラに伝えます。 プラットフォーム固有のコードで詳しく説明します。

 import io.ktor.client.*
import io.ktor.client.features.json.*
import 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 = true
               }
           )
       }
   }

   楽しみを中断します fetchLatestComic() =
       httpClient.get<XkcdResponse>("$baseUrl/info.0.json")

}

ネットワーク層の準備ができたら、ドメイン層に移動して、データのローカル モデルを表すクラスを作成できます。 この例では、さらにいくつかのフィールドをスキップして、コミックのタイトルと URL だけを画像に残しました。

 データクラス ComicModel(
   val imageUrl: 文字列、
   val タイトル: 文字列
)

このレイヤーの最後の部分は、ネットワーク リクエストをトリガーし、そのレスポンスをローカル モデルにマッピングするユース ケースを作成することです。

 クラス GetLatestComicUseCase(private val xkcdApi: XkcdApi) {
   一時停止 fun run() = xkcdApi.fetchLatestComic()
       .let { ComicModel(it.img, it.title) }
}

Android 向けのシンプルな UI

androidAppディレクトリ移動します。これは、ネイティブの Android アプリが格納されている場所です。 まず、 Android 固有の依存関係を、ここにある他のbuild.gradle.ktsファイルに追加する必要があります。 繰り返しますが、以下のリストは、既存のものに追加する必要がある依存関係のみを示しています。 このアプリは Model-View-ViewModel アーキテクチャ (最初の 2 行) を使用し、返された URL からコミック画像を読み込むために Glide (2 行目) を使用します。

 依存関係 {
   実装("androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0")
   実装 ("androidx.lifecycle:lifecycle-livedata-ktx:2.2.0")
   実装 ("com.github.bumptech.glide:glide:4.11.0")
   annotationProcessor("com.github.bumptech.glide:compiler:4.11.0")
}

デフォルトでは、新しく作成されたプロジェクトには MainActivity とそのレイアウト ファイルactivity_main.xmlが含まれている必要があります。 いくつかのビューを追加してみましょう。タイトル用のTextViewと、コミック自体用の ImageView を 1 つずつ追加します。

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:アンドロ
   アンドロイド:
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:gravity="中心"
   android:orientation="垂直">

   <TextView
       アンドロイド:
       Android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>

   <ImageView
       アンドロイド:
       android:layout_width="match_parent"
       android:layout_height="wrap_content"/>

</LinearLayout>

次に、アプリの状態を表現する必要があります。これは、新しいコミックの読み込み中、表示中、または読み込み中にエラーが発生した可能性があります。

 封印されたクラス状態 {
   オブジェクトの読み込み: State()
   class Success(val result: ComicModel) : State()
   オブジェクト エラー : State()
}

前に作成したビジネス ロジックを使用して、最小限の ViewModel を追加しましょう。 すべてのクラスをインポートできます。 MutableLiveDataは監視可能なフィールドです。ビューはその変更を監視し、それに応じて自身を更新します。 viewModelScopeは、ビューモデルのライフサイクルに関連付けられたコルーチン スコープです。アプリが閉じられた場合、保留中のタスクが自動的にキャンセルされます。

 クラス MainViewModel : ViewModel() {
   private val getLatestComicUseCase = GetLatestComicUseCase(XkcdApi())
   val comic = MutableLiveData<State<ComicModel>>()

   楽しい fetchComic() {
       viewModelScope.launch {
           comic.value = State.Loading()
           runCatching { getLatestComicUseCase.run() }
               .onSuccess { comic.value = State.Success(it) }
               .onFailure { comic.value = State.Error() }
       }
   }
}

最後にもう 1 つ - MainActivity ですべてを接続します。

 class MainActivity : AppCompatActivity(R.layout.activity_main) {
   private val viewModel: MainViewModel by lazy {
      ViewModelProvider(this).get(MainViewModel::class.java)
   }

   楽しみをオーバーライド onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      viewModel.comic.observe(これ) {
         いつ(それ){
            State.Loading です -> {
               findViewById<TextView>(R.id.titleLabel).text = "読み込み中"
            }
            State.Success です -> {
               findViewById<TextView>(R.id.titleLabel).text = it.result.title
               Glide.with(これ)
                  .load(それ.result.img)
                  .into(findViewById(R.id.image))
            }
            State.Error です -> {
               findViewById<TextView>(R.id.titleLabel).text = "エラー"
            }
         }
      }
      viewModel.fetchComic()
   }
}

以上で、Android アプリの準備は完了です。

KMM で開発された Android アプリケーション

iOS 向けのシンプルな UI

上記はすべて Android Studio で行ったので、この部分では Xcode に切り替えてより便利にしましょう。 これを行うには、 Xcode を開いて iosApp ディレクトリを選択するだけです。このディレクトリには、事前構成された Xcode プロジェクトが含まれています。 デフォルトでは、このプロジェクトは GUI に SwiftUI を使用するため、簡単にするためにそれに固執しましょう。

最初に行うことは、コミック データを取得するための基本的なロジックを作成することです。 前と同じように、状態を表すものが必要です。

 列挙状態 {
    ケースローディング
    ケースサクセス(コミックモデル)
    ケースエラー
}

次にもう一度ViewModelを用意しましょう

 クラス ViewModel: ObservableObject {
    let getLatestComicUseCase = GetLatestComicUseCase(xkcdApi: XkcdApi())
        
    @Published var comic = State.loading
        
    初期化() {
        self.comic = .loading
        getLatestComicUseCase.run { fetchedComic、エラー
            if fetchedComic != nil {
                self.comic = .success(fetchedComic!)
            } そうしないと {
                self.comic = .error
            }
        }
    }
}

そして最後に、ビュー。

注: 簡単にするために、Android で Glide を使用したのと同じように、SwiftUI コンポーネントの RemoteImage を使用して画像を表示しました。

 struct ContentView: ビュー {
 
    @ObservedObject private(set) var viewModel: ViewModel
    
    var body: some View {
        comicView()
    }
    --
    private func comicView() -> some View {
        viewModel.comic を切り替える {
        ケースの読み込み:
            return AnyView(Text("読み込み中"))
        場合の結果(せコミック):
            AnyView(VStack を返します {
                テキスト(コミック.タイトル)
                RemoteImage(url:comic.img)
            }))
        ケース.エラー:
            return AnyView(Text("エラー"))
        }
    }
}

以上で、iOS アプリも準備完了です。

KMM で開発された iOS アプリケーション

概要

最後に、タイトルからの質問に答えるために – Kotlin Multiplatform はクロスプラットフォーム開発の未来ですか? – それはすべてニーズに依存します。 両方のモバイル プラットフォーム用の小さくて同一のアプリを同時に作成したい場合は、両方のプラットフォームの開発に関する必要な知識が必要なため、おそらくそうではありません。

製品アイコンのリリース

私たちの専門家と一緒に次のアプリを開発しましょう

見積もりを取得

ただし、すでに Android および iOS 開発者のチームがあり、最高のユーザー エクスペリエンスを提供したい場合は、開発時間を大幅に短縮できます。 提供された例のように、共有モジュールのおかげで、アプリケーション ロジックは 1 回だけ実装され、ユーザー インターフェイスは完全にプラットフォーム固有の方法で作成されました。 それでは、試してみませんか? ご覧のとおり、簡単に始められます。

ビジネスの観点から見たクロスプラットフォーム開発に興味がありますか? クロスプラットフォーム開発の利点に関する記事をご覧ください。