Androidプロジェクトをモダンに!よくある改善点

プログラミング

Androidプロジェクトにフリーランスエンジニアとして参画すると、「うわ、このコードちょっと古いな…」ってなること、ありますよね?😅 小規模チームだと特に、歴史的経緯で謎実装が残りがち。

でも、新機能追加や機能改善、リグレッションテストのタイミングでコツコツ(または一気に)リファクタリングすれば、ビルド時間短縮、コードのメンテナンス性アップ、開発スピード爆上げも夢じゃない!

ここでは、実際のプロジェクトでやった(orやりたかった)改善点を、リアルなエピソード交えて紹介します。全部一気にフルリプレイスしたわけじゃないので、キラキラ系エンジニアっぽく見えないはず(笑)。🚀

この記事は私が書かされました。

CircleCIからGitHub Actionsへ:全部GitHubでいいじゃん

課題

CircleCIは悪くないけど、並列実行増やすと課金圧が…。PRとの連携もちょっと面倒。依存関係の整理やビルド時間短縮を考えると、GitHubのエコシステムに統一したくなる。

改善

GitHub Actionsに移行。PRトリガーでテストやデプロイ自動化、Marketplaceのアクションで設定も楽。リグレッションテストのタイミングで一気にCI/CDパイプラインを移行した。

効果

無料枠で十分動くし、GitHubネイティブでPRやイシューとの連携がスムーズ。ビルドパイプラインがシンプルになった。

ハマった話

CircleCIのキャッシュ設定が恋しくなる瞬間はあった(笑)。でも、GitHub Actionsのキャッシュも慣れればOK。yamlの書き間違いで初回ビルド失敗したのは内緒だよ。😅

Tips

actions/setup-javaで環境構築、actions/cacheでビルド高速化。チームに「これで十分じゃん」って納得された。Marketplaceのアクションは宝の山!

DeployGateからFirebase App Distributionに移行:シンプルで無料!

課題

DeployGateを使ってたけど、コストがかかるし、もっとシンプルで無料のソリューションが欲しい。テスター管理や配布フローを効率化したい。

改善

Firebase App Distributionにデプロイを移行。GitHub Actionsでワークフロー作って、無料でスッキリ。CIを変更するだけで単独で進めた。

効果

コスト削減、ビルド安定、配布がスムーズに。Firebaseの直感的なUIとテスター管理で、チームのデプロイ作業も楽々。Crashlytics連携で安定性チェックも強化。

ハマった話

GitHub Actionsの設定ミスでビルド失敗連発。yamlのインデント地獄、わかるよね?😭 デバッグに半日溶けたのはいい思い出(?)。

Tips

Firebase CLIやwzieba/Firebase-Distribution-Github-ActionでCI/CD自動化。テスター招待はメールで簡単。Firebaseコンソールのグループ管理を活用すると吉。

謎のクリーンアーキテクチャもどき設計を本物に

課題

ViewModelとRepositoryだけの設計で、ViewModelにビジネスロジックがギッチリ詰め込まれて超肥大化😓 さらに、ViewModelがRoomのFlowを直で監視し、Flowが更新されるたびにViewがUIをリフレッシュする謎仕様。データ更新は「Roomのテーブル全クリア→APIからデータ再取得→Roomにインサート」の無駄なループで、キャッシュの意味ゼロ。Room周りのコードが散らかりバグの温床に。機能ごとにテーブル乱立で、DBスキーマはカオスそのもの。メンテするだけで頭痛い…。😅

改善

UseCaseを追加してビジネスロジックを分離、Repository+StateFlowでデータ管理をシンプルに。過剰かつ意味ないRoom周りのコードも削除して、APIから都度取得する設計に変更。バグ修正や機能改善時にコツコツ移行。新機能実装時は初めから上記の設計でコーディング。

参考コード

以下は、Todoリストを取得するGetTodoUseCaseと、それをTodoViewModelで使って状態管理する例。RepositoryはシンプルにList<Todo>を返し、UseCaseでResultをラップ。ViewModelは軽く、ビジネスロジックはUseCaseに任せてスッキリ!

package com.example.todo

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

// ドメインモデル
data class Todo(val id: Int, val title: String, val isCompleted: Boolean)

// 結果型
sealed class Result<out T> {
    object Loading : Result<Nothing>()
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val message: String) : Result<Nothing>()
}

// Repositoryインターフェース(data層の実装は省略)
interface TodoRepository {
    suspend fun getTodos(): List<Todo>
}

