Androidアプリ開発の覚書き

Androidアプリ開発を、Kotlin, Jetpack Composeで行っています。気になったことをChatGPTに聞いて記事にしています。

Androidアプリをマルチモジュール化する

Jetpack Composeを用いるアプリが肥大化すると、UI確認のためにビルドが必要なことがネックになり、マルチモジュール化が必要になるそうです。

後々苦しまないよう、早めにChatGPTで聞いてみました。

マルチモジュール化とは何ですか? どうやって実現するのですか?

User
AndroidアプリをJetpack Composeで開発しています。

マルチモジュール化とは何ですか?

どうやって実現するのですか?

ChatGPT
マルチモジュール化は、アプリケーションを機能やレイヤーごとに分割して、独立したモジュールとして管理するアーキテクチャアプローチです。このアプローチにより、コードの再利用性が向上し、チームの協業が容易になり、ビルド時間の短縮などのメリットがあります。

Jetpack ComposeでAndroidアプリをマルチモジュール化するには、以下の手順で実現できます:

1.モジュールの分割計画を立てる
機能モジュール:アプリの特定の機能を担うモジュール。例えば、ログイン、ダッシュボード、設定など。

ライブラリモジュール:共通のコンポーネントやユーティリティを提供するモジュール。UIコンポーネント、ネットワーク処理、データベース操作などが含まれます。

アプリケーションモジュール:アプリのエントリーポイントを含むモジュール。他のすべてのモジュールを統合します。

2.新しいモジュールを作成する
Android Studioで新しいモジュールを作成します。以下の手順に従います:

File > New > New Moduleを選択。
モジュールの種類を選択(例:Library Module)。
モジュールの設定を入力し、Finishをクリック。

3.モジュール間の依存関係を設定する
モジュール間でコードを共有するには、依存関係をbuild.gradleファイルに追加します。例えば、アプリケーションモジュールが機能モジュールに依存する場合は、アプリケーションモジュールのbuild.gradleに以下のように記述します:

dependencies {  
  implementation project(":feature-module-name")  
}  

4.モジュールを機能に沿って開発する
各モジュールが独立して機能するように、機能ごとに開発を進めます。Jetpack Composeを使ってUIを構築する際も、各モジュール内で完結させます。

5.モジュール間でデータを共有する
モジュール間でデータを共有するには、インターフェースや共通データモデルをライブラリモジュールに定義し、それを他のモジュールから利用する形にします。

マルチモジュール化は初めは複雑に思えるかもしれませんが、アプリケーションの規模が大きくなるにつれて、そのメリットが顕著に現れます。適切な計画と構成により、効率的な開発プロセスとメンテナンスの容易さを実現できます。

新しいプロジェクトで、以前作ったモジュールを利用したい場合、どうしたら良いですか?

User
新しいプロジェクトで、以前作ったモジュールを利用したい場合、どうしたら良いですか?

ChatGPT
以前作成したモジュールを新しいプロジェクトで利用するには、いくつかの方法がありますが、主に以下の2つのアプローチが一般的です。

1.モジュールをプロジェクトにコピーする
もっとも単純な方法は、以前作成したモジュールのディレクトリを新しいプロジェクトのディレクトリにコピーまたは移動することです。その後、以下の手順でモジュールをプロジェクトに追加します。

モジュールのコピー:以前作成したモジュールのフォルダを新しいプロジェクトのフォルダ内にコピーします。

設定の更新:新しいプロジェクトのsettings.gradle(またはsettings.gradle.kts)ファイルに、コピーしたモジュールを含めるための行を追加します。例えば、モジュール名がmylibraryの場合、以下のように追記します。

include ':mylibrary'  

依存関係の追加:プロジェクトのモジュール(通常はappモジュール)が新しく追加したモジュールに依存するように、そのbuild.gradle(またはbuild.gradle.kts)ファイルに依存関係を追加します。

dependencies {  
  implementation project(":mylibrary")  
}  

プロジェクトの同期:Android Studioでプロジェクトを再同期させ、新しいモジュールが正しく認識されるようにします。

