概要
macOS 上で Python (Flask) + PostgreSQL 16 による REST API バックエンドを構築し、Android (Kotlin / Jetpack Compose) から Retrofit2 を用いて CRUD 処理を行う読書管理アプリ(BookLogApp)を実装しました。
開発過程で遭遇した macOS ファイアウォール越しでのエミュレータ通信の安定化 や SocketTimeoutException の回避ノウハウ についてもまとめています。
開発環境・使用技術
バックエンド (Mac)
- OS: macOS
- Language: Python 3.12
- Framework: Flask
- Database: PostgreSQL 16
-
API Endpoints:
-
GET /api/books- 書籍一覧の取得 -
POST /api/books- 新規書籍の登録 -
PUT /api/books/<id>- 書籍情報の更新 -
DELETE /api/books/<id>- 書籍の削除
-
フロントエンド (Android)
- IDE: Android Studio
- Language: Kotlin
-
UI: Jetpack Compose (
Material3,LazyColumn,Scaffold,AlertDialog) -
Architecture: MVVM (
ViewModel+StateFlow) - HTTP Client: Retrofit 2 + Gson + OkHttpClient (タイムアウト 30秒設定)
- Asynchronous: Kotlin Coroutines
主なはまりどころと解決策
1. エミュレータ ➔ Mac ローカルサーバーへの接続(10.0.2.2 vs ローカル IP)
-
現象: エミュレータの Chrome ブラウザで
10.0.2.2:5000は開けるものの、アプリ内の Retrofit 通信でSocketTimeoutExceptionが発生。 - 原因: macOS のセキュリティ制限やソケット再利用時のキャッシュ、およびローカルネットワークルーティングの影響。
-
解決策:
- Mac のローカル IP アドレス(例:
192.168.0.5)を確認し、BASE_URLに:5000/を明示指定(http://192.168.0.5:5000/)。 - OkHttpClient で
connectTimeout/readTimeoutを 30 秒に明示設定。
- Mac のローカル IP アドレス(例:
2. macOS ファイアウォール ON 時の Connection reset 解消
-
現象: ファイアウォールを ON にすると、ローカル IP 接続時に
SocketException: Connection resetが発生する。 -
解決策:
- macOS 15 (Sequoia) の「システム設定」➔「ネットワーク」➔「ファイアウォール」➔「オプション」を開く。
- 個別許可リスト(+ボタン)に、サーバーを実行している 「ターミナル(Terminal / IDE)」 および
python3バイナリ を直接追加し、「着信接続を許可」に設定。
実装したコードのポイント
1. Retrofit クライアント設定 (RetrofitInstance.kt)
タイムアウト時間を 30 秒に延ばし、接続を安定化させています。
package com.example.booklogapp
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
object RetrofitInstance {
private const val BASE_URL = "[http://192.168.0.5:5000/](http://192.168.0.5:5000/)"
private val okHttpClient = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
val api: BookApiService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(BookApiService::class.java)
}
}
2. ViewModel での非同期 State 管理 (BookViewModel.kt)
StateFlow を用いて、POST / PUT / DELETE 成功時に自動で一覧(fetchBooks)を最新化しています。
fun addBook(newBook: Book, onSuccess: () -> Unit) {
viewModelScope.launch {
_isLoading.value = true
try {
val response = RetrofitInstance.api.createBook(newBook)
if (response.isSuccessful) {
onSuccess()
fetchBooks() // 一覧を自動再取得
}
} catch (e: Exception) {
_errorMessage.value = e.localizedMessage
} finally {
_isLoading.value = false
}
}
}
まとめと今後の展望
バックエンドの PostgreSQL データベースから Android アプリの Jetpack Compose 画面まで、リアルタイムに双方向同期するアプリ基盤が完成しました。
次回は以下のアプリアップグレードに取り組む予定です!
-
タイトル・著者名による キーワード検索 および ステータス(未読/読書中/読了)フィルター
-
Jetpack Compose テーマカラーやカードデザインの刷新