昨年(2021年)末にAndroid Developersに掲載されているアプリアーキテクチャガイドが更新されていましたね。更新版の内容で参考になったことの一つが、UseCaseの実装についてでした。Domain layerに関する以下のページです。
ドメインとは何か、UseCaseとは何か、ということについては上記のリンクに譲ることにして、ここではその実装方法を見ていきたいと思います。
リンク先では、以下のようにUseCaseの invoke
を実装することで、UseCaseがただ一つの機能のみを持っていることを強制できるようにしています。以下のサンプルコードは上記のリンク先のものです。
operator fun invoke(date: Date): String {
return formatter.format(date)
}
例えばコーディング規約などでUseCaseは invoke
のみを実装していることを定めれば、一つのUseCaseがただ一つの機能のみを提供していることが保証できるようになるかと思います1。 invoke
の引数と返り値は自由に定義できるので、実装上も不便はなさそうです。
UseCaseの命名は、アプリアーキテクチャガイドにあるように、「動詞+名詞+UseCase」というように命名します。例えば、LogOutUserUseCaseというような命名です。このように命名した場合は、インスタンスに対して logOutUserUseCase()
というように書けば invoke
に実装した処理を実行できます。この命名規則に従うことにより、このUseCaseが「ユーザーのログアウトを行う」という処理を実行していることがわかりやすくなると思います。
また、 invoke
を実装する場合も、UseCaseのinterfaceを切ったり、 suspend
を付けることもできます。例えばUseCaseの invoke
でFlowを返す、という時にはこんな感じで実装できます。 @Inject
はDaggerのアノテーションです。
interface LogOutUserUseCase {
operator fun invoke(): Flow<LogOutState>
}
class LogOutUserUseCaseImpl @Inject constructor() : LogOutUserUseCase {
override operator fun invoke(): Flow<LogOutState> =
flow {
// do something here
}
}
invoke
をsuspend funにする時はこんな感じで書けます。
suspend operator fun invoke(): AwesomeType
override suspend operator fun invoke(): AwesomeType {
// do something here
}
UseCaseを実行する時は、UseCaseのインスタンスに ()
を付けて invoke
を呼び出せば良いです。
viewModelScope.launch {
logOutUserUseCase()
.map(/* do something here */)
.onEach(/* do something here */)
.launchIn(this)
}
}
UseCaseをいい感じに実装できて捗りそうです。
余談ですが、UseCaseを1関数にすることでKotlinの fun interface
を適用して関数型インターフェースにできますので(参考:[Kotlin] 関数定義が1つだけのinterfaceは fun interface
で定義できる)、こんな感じで書くこともできます。
fun interface SomeUseCase {
operator fun invoke()
}
val useCase = SomeUseCase { /* Not yet implemented */ }
fun interface
は関数定義が1つのみであることがコンパイラによって検証されますので、UseCaseが1関数のみであることが保証できて嬉しいかもしれません。
一つのUseCaseに複数の機能が生えてくることが稀によくある。 ↩︎