これまで私たちのメモアプリは、すべてのデータがスマホの中だけで完結していました。Day 2の今日は、このアプリをインターネットの世界に接続し、クラウド上のサーバーと通信するための「神経系(ネットワーキング層)」を構築します。
このステップをマスターすれば、あなたは「クライアント・サーバー型」アプリ開発者として大きな一歩を踏み出すことになります。
今回は、サーバー開発の手間を省くため、偽物のサーバーを簡単に作れるMockAPIというサービスを「練習相手」として利用します。
1. 今回のゴール (要件定義)
- 目的: Androidアプリが、リモートサーバー(MockAPI)と通信するための、再利用可能で堅牢なネットワーキング層を構築する。
-
スコープ:
- ⭕ やること: Retrofitの導入、API仕様の定義、Hiltによる依存性注入、そして最初の通信テストまで。
- ❌ やらないこと: この通信機能を実際のUIに反映させること(それはDay 3以降のタスクです)。
2. 全体像の設計 (基本設計)
-
アーキテクチャ: 既存のMVVMアーキテクチャを拡張し、
Repository
が新設するApiService
を通じてサーバーと通信できるようにします。 -
コンポーネント:
-
network
パッケージを新設し、通信関連のコードを集約します。 -
DTO (Data Transfer Object): サーバーとの通信専用のデータクラス(例:
MemoResponse
)を作成します。 - ApiService: サーバーのAPIエンドポイント(URL)と通信方法(GET, POSTなど)を定義するインターフェースです。
-
Hiltモジュール:
Retrofit
やApiService
といった、アプリ全体で共有する通信部品の作り方をHiltに教えます。
-
3. 詳細設計 & 実装ステップ
ここからは、具体的なコードと共に実装手順を詳細に解説します。
Step 1: サーバーの準備 (MockAPIのセットアップ) (約30分)
🎯 目標: 通信の相手となる、練習用のサーバー(API)を用意する。
👨💻 作業内容:
- MockAPI.io にアクセスし、アカウントを作成します。
-
user
リソースの作成:-
Resource Name
:user
-
Schema
:email
(String),password
(String) を追加してCreate。
-
-
memo
リソースの作成:-
Resource Name
:memo
-
Schema
:title
(String),content
(String),createdAt
(Date),updatedAt
(Date),userId
(String) を追加してCreate。
-
-
APIのURLを控える:
- 画面上部に表示されている
https://xxxxxxxx.mockapi.io/api/v1/
のようなURLをコピーしておきます。
- 画面上部に表示されている
Step 2: 依存関係の追加 (Gradle設定) (約30分)
🎯 目標: サーバーと通信するためのライブラリ「Retrofit」をプロジェクトに追加する。
👨💻 作業内容:
app/build.gradle.kts
を開き、dependencies
ブロックに以下のライブラリを追記し、「Sync Now」をクリックします。
app/build.gradle.kts
dependencies {
// ... (HiltやComposeの依存関係) ...
// ★★★ 以下を追記 ★★★
// Retrofit (本体)
implementation("com.squareup.retrofit2:retrofit:2.9.0")
// kotlinx.serialization と Retrofit を連携させるためのコンバータ
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
// 通信の詳細なログを見るためのインターセプタ (デバッグに超便利)
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
}
Step 3: API仕様の定義 (通信のルール作り) (約1.5時間)
🎯 目標: MockAPIの仕様に合わせて、通信用のデータクラス(DTO)と、通信メソッド(エンドポイント)を定義したインターフェースを作成する。
👨💻 作業内容:
-
com.example.simplememoapp_android
の下にnetwork
パッケージを、さらにその下にdto
パッケージを新設します。 -
dto
パッケージにMemoResponse.kt
を作成します。 -
network
パッケージにApiService.kt
を作成します。
network/dto/MemoResponse.kt
(新規作成)
package com.example.simplememoapp_android.network.dto
import kotlinx.serialization.Serializable
@Serializable
data class MemoResponse(
val id: String,
val createdAt: String,
val title: String,
val content: String,
val updatedAt: String,
val userId: String
)
【ポイント】: これはDB用のMemo
クラスとは別物です。サーバーとの通信専用のデータ形式なので、dto
(Data Transfer Object) と呼びます。
network/ApiService.kt
(新規作成)
package com.example.simplememoapp_android.network
import com.example.simplememoapp_android.network.dto.MemoResponse
import retrofit2.http.GET
import retrofit2.http.Path
interface ApiService {
// 例: .../users/{userId}/memos というURLにGETリクエストを送る
@GET("users/{userId}/memos")
suspend fun getMemosForUser(@Path("userId") userId: String): List<MemoResponse>
// 今後、ログイン用のPOSTや、メモ作成用のPOSTなどもここに追加していく
}
Step 4: Hiltモジュールの拡張 (DIの設定) (約30分)
🎯 目標: Hiltに「Retrofitの作り方」を教え、アプリのどこからでも使えるようにする。
👨💻 作業内容:
di/AppModule.kt
を開き、RetrofitとApiServiceのインスタンスを生成するための@Provides
関数を追記します。
di/AppModule.kt
(追記)
// ... import文を追加 ...
import com.example.simplememoapp_android.network.ApiService
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
// ... (Database, Dao, RepositoryのProvides関数) ...
// ★★★ 以下を追記 ★★★
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
// 通信のログをLogcatに出力するための設定
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.build()
}
@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
val contentType = "application/json".toMediaType()
// kotlinx.serializationがJsonのパースで厳格すぎるとエラーになる場合があるため、設定を緩める
val json = Json { ignoreUnknownKeys = true }
return Retrofit.Builder()
.baseUrl("https://xxxxxxxx.mockapi.io/api/v1/") // ★ Step1で控えたあなたのURLに書き換える!
.client(okHttpClient)
.addConverterFactory(json.asConverterFactory(contentType))
.build()
}
@Provides
@Singleton
fun provideApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
}
【注意】
baseUrl
には、Step 1で控えたあなた専用のMockAPIのURLを必ず設定してください。
Step 5: 動作確認 (最初の通信テスト) (約30分)
🎯 目標: UIはまだないが、バックエンドとの神経系が正しく繋がったかをLogcatで確認する。
👨💻 作業内容:
-
MemoRepository
とMemoListViewModel
に一時的なテストコードを埋め込みます。
di/AppModule.kt
(一時的な修正)
// ...
@Provides
@Singleton
// ★引数にapiServiceを追加
fun provideMemoRepository(dao: MemoDao, apiService: ApiService): MemoRepository {
// ★apiServiceを渡す
return MemoRepository(dao, apiService)
}
data/repository/MemoRepository.kt
(一時的な修正)
// ★コンストラクタにapiServiceを追加
class MemoRepository(
private val memoDao: MemoDao,
private val apiService: ApiService
) {
// ...
// ★テスト用メソッドを追加
suspend fun checkApiStatus() {
apiService.getMemosForUser("1")
}
}
ui/viewmodel/MemoListViewModel.kt
(一時的な修正)
// ...
import android.util.Log
@HiltViewModel
class MemoListViewModel @Inject constructor(
private val repository: MemoRepository
) : ViewModel() {
// ...
init {
// ★テスト用のコードをinitブロックに追加
viewModelScope.launch {
try {
Log.d("APITEST", "APIへの接続テストを開始します...")
repository.checkApiStatus()
Log.d("APITEST", "APIへの接続に成功しました!")
} catch (e: Exception) {
Log.e("APITEST", "APIへの接続に失敗しました: ${e.message}")
}
}
}
// ...
}
-
アプリを実行し、Android StudioのLogcatで
APITEST
と検索します。APIへの接続に成功しました!
と表示され、その前にOkHttpのログ(JSONデータなど)が出力されていれば、今日のタスクは完全成功です。 -
【重要】 動作確認が終わったら、
MemoListViewModel
のinit
ブロックに追加したテストコードは必ず削除してください。
お疲れ様でした!これでDay 2のタスクは完了です。あなたのアプリは、サーバーと会話する能力を手に入れました。