[Clean Architecture] UseCase

ee·2024년 10월 1일

Clean Architecture

목록 보기
2/6

UseCase

안드로이드 공부를 시작한 사람들 대부분은 보통 viewmodel -> repository -> data 구조의 디자인 패턴을 사용했을 것이다. 나 또한 이 패턴을 자주 사용했는데 이 패턴의 문제는 repository가 수정될 때마다 해당 repository를 참조하는 곳들 모두 수정해야되는 문제가 발생할 수도 있다. 작은 규모의 앱에서는 문제가 없을 수 있지만 규모가 큰 앱이라면 UseCase를 사용하는 것이 바람직하다.

UseCase는 직접 Repository에 의존하여 비즈니스 로직을 갖게 되고, ViewModel은 의존하고 있는 UseCase 내부에 작성된 비즈니스 로직을 그대로 사용하기만 하면 되는 이유로, 특정 로직에 대한 수정을 최소화할 수 있다.

작성법

Repository

interface CoinRepository {
    suspend fun getCoins(): List<CoinDto>
    suspend fun getCoinById(coinId: String): CoinDetailDto
}

UseCase

class GetCoinsUseCase @Inject constructor(
    private val repository: CoinRepository
) {
    operator fun invoke(): Flow<Resource<List<Coin>>> = flow {
        try {
            emit(Resource.Loading<List<Coin>>())
            val coins = repository.getCoins().map { it.toCoin() }
            emit(Resource.Success<List<Coin>>(coins))
        } catch (e: HttpException){
            emit(Resource.Error<List<Coin>>(e.localizedMessage ?: "An unexpected error occurred"))
        } catch (e: IOException){
            emit(Resource.Error<List<Coin>>(""))
        }
    }
}

@Inject constructor(private val repository: CoinRepository) 이 부분은 다음에 자세히 설명하겠지만 간단하게 설명하면 클래스의 생성자로 CoinRepository라는 리포지토리를 주입받아, 해당 리포지토리에서 코인을 가져오는 역할을 한다.

invoke 연산자 함수는 객체를 함수처럼 호출할 수 있게 하고 flow 빌더를 사용하여 비동기적으로 데이터를 처리한다.
emit 함수는 Flow 내부에서 사용되며 비동기적으로 호출되어, 데이터를 처리할 때 각 값들을 차례차례 방출한다.

코인을 가져오는데 성공하면 데이터를 Success함수에 인자로 전달하여 방출한다.

viewModel

@HiltViewModel
class CoinListViewModel @Inject constructor(
    private val getCoinsUseCase: GetCoinsUseCase
): ViewModel(){
    private val _state = mutableStateOf(CoinListState())
    val state: State<CoinListState> = _state

    init {
        getCoins()
    }

    private fun getCoins(){
        getCoinsUseCase().onEach { result ->
            when(result){
                is Resource.Success -> {
                    _state.value = CoinListState(coins = result.data ?: emptyList())
                }
                is Resource.Error -> {
                    _state.value = CoinListState(
                        error = result.message ?: "An unexpected error occurred"
                    )
                }
                is Resource.Loading -> {
                    _state.value = CoinListState(isLoading = false)
                }
            }
        }.launchIn(viewModelScope)
    }
}

마찬가지로 Usecase를 주입받는다.
getCoinsUseCase를 함수처럼 호출하여 각 결과에 따라 CoinListState에 결과값을 전달한다.

마무리

UseCase를 사용하면 그 장점은 분명하다. 코드 파악이 쉬워지며 유지 보수에 큰 장점을 가진다. 다음 프로젝트에 나도 써봐야 겠다.

profile
정진이

0개의 댓글