2.Gitサブモジュールやプライベートなリポジトリとして管理
もう一つの方法は、モジュールをGitリポジトリとして管理し、それを新しいプロジェクトにGitサブモジュールとして追加するか、あるいはプライベートなアーティファクトリポジトリ(例:MavenやJitPack)にアップロードして依存関係として追加する方法です。

Gitサブモジュールとして追加:この方法では、モジュールのソースコードがプロジェクトに直接含まれます。Gitサブモジュールとしてモジュールを追加するには、Gitのコマンドラインツールを使用します。

git submodule add [モジュールのGitリポジトリURL] [ローカルパス]  

プライベートリポジトリから依存関係として追加:モジュールをビルドしてアーティファクトリポジトリにアップロードし、そのリポジトリを新しいプロジェクトのbuild.gradleに追加してから、依存関係として指定します。

どちらの方法を選択するかは、プロジェクトの要件やチームの作業フローによります。Gitサブモジュールは、ソースコードを直接管理したい場合に適していますが、プライベートリポジトリはビルド済みのアーティファクトを管理する場合に便利です。

少し話は戻りますが、5のモジュール間でデータを共有する方法についても教えてください。

User
少し話は戻りますが、5のモジュール間でデータを共有する方法についても教えてください。

ChatGPT
モジュール間でデータを共有する方法はいくつかありますが、主に以下の3つの方法が一般的です。これらの方法は、モジュールの独立性を保ちつつ、必要なデータや機能を効率的に共有するために設計されています。

1.共通インターフェースを使用する
共通のインターフェース(または抽象クラス)を定義し、それを異なるモジュール間で共有することで、モジュール間の疎結合を保ちつつデータや機能の共有を実現できます。このインターフェースは、通常、共通ライブラリや基本モジュール内に配置されます。

共通インターフェースを定義する:共通機能を定義したインターフェースを作成します。

インターフェースの実装:各モジュールはこのインターフェースを実装し、具体的な機能を提供します。

依存性の注入:DI(依存性注入)フレームワークを使用して、実行時に具体的な実装を注入することで、モジュール間でインターフェースを介して通信します。

2.イベントバスやメッセージングシステムを利用する
イベントバスやメッセージングシステム(例:LiveData, Flow, RxJavaなど)を使用して、モジュール間で非同期的にデータを共有することができます。これにより、モジュール間の直接的な依存関係を避けながら、データの更新やイベントの通知を行うことができます。

イベントのパブリッシュ:一方のモジュールがイベントをパブリッシュ(発行)します。

イベントのサブスクライブ:他方のモジュールがイベントをサブスクライブ(購読)し、イベントの発生時に通知を受け取ります。

3.共有データモデルを利用する
共有データモデルを使用すると、異なるモジュール間でデータ構造を共有することができます。これらのモデルは、共通モジュールに定義され、必要なモジュールによって参照されます。

共有データモデルの定義:共通モジュール内にデータモデル(例:エンティティクラス、DTO)を定義します。

データモデルの利用:これらの共有データモデルを利用して、異なるモジュール間でデータを渡します。

これらの方法を適切に組み合わせることで、アプリケーションの構造を清潔に保ちながら、モジュール間で効率的にデータを共有できます。デザインパターンやアーキテクチャの原則(例:SOLID原則)に従うことで、メンテナンス性や拡張性の高いアプリケーションを開発することが可能になります。

具体的には、どのようにすれば良いですか?

User
具体的には、どのようにすれば良いですか?

ChatGPT
具体的にモジュール間でデータを共有する方法を実装するためには、以下のステップに従って進めることができます。ここでは、共通インターフェースを使用する方法を例にとります。

ステップ1: 共通インターフェースを定義する
共通機能を提供するインターフェースを、共有可能なライブラリモジュール(例えば、coreやbaseといった名前のモジュール)に定義します。このインターフェースは、他のモジュールが実装するメソッドを含んでいます。

// coreモジュール内  
interface DataRepository {  
  fun getData(): List<MyDataModel>  
}  

ステップ2: インターフェースの実装
異なる機能を持つモジュールがこのインターフェースを実装し、具体的なデータ取得ロジックを提供します。

// featureモジュール内  
class MyDataRepository : DataRepository {  
  override fun getData(): List<MyDataModel> {  
    // データ取得の実装  
    return listOf(/* データ */)  
  }  
}  