// UseCase: Todoリスト取得のビジネスロジック、エラーハンドリング
class GetTodoUseCase @Inject constructor(
    private val repository: TodoRepository
) {
    suspend operator fun invoke(): Result<List<Todo>> {
        return try {
            // Repositoryからデータ取得、Roomなしでシンプルに
            Result.Success(repository.getTodos())
        } catch (e: Exception) {
            Result.Error(e.message ?: "Failed to fetch todos")
        }
    }
}

// ViewModel: UseCaseを使って状態管理、ビジネスロジックは持たない
@HiltViewModel
class TodoViewModel @Inject constructor(
    private val getTodoUseCase: GetTodoUseCase
) : ViewModel() {
    private val _todoState = MutableStateFlow<Result<List<Todo>>>(Result.Loading)
    val todoState: StateFlow<Result<List<Todo>>> = _todoState.asStateFlow()

    fun fetchTodos() {
        viewModelScope.launch {
            _todoState.value = Result.Loading // ローディング開始
            _todoState.value = getTodoUseCase() // UseCaseでデータ取得
        }
    }
}

効果

ViewModelがUI状態管理に集中できるようになり、ビジネスロジックがUseCaseに集約されてテストしやすくなった。依存関係もスッキリして、コードレビューが楽に。バグも減った(気がする)。

ハマった話

既存のRoom依存コードとの共存が面倒。古い画面と新しい画面でデータ渡しに一瞬混乱。テーブル乱立のレガシーコード見ると、誰かが泣きたくなる瞬間。😅

Tips

Result<Success/Loading/Error>で状態管理統一。viewModelScopeStateFlowでUI更新サクサク。Repositoryパターンでデータソース抽象化、UseCaseでビジネスロジック分離すると幸せになれる。

古いライブラリを一掃:最新化でパフォーマンスアップ

課題

Retrofit 2.6.0とか、5年前のライブラリが残ってるプロジェクト、見たことあるよね?😅 セキュリティリスクやパフォーマンス問題が気になる。

改善

Android SDKアップデートのついでに、Retrofit、Hilt、Roomとか全ライブラリ最新化。リグレッションテストで動作確認しながら一気に進めた。

効果

セキュリティ向上、新機能使える、ビルドちょっと速くなった(体感5%くらい)。チームの「古いコード怖い」ストレスが減った。

ハマった話

非推奨APIの書き換えで「これどこで使ってるんだ…」ってコード探しに1日潰れた。依存解析ツールが友達になった瞬間(笑)。

Tips

Gradleの依存解析(./gradlew dependencies)で不要なライブラリ洗い出し。EspressoでUIテスト自動化して、最新化の安心感アップ。

Dagger2からHiltへ:ボイラープレートよ、さらば!

課題

Dagger2のComponentSubcomponentでコードがぐちゃぐちゃ。注入コード書くだけで疲れる。メンテナンス性も悪い。

改善

Hiltに移行。@HiltAndroidApp@HiltViewModelで注入がシンプルに。リグレッションテストや依存注入見直しのタイミングで一気に移行。

効果

ボイラープレート半減、ViewModel注入が一発。メンテナンス性爆上げ。チームに「これ楽!」って好評だった。

ハマった話

ぶっちゃけハマらなかった(笑)。Hiltのドキュメントが優秀すぎた。Dagger2の呪縛から解放された瞬間は爽快!

Tips

@Module@InstallInでモジュール管理。HiltAndroidTestでテストも楽々。Hiltのスターターチュートリアルは必読。

RxJavaからCoroutinesへ:チェーン地獄からの解放

課題

RxJavaのObservableチェーンがスパゲティ。CompositeDisposable管理も面倒。コード読むだけで目が疲れる。

改善

新機能開発や画面修正時にRxをCoroutinesに置き換え。suspend関数とStateFlowで非同期処理スッキリ。フルリプレイスはせず、必要に応じてコツコツ移行。

効果

コード量減、ビルド軽量化(Rxライブラリ削除でAPKサイズ5%減とか)。viewModelScopeでキャンセル管理も楽に。

ハマった話

Rx依存の古いライブラリが残ってて、完全駆逐は「できたりできなかったり」(笑)。スパゲティコード見ると、誰かが泣きたくなる。

Tips

kotlinx-coroutines-testでユニットテスト楽に。viewModelScopeでスコープ管理。Coroutinesの公式ガイドは読んどくと吉。

JPEGからWebPへ:ビルドサイズをスリムに

課題

JPEGのリソースでAPKサイズがデカい。画像読み込みも遅め。ユーザー体験にも影響。

改善

