
みなさまこんにちは〜!
メモリアインクのすだです。
本日は、
Androidアプリ開発の設計をするうえで、UseCaseがどのような役割を果たすのか、その必要性について解説していきます。
この記事を読んでわかること…
・UseCaseとは?
・UseCaseの必要性
環境
- Kotlin (ver 1.9.0)
- Android Studio (Giraffe | 2022.3.1 Patch 3)
UseCaseとは?
UseCaseは、アプリの機能や操作をひとつの単位として切り出す中間層のことで、
クリーンアーキテクチャやMVVM構成の中でも非常に重要な役割を持ちます。


たとえば上記のような設計で開発を行う場合、
「アプリが何をするか」を表すロジックを、UIやデータ層とは独立して書くためにUseCaseを使います。
例えば:
- ユーザーを取得する
- 投稿を保存する
- ログイン処理を行う
など、「1機能 = 1UseCase」 のように分けることで、画面やネットワーク層に依存しない再利用可能な処理が実現できます。
UseCaseを使わないとどうなるか – UseCaseの必要性
先程の設計図から「UseCase」を抜いたらどうでしょうか。
例えば、「ログインを行う」処理を UseCaseなしで実装してみます。
ログイン処理をViewModelに書いてみる
fun login(email: String, password: String) {
viewModelScope.launch {
try {
val response = api.login(email, password)
if (response.isSuccessful) {
val token = response.body()?.token ?: ""
tokenStore.saveToken(token)
// 成功後のロジック(ナビゲーション、通知など)
} else {
// エラーハンドリング
}
} catch (e: Exception) {
// 通信エラー処理
}
}
}
本来、ViewModelは「UIの状態管理・イベント受け取り」を行うために使われる処理ですが
これではそれ以外の責務を負ってしまい、再利用性が低くなるほかテストを行う上でも不便になります。
ログイン処理をRepositoryに書いてみる
じゃあRepositoryに書けばいいのでは?と思うかもしれません。
class UserRepositoryImpl(...) : UserRepository {
override suspend fun login(email: String, pass: String): Boolean {
val response = api.login(email, pass)
if (response.isSuccessful) {
tokenStore.save(response.body()?.token ?: "")
return true
}
return false
}
}
これは「動く」し「ある程度分離されている」ように見えます。
でも、以下のような 重要な問題があります。
① Repositoryの責務が膨らみすぎる
本来 Repository は「データの取得と保存」に専念すべきですが、ログイン判定やトークンの保存まで担当すると「何でも屋」になります。Repository はあくまで データの仲介役で、ロジックを持つべきではありません。
② ロジックが共有しづらい/再利用できない
ログイン成功後に通知を出したい場合や、トラッキングイベントを送りたい場合はどうしましょう。
それらをRepositoryに全部書くと、別のユースケースでも再利用できず、結局またRepositoryに条件分岐を書き足すはめになります。
③ テストしづらくなる
Repositoryにロジックが集中していると、ドメインルールのテストをしたくても
APIやDBと切り離せず、重たいテストになりがちです。
ログイン処理をUseCaseに書いてみる
では、UseCaseを使って役割を分担してみます。
① ViewModel ▼
class LoginViewModel(
private val loginUseCase: LoginUseCase
) : ViewModel() {
val loginResult = MutableStateFlow<Boolean?>(null)
fun login(email: String, password: String) {
viewModelScope.launch {
try {
val success = loginUseCase(email, password)
loginResult.value = success
} catch (e: Exception) {
loginResult.value = false
}
}
}
}
ViewModelでは、UseCaseで行った処理の結果を受け取って状態管理を行っているだけの役割です。
② UseCase ▼
class LoginUseCase(
private val repository: UserRepository,
private val tokenStore: TokenStore,
private val tracker: LoginTracker
) {
suspend operator fun invoke(email: String, password: String): Boolean {
val token = repository.login(email, password)
tokenStore.save(token)
tracker.logLogin(token)
return true
}
}
そしてUseCaseでログインの具体的な処理を行います。
UseCaseのような「1つの処理だけを行うオブジェクト」は、invoke
を使うことで “名前付き関数” のように扱うことができます。
③ Repository ▼
interface UserRepository {
suspend fun login(email: String, password: String): String
}
class UserRepositoryImpl(
private val authApi: AuthApi
) : UserRepository {
override suspend fun login(email: String, password: String): String {
val response = authApi.login(LoginRequest(email, password))
if (response.isSuccessful) {
return response.body()?.token ?: throw Exception("トークンが空です")
} else {
throw Exception("ログイン失敗: ${response.code()}")
}
}
}
さらにRepository で、UseCase(ビジネスロジック)と通信処理(APIやDB)をつなぐ「橋渡し役」を担います。
(実装の詳細(API、DB、Firebaseなど)をUseCaseに見せないために、Repositoryが間に入って抽象化してくれます。)
このように、各レイヤーの役割を明確に分離した設計で開発を行うことで、可読性や保守性の向上が期待できるほか、単体テストの実施も非常に容易になります。
まとめ
おつかれさまでした。いかがでしたでしょうか!
基本的には「1つの意味ある処理単位」がUseCaseになります。
細かく分けすぎず、「ユーザー登録」「記事の保存」「設定の更新」などが目安です。



技術者としてのキャリアパスを次のレベルへと進めたい皆様、
<未経験からIT・Webエンジニアを目指すなら【ユニゾンキャリア】>
自分の市場価値をさらに向上させてみませんか?
それではまた次回の記事でお会いしましょう!
コメント