들어가기 전
‘엄빠도어렸다’ 프로젝트 2차 스프린트 중, 기존 코드를 Hilt를 통해 의존성 주입 리팩토링을 진행하였습니다.
그 과정과 의존성 주입으로 인해 변경된 점, 의존성 주입과 Hilt의 장점 등을 포스팅하고자 합니다.
기존 코드와 Hilt의 필요성
class HomeRemoteDataSource {
private val homeService = ServicePool.homeService
}
2차 스프린트 이전의 코드는 정적 객체에 직접 의존하는 방식으로 작성되어 있었습니다.
이 방식의 문제점은 다음과 같습니다:
의존성 관리의 어려움
HomeRemoteDataSource
클래스는 ServicePool
의 정적 객체에 직접 의존합니다.
이로 인해 코드 변경이나 테스트에 유연성이 떨어지고, Mock 객체를 활용한 테스트가 어렵습니다.
확장성 부족
새로운 HomeService
구현체를 도입하려면 ServicePool
과 HomeRemoteDataSource
를 모두 수정해야 합니다. 이는 코드 유지보수에 부담을 줍니다.
따라서 외부에서 객체를 주입받는 방식으로 전환할 필요성을 느꼈고, 이를 위해 Dependency Injection(DI)을 도입하게 되었습니다.
Android에서 Dependency Injection
안드로이드에서 DI를 구현할 수 있는 프레임워크로는 Dagger, Hilt, Koin 등이 있습니다.
이 중 Hilt를 선택한 이유는 다음과 같습니다.
Hilt는 안드로이드의 주요 컴포넌트(Activity, Fragment, ViewModel 등)에 DI를 쉽게 적용할 수 있습니다.
또한 Dagger를 기반으로 하며, 복잡한 설정 없이 어노테이션을 활용해 간결한 방식으로 DI를 구성할 수 있습니다.
Hilt 적용 과정
1. Application 클래스
Hilt를 사용하려면 @HiltAndroidApp
어노테이션을 Application
클래스에 추가해야 합니다.
이는 Hilt에 의존성 주입을 시작하는 지점을 알려줍니다.
@HiltAndroidApp
class MyApplication : Application
2. Activity / Fragment
@AndroidEntryPoint
어노테이션을 추가해 Hilt가 해당 클래스의 의존성을 관리하도록 설정합니다.
@AndroidEntryPoint
class HomeActivity : AppCompatActivity() {
private val viewModel: HomeViewModel by viewModels()
}
private val viewModel: HomeViewModel by viewModels { ViewModelFactory(this) }
private val viewModel: HomeViewModel by viewModels()
이전에는 ViewModel의 의존성 주입을 직접하기 위해서 ViewModelFactory를 사용해야했습니다.
하지만 DI 적용 시 by viewModels 로 hilt가 제공하는 ViewModel 팩토리를 자동으로 사용하므로 더이상 ViewModelFacotry를 작성할 필요가 없어졌습니다. 이로 인해 리팩토링 전후, viewModel 선언 방식이 더욱 간결해졌습니다.
3. ViewModel
Hilt를 사용하려면 @HiltViewModel
어노테이션을 추가하고, 생성자에 @Inject
를 사용해 의존성을 주입받습니다.
class HomeViewModel(private val homeRepositoryImpl: HomeRepositoryImpl) : ViewModel()
@HiltViewModel
class HomeViewModel @Inject constructor(
private val homeRepository: HomeRepository
) : ViewModel()
기존에는 구현체(HomeRepositoryImpl
)에 직접 의존했으나, Hilt 적용 후 인터페이스를 통해 의존성을 주입받도록 변경했습니다. 이로인해 결합도를 낮춰지고 테스트에도 유연해지는 효과를 얻을 수 있습니다.
4. Repository
Hilt를 사용하면서 Repository
와 Impl
을 인터페이스 기반으로 분리했습니다. ViewModel에서 Repository
인터페이스를 주입받도록 변경했습니다.
하지만 이로인해 알맞은 Impl 주입을 위한 Hilt에게 매핑 정보를 제공하기 위해 Module
을 작성했습니다.
@InstallIn(SingletonComponent::class)
@Module
abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindHomeRepository(
homeRepositoryImpl: HomeRepositoryImpl
): HomeRepository
}
@Module
: 의존성 규칙 정의합니다.
@Binds
: 인터페이스와 구현체의 1:1 매핑을 정의합니다.
@Singleton
: Singleton 범위로 객체를 관리합니다.
5. Service
Retrofit
서비스 생성도 Module
을 통해 주입받도록 변경했습니다.
@Module
@InstallIn(SingletonComponent::class)
object ServiceModule {
@Singleton
@Provides
fun provideHomeService(retrofit: Retrofit): HomeService =
retrofit.create(HomeService::class.java)
}
6. DataSource
DataSource 클래스에서도 @Inject
를 사용해 Service 의존성을 주입받도록 수정했습니다.
class HomeRemoteDataSource {
private val homeService = ServicePool.homeService
}
class HomeRemoteDataSource @Inject constructor(
private val homeService: HomeService
)
Hilt 리팩토링 후 장점
유지보수성 향상 : 객체 생성 및 의존성 관리를 Hilt가 담당하므로 코드가 간결해지고 변경에 유연해졌습니다.
결합도 감소 : 인터페이스 기반 설계를 통해 클래스 간의 의존성을 낮추고 확장 가능성을 높였습니다.
코드 간소화 : 기존 팩토리 클래스와 정적 객체의 의존성을 제거하여 선언 방식이 간결해졌습니다.
'Android > 프로젝트 개발' 카테고리의 다른 글
[Android] Retrofit에서 XML 데이터 통신 및 파싱 방법 / RecyclerView XML 데이터 받아오기 (0) | 2024.02.26 |
---|---|
[Android] 인앱 업데이트 구현하기 / 인앱 업데이트 테스트 (1) | 2023.11.01 |
[Android] registerForActivityResult로 팝업 Activity를 만들어보자 (0) | 2023.08.26 |
[Android] 안드로이드 MaskFilter로 blur 효과 구현하기 (0) | 2023.08.07 |
[SWith] 검색 무한스크롤 (0) | 2022.09.24 |