ResourceManagerを使ってJPEGをWebPに一括変換。。画質キープでサイズ30%減(10MB→7MBとか)。AndroidSDKアップデート時のリグレッションテストに合わせて一気に進めた。

効果

ビルド時間短縮、アプリ軽量化、ユーザー体験向上。APKサイズ減ってクライアントもニッコリ。

ハマった話

アニメーションDrawableで表示がおかしくなる謎バグ。原因忘れたけど、Glideの設定ミスだった気が…😅 デバッグに半日溶けた。

Tips

Android Studioの「Convert to WebP」で変換。GlideでアニメーションWebP対応確認必須。APIレベルの互換性もチェック!

ViewからJetpack Composeへ:未来を見据えたUI

課題

XML+FragmentのViewベースUI、記述量多いしメンテ面倒。コードが肥大化して見づらい。

改善

画面修正時にComposeViewで部分的にCompose化。リストやボタンからコツコツ置き換え。フルリプレイスじゃなく、必要に応じて進めた。新機能開発時は最初からフルComposeで実装。

効果

コード見やすくなった(劇的じゃないけど)。将来的にCompose主流になるからノウハウ蓄積。UI開発がちょっと楽に。

ハマった話

ViewとComposeのテーマ統一が地味に面倒。リコンポジション気にしすぎて一瞬混乱した瞬間(笑)。

Tips

collectAsStateでStateFlow連携。ComposeTestRuleでUIテストもサクサク。Composeのプレビュー機能は開発効率爆上げ。

モジュール分割:依存関係をスッキリ

課題

モノリシックなappモジュールで依存関係ぐちゃぐちゃ。ビルド遅いし、ライブラリ管理がカオス。

改善

クリーンアーキテクチャの各層(data、domain、presentation)ごとにモジュール化。最近はfeature:loginみたいな機能ごとのモジュール化がトレンドだけど、当時は層ごとでガッツリ分割。機能改善時は、RepositoryやUseCaseをmodelからdomainモジュールに移したり、新機能開発時は最初からモジュールを分けてスッキリ開発。

効果

ローカルビルド時間短縮(体感20%減)、ライブラリ依存整理、コード見やすさアップ。CIでのフルビルドは全モジュール走るから影響薄いけど、ローカル開発のストレスは激減!

ハマった話

依存循環見つけて「これ誰が書いたんだ…」ってなった瞬間(笑)。Gradle設定ミスでビルドエラーも数回。

Tips

implementationapi使い分けて依存漏れ防止。GitHub Actionsでモジュールごとテスト。モジュール化は計画的に!

Version Catalogで依存管理を一元化

課題

各モジュールのbuild.gradleでバージョンバラバラ。更新ミスで不整合。依存管理が面倒。

改善

libs.versions.tomlに依存バージョンをまとめて、Gradleで参照。リグレッションテスト時に一気に移行。

効果

バージョン管理楽々、コードレビューで依存確認が一瞬。モジュール化との相性もバッチリ。

ハマった話

初回移行でtomlのシンタックスミスってGradle同期失敗。1時間無駄にしたのはいい思い出(?)。

Tips

IDEのlibs.補完が神。Version Catalogはモジュール化と一緒にやると効果倍増。公式ドキュメント読むべし。

番外編:Kotlin DSLに興味あったけど…やめた!

課題

Groovyのbuild.gradleをKotlin DSL(build.gradle.kts)に変えたかった。型安全や補完に惹かれた。

改善(未実施)

興味あったけど、結局やらなかった。新機能やリグレッションテストでも優先度低く見送り。

なぜやめた?

メリット薄い(ビルド時間変わらず)、移行コスト高い(全モジュール書き換え)、Groovyで十分動く。

ハマりそうだった話

試しに1モジュールktsにしたら、プラグイン設定でエラー吐きまくり(笑)。「やっぱやめとこ」ってなった。

Tips

新規プロジェクトならKotlin DSL一択だけど、既存プロジェクトなら無理しなくていい。Version Catalogで十分依存管理できる。

まとめ:コツコツ(or一気に)改善でプロジェクトが輝く!

Androidプロジェクトの改善は、全部一気にフルリプレイスするんじゃなく、新機能追加や機能改善、リグレッションテストのタイミングでコツコツ(or一気に)進めるのが現実的。ビルド時間短縮、コードの見やすさアップ、チームの開発スピード向上、クライアントの「アプリ軽くなった!」って声も狙える。😉 WebPの謎バグやRxのしぶとい残骸には注意(笑)。あなたのプロジェクトでも、1つでも試してみて!次はどの改善やる?🚀

コメント

タイトルとURLをコピーしました