본문 바로가기

안드로이드/클린 아키텍처

[안드로이드 클린 아키텍처 시리즈] UI Layer 구현 2편 (with Hilt) #9


1. 안드로이드 클린 아키텍처 UI Layer 2편

안녕하세요. 이전 포스팅에서는 UI Layer를 위한 Hilt 의존성 주입의 기본 설정과 필요성에 대해 살펴보았습니다. 이번 포스팅에서는 UI Layer의 핵심 요소 중 하나인 ViewModel에서 UseCase를 활용하는 방법에 대해 다루고, 실제로 의존성 주입을 통해 UseCase와 Repository를 ViewModel에 주입하는 과정을 구현하겠습니다.

UI Layer에서 Hilt 모듈을 관리하는 파일
이번 시간에 구성할 hilt 모듈

2. ViewModel에서 UseCase 활용의 중요성

ViewModel은 사용자 인터페이스(UI)의 상태와 로직을 관리하는 역할을 담당합니다. UI와 모델 사이의 상호작용을 처리하며, UI Layer에서 사용자의 액션에 따라 데이터를 처리하고 결과를 UI에 반영해야 합니다. 이 과정에서 UseCase는 비즈니스 로직을 실행하고, 필요한 데이터를 Repository로부터 가져오는 역할을 합니다. 따라서 ViewModel에서 UseCase를 사용하게 됩니다.

2-1. Hilt를 사용한 ViewModel 의존성 주입

ViewModel에서는 @HiltViewModel, @Inject 어노테이션을 사용하고 있습니다.

  • @HiltViewModel 어노테이션은 Hilt에게 해당 클래스가 ViewModel 임을 알려줍니다.
  • @Inject constructor는 어노테이션은 해당 클래스를 생성할 때, 생성자의 매개변수에 다른 의존성이 필요한지 알게 됩니다. 즉, Hilt 입장에서는 Domain Layer에서 정의했었던 GetMovieDetail(UseCase)이라는 의존성이 필요하다고 알게 됩니다. 그래서 반드시 GetMovieDetail 클래스의 인스턴스를 제공하는 방법을 Hilt에게 알려주어야 합니다.
@HiltViewModel
class MovieDetailViewModel @Inject constructor(
    private val getMovieDetail: GetMovieDetail,
    savedStateHandle: SavedStateHandle,
) : ViewModel() {
	...
}

2-2. UseCase 의존성 주입 구현

Hilt에서는 모듈을 생성해서 의존성 제공하는 방법을 구현할 수 있습니다. 위의 코드 예시에서는 ViewModel의 생성자 매개변수에 있는 GetMovieDetail 클래스의 인스턴스를 제공해주어야 한다고 했습니다. 그러기 위해서 아래와 같이 의존성을 제공하는 코드를 구현하게 되면 이러한 의존성을 Hilt를 통해 자동으로 주입받을 수 있습니다.

@Provides
fun provideGetMovieDetailUseCase(movieRepository: MovieRepository): GetMovieDetail {
    return GetMovieDetail(movieRepository)
}

2-3. 같은 방법으로 남은(Repository, Retrofit client 등) 의존성 주입 구현

위의 예시를 보면 GetMovieDetailUseCase에 대한 의존성을 제공할 때, 매개변수에 MovieRepository가 있는 것을 볼 수 있습니다. GetMovieDetail 인스턴스를 생성할 때 MovieRepository의 인스턴스가 필요하므로 MovieRepository의 의존성을 제공하는 방법을 구현하면 됩니다. 동일한 DataSource, Retrofit client 등에 대한 의존성을 제공하는 방법을 구현만 하면 Hilt를 통해 자동으로 의존성 주입을 받을 수 있게 됩니다.

저는 DataModule과 NetworkModule로 나눠서 진행했습니다.

@Module
@InstallIn(SingletonComponent::class)
class DataModule {
    @Provides
    @Singleton
    fun provideMovieRemoteDataSource(
        movieApi: MovieApi
    ): MovieDataSource.Remote {
        return MovieRemoteDataSource(movieApi)
    }
    
   @Provides
    @Singleton
    fun provideMovieRepository(
        movieRemote: MovieDataSource.Remote
    ): MovieRepository {
        return MovieRepositoryImpl(movieRemote)
    }

    @Provides
    fun provideGetMovieDetailUseCase(movieRepository: MovieRepository): GetMovieDetail {
        return GetMovieDetail(movieRepository)
    }
}

 

@Module
@InstallIn(SingletonComponent::class)
class NetworkModule {
    @Singleton
    @Provides
    fun provideRetrofit(client: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .addCallAdapterFactory(CoroutineCallAdapterFactory())
            .addConverterFactory(GsonConverterFactory.create())
            .client(client)
            .baseUrl(BuildConfig.BASE_URL)
            .build()
    }

    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder()
        .addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC))
        .build()


    @Singleton
    @Provides
    fun provideMovieApi(retrofit: Retrofit): MovieApi {
        return retrofit.create(MovieApi::class.java)
    }
}

3. Hilt를 이용한 의존성 주입 정리

ViewModel에서 UseCase와 같은 의존성을 주입받기 위해, @HiltViewModel 어노테이션과 @Inject 생성자를 사용하여 Hilt에게 필요한 의존성을 알려주었습니다. 이를 통해, ViewModel은 UseCase를 직접 생성하거나 관리할 필요 없이, 비즈니스 로직 구현에만 집중하면 됩니다. 또한, UseCase 생성 시 필요한 Repository와, Repository 생성 시 필요한 DataSource 및 Retrofit client 등의 의존성을 제공하기 위해 @Module 어노테이션을 사용한 Hilt 모듈을 구성했습니다.

DataModule과 NetworkModule에서는 각각 데이터 처리와 네트워크 통신에 필요한 의존성을 제공하도록 구성했습니다. Hilt는 이 모듈을 통해 인스턴스가 필요한 곳에서 의존성을 자동으로 주입하게 됩니다.

다음 포스팅에서는 UI Layer에서 실제로 UI 컴포넌트와 ViewModel을 연결하고, 화면에 데이터를 표시하는 방법에 대해 설명하겠습니다.

반응형