ステップ3: 依存性の注入
依存性注入(DI)を使用して、実装クラスをアプリケーションや他のモジュールに提供します。Dagger, Hilt, KoinなどのDIライブラリを利用することが一般的です。

// AppモジュールでのHiltを使用した例  
@Module  
@InstallIn(SingletonComponent::class)  
object RepositoryModule {  
    
  @Singleton  
  @Provides  
  fun provideDataRepository(): DataRepository = MyDataRepository()  
}  

ステップ4: 実装クラスの利用
最後に、必要な場所でDIライブラリを通じてインターフェースの実装を注入し、そのメソッドを呼び出してデータを取得します。

@AndroidEntryPoint  
class MyFragment : Fragment() {  
  
  @Inject  
  lateinit var dataRepository: DataRepository  
  
  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {  
    super.onViewCreated(view, savedInstanceState)  
      
    val data = dataRepository.getData()  
    // データを使用する  
  }  
}  

このように、共通インターフェースを定義して、そのインターフェースを異なるモジュールが実装することで、疎結合ながらもモジュール間でのデータ共有が可能になります。依存性注入を利用することで、実装クラスのインスタンスを柔軟に管理し、必要な場所に提供することができます。

共通のリポジトリを作成して、依存性注入するようにするのですね。例で使っていた依存性注入のためのライブラリの概要や使い方について、詳しく教えてください。

User
共通のリポジトリを作成して、依存性注入するようにするのですね。

よくわかりました。

例で使っていた依存性注入のためのライブラリの概要や使い方について、詳しく教えてください。

ChatGPT
依存性注入(Dependency Injection, DI)は、コンポーネント間の依存関係を外部から提供するデザインパターンです。このパターンにより、コンポーネントは自身の依存関係を直接作成するのではなく、外部から注入されることによって取得します。これにより、コードの再利用性、テストの容易さ、モジュール性が向上します。

Android開発における主要なDIライブラリには、Dagger/Hilt、Koin、Kodeinなどがあります。以下はそれぞれのライブラリの概要と基本的な使い方です。

Dagger/Hilt
DaggerはAndroidおよびJavaで広く使用されている強力な依存性注入フレームワークです。HiltはDaggerをベースにした、Android専用のDIライブラリで、Daggerの複雑さを抽象化し、AndroidアプリケーションでのDIの設定と使用を簡単にします。

Hiltの使用法:
build.gradleにHiltの依存関係を追加します。
アプリケーションクラスで@HiltAndroidAppアノテーションを使用します。

アクティビティやフラグメントに@AndroidEntryPointを付け、依存関係が必要なクラスに@Injectアノテーションを使用して依存関係を注入します。

モジュールクラスで依存関係を提供する方法を定義し、@Moduleおよび@InstallInアノテーションを使用します。

Koin
KoinはKotlin向けの軽量で簡単に設定できるDIライブラリです。リフレクションを使用しない純粋なKotlin実装であり、設定がシンプルです。

Koinの使用法:
build.gradleにKoinの依存関係を追加します。
アプリケーションの起動時にKoinモジュールを開始します。

moduleブロック内で依存関係を定義し、singleやfactoryキーワードを使用して提供方法を指定します。

必要な場所でget()やby inject()を使用して依存関係を注入します。

Kodein
KodeinはKotlinで書かれた別のDIライブラリで、Androidだけでなく、任意のKotlinプロジェクトにも適用可能です。シンプルでありながら強力なAPIを提供します。

Kodeinの使用法:
build.gradleにKodeinの依存関係を追加します。
アプリケーションクラスでKodeinのインスタンスを初期化し、DIコンテナを設定します。

モジュール内で依存関係を定義し、bindメソッドを使用して提供方法を指定します。

by kodein()デリゲートやinstance()メソッドを使用して依存関係を注入します。

これらのライブラリは、アプリケーションの構造と依存関係の管理を簡素化し、テストのしやすさを向上させるために役立ちます。プロジェクトやチームの好み、既存のアーキテクチャ、特定のニーズに応じて適切なものを選択してください。

User
ありがとうございました。

ChatGPT
どういたしまして!もし他に質問があれば、いつでもお気軽にどうぞ。プロジェクトでの成功をお祈りしています!