Sheath는 Android 전용 기능이 추가된 코틀린 전용 종속 항목 삽입 라이브러리입니다. Hilt와 Koin의 사용성을 보완하고자 만들었습니다.
1. Scope
Koin은 범위를 커스텀으로 지정하고 범위가 닫힐 때까지 해당 범위 내에서 같은 인스턴스를 사용할 수 있습니다. Hilt는 Android 클래스와 관련된 범위를 지정할 수 있습니다. 또한 Hilt는 범위를 지정하지 않으면 기본적으로 항상 새 인스턴스를 생성합니다.
Sheath는 세 가지의 범위만 존재합니다. 그리고 범위를 지정하지 않으면 기본적으로 싱글톤 인스턴스를 사용합니다.
2. 생성자 삽입
Koin은 생성자 삽입을 할 때 모듈에서 get()
메서드를 사용해 종속 항목을 삽입합니다. Hilt는 생성자 삽입을 할 때 @Inject
주석을 붙이기
위해 constructor
예약어를 생략할 수 없습니다.
Sheath는 생성자에 @Inject
주석이 붙어 있지 않다면 주 생성자로 생성자 삽입을 하기 때문에 constructor
예약어를 생략해도 됩니다.
3. 모듈
Koin은 모든 의존성 관리 대상을 모듈에 등록해야 합니다. Hilt는 종속 항목의 타입과 의존성 관리 대상이 타입이 다르면 결합 방식을 알리기 위해 모듈에 등록해야 합니다.
Sheath는 종속 항목의 타입이 의존성 관리 대상의 부모 타입이라면 종속 항목을 제공받을 수 있습니다.
4. 잘못된 의존 그래프(종속 항목을 제공할 수 없거나 모호하거나 의존 사이클이 존재하는 경우)일 때 에러 발생 시점
Koin은 해당 종속 항목이 사용될 때 에러가 발생합니다. Hilt는 컴파일 타임에 에러가 발생합니다.
Sheath는 Android 클래스에서는 해당 종속 항목이 사용될 때 에러가 발생합니다. 일반 클래스에서 잘못되었다면 Sheath 애플리케이션이 실행된 후 에러가 발생합니다.
따라서 Android의 Application
클래스에서 onCreate()
일 때 Sheath 애플리케이션을 실행하여 에러를 빨리 마주하는 게 좋습니다.
먼저 maven { url "https://jitpack.io" }
저장소를 프로젝트의 루트 build.gradle
파일에 추가합니다.
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
...
maven { url "https://jitpack.io" }
}
}
그런 다음, app/build.gradle
파일에 다음 의존성을 추가합니다.
plugins {
id 'kotlin-kapt'
}
android {
...
}
dependencies {
implementation "com.github.ki960213:sheath:1.0.2"
kapt "com.github.ki960213:sheath:1.0.2"
}
참고 sheath의 최신 버전은 해당 페이지 오른쪽 위의 releases에서 확인하실 수 있습니다.
Sheath를 사용하는 모든 앱은 SheathApplication.run(applicationContext)
명령어를 실행하여 Sheath 애플리케이션을 시작해야 합니다.
Sheath 애플리케이션을 시작하면 의존성 관리 대상인 Sheath 컴포넌트를 Sheath 컨테이너에 등록합니다. Android 클래스 와 Sheath 컴포넌트는 Sheath 컨테이너에 등록된 종속 항목을 제공 받을 수 있습니다.
Sheath 컴포넌트란 의존성 관리 대상을 의미합니다. Sheath 컴포넌트를 등록하는 방법은 다음과 같습니다.
- 클래스에
@Component
주석 혹은@Component
주석이 붙은 애노테이션 붙이기 - Sheath 모듈에 종속 항목을 제공하는 함수 정의하고 그 함수에
@Component
주석 붙이기
어떤 클래스를 Sheath 컴포넌트로 등록하고 싶다면 @Component
주석 혹은 @Component
주석이 붙은 애노테이션을 붙이면 됩니다.
@Component
class ExampleClass {
...
}
참고로 @Component
주석이 붙은 애노테이션이란 단지 가독성을 위해 존재하는 애노테이션입니다. @Component
주석을 붙인 것과 동일하게
취급됩니다. @Component
주석이 붙은 애노테이션 종류는 다음과 같습니다.
@UseCase
@Repository
@DataSource
제약사항
@Component
주석 혹은@Component
주석이 붙은 애노테이션은 인스턴스를 생성할 수 없는 클래스에는 붙일 수 없습니다. 만약 abstract 클래스나 interface에 붙인다면 Sheath 애플리케이션이 실행된 후 런타임 에러가 발생합니다.
클래스가 외부 라이브러리에서 제공되므로 클래스를 소유하지 않은 경우(Retrofit, OkHttpClient
또는 Room 데이터베이스와 같은 클래스) 또는 빌더 패턴으로
인스턴스를 생성해야 하는 경우 클래스에 애노테이션을 붙여서 Sheath 컴포넌트로 등록할 수 없습니다.
이러한 경우 Sheath 모듈 내에 함수를 생성하고 이 함수에 @Component
주석을 붙여 특정 타입의 인스턴스를 제공하는 방법을 Sheath에 알릴 수 있습니다.
주석이 달린 함수는 Sheath
에 다음 정보를 제공합니다.
- 함수 반환 타입은 함수가 어떤 타입의 인스턴스를 제공하는지 Sheath에 알려줍니다.
- 함수 매개변수는 해당 타입의 종속 항목을 Sheath에 알려줍니다.
- 함수 본문은 해당 타입의 인스턴스를 제공하는 방법을 Sheath에 알려줍니다. 인스턴스를 생성해야 할 때 함수 본문을 실행합니다.
@Module
object AnalyticsModule {
@Component
fun provideAnalyticsService(
// 해당 타입의 종속 항목
): AnalyticsService = Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(AnalyticsService::class.java)
}
참고
@Component
주석이 붙은 애노테이션을 함수에 붙여도 잘 동작합니다.
제약사항 Sheath 모듈은 object 클래스여야 합니다. 그렇지 않으면 Sheath 애플리케이션이 실행된 후 런타임 에러가 발생합니다. 만약 함수가 nullable 타입을 반환한다면 Sheath 애플리케이션이 실행된 후 런타임 에러가 발생합니다.
Sheath 애플리케이션을 실행하여 Sheath 컴포넌트를 등록하게 되면 다른 Android 클래스에 컨테이너에 등록된 종속 항목을 제공할 수 있습니다.
Sheath는 현재 다음 Android 클래스를 지원합니다.
Application
ViewModel
(@SheathViewModel
을 사용하여)Activity
Fragment
Service
BroadcaseReceiver
Android 클래스에서 종속 항목을 가져오려면 다음과 같이 inject()
를 사용하여 종속 항목 주입을 실행합니다.
class ExapleActivity : AppCompatActivity() {
private analytics: AnalyticsAdapter by inject()
...
}
일반 클래스에 종속 항목 삽입 방법은 생성자 삽입, 프로퍼티 삽입, 메서드 삽입 세 가지가 있습니다. 참고로 Sheath 컴포넌트를 삽입 받는 클래스는 모든 삽입이 완료된 상태의 종속 항목을 주입 받습니다.
생성자에 @Inject
주석을 붙이거나 어떠한 생성자에 @Inject
주석이 붙어 있지 않다면 주 생성자로 종속 항목을 삽입받습니다. 만약 여러 생성자에 @Inject
주석이 붙어 있다면 Sheath 애플리케이션이 실행된 후 런타임 에러가 발생합니다.
@Component
class ExampleClass(exam: ExampleClass2) {
...
}
아래의 경우 @Inject
가 붙은 생성자를 통해 종속 항목을 삽입받습니다.
@Component
class ExampleClass(str: String) {
@Inject
constructor(exam: ExampleClass2) : this(exam.toString())
...
}
프로퍼티에 @Inject
주석을 붙여 종속 항목을 삽입받을 수 있습니다.
@Component
class ExampleClass {
@Inject
private lateinit var exam: ExampleClass2
...
}
프로퍼티를 통해 삽입받을 땐 var
를 사용하여 프로퍼티를 선언해야 합니다. 그렇지 않으면 Sheath 애플리케이션이 실행된 후 런타임 에러가 발생합니다.
메서드에 @Inject
주석을 붙여 종속 항목을 삽입받을 수 있습니다.
@Component
class ExampleClass {
@Inject
fun exam(exam: ExampleClass2) {
...
}
}
참고로 매개변수가 없더라도 종속 항목을 삽입할 때 수행될 수 있습니다.
Sheath 컴포넌트의 범위는 세 가지가 있습니다.
- 싱글톤
- 프로토타입
- 종속 항목을 제공 받는 인스턴스의 생명주기에 맞춰진 범위
Sheath 컴포넌트를 등록할 때 @SheathViewModel
주석을 붙인 경우를 제외하고 추가적인 설정을 하지 않으면 싱글톤입니다. 처음 컨테이너에 등록될 때 만든
인스턴스가 제공됩니다.
Sheath 컴포넌트로 등록할 때 @Prototype
주석을 붙이면 해당 컴포넌트는 어떤 클래스에 제공되든 항상 새로운 인스턴스가 제공됩니다.
@Prototype
@Component
class ExampleClass {
...
}
@Module
object ExampleModule {
@Prototype
@Component
fun provideExampleClass2(): ExampleClass2 {
...
}
}
항상 새로운 인스턴스의 종속 항목을 주입 받고 싶다면 종속 항목 주입 방법 선언 시 @NewInstance
주석을 붙이면 됩니다.
만약 생성자 주입 시 새로운 인스턴스를 받고 싶다면 아래처럼 생성자의 매개변수에 @NewInstance
주석을 붙이면 됩니다.
@Component
class ExampleClass(@NewInstance private val exam: ExampleClass2) {
...
}
만약 프로퍼티 주입 시 새로운 인스턴스를 받고 싶다면 아래처럼 프로퍼티에 @NewInstance
주석을 붙이면 됩니다.
@Component
class ExampleClass {
@NewInstance
@Inject
private lateinit var exam: ExampleClass2
}
만약 메서드 주입 시 새로운 인스턴스를 받고 싶다면 아래처럼 메서드의 매개변수에 @NewInstance
주석을 붙이면 됩니다.
@Component
class ExampleClass {
@Inject
fun examFun(@NewInstance exam: ExampleClass2) {
...
}
}
만약 Sheath 모듈에 정의된 함수에서 새로운 인스턴스를 받고 싶다면 아래처럼 메서드의 매개변수에 @NewInstance
주석을 붙이면 됩니다.
@Module
object ExamModule {
@Component
fun provideExample(@NewInstance exam: ExampleClass2): ExampleClass {
...
}
}
Android 클래스에서 새로운 인스턴스를 받고 싶다면 아래처럼 inject()
메서드에 isNewInstance
값을 true
로 설정하면 됩니다.
class ExampleActivity : AppCompatActivity() {
private val exampleComponent: ExampleComponent by inject(isNewInstance = true)
...
}
@SheathViewModel
로 주석 처리하여 ViewModel
을 Sheath에 제공합니다.
@SheathViewModel
class ExampleViewModel(
private val repository: ExampleRepository,
) : ViewModel() {
...
}
그런 다음 액티비티 또는 프래그먼트는 by viewModels()
나 activityViewModels()
메서드를 사용하여 평소와 같이 ViewModel
인스턴스를
가져올 수 있습니다.
class ExampleActivity : AppCompatActivity() {
private val exampleViewModel: ExampleViewModel by viewModels()
...
}
참고:
@SheathViewModel
주석은@Prototype
주석과@Component
주석을 붙인 애노테이션입니다.
종속 항목과 동일한 타입의 다양한 구현을 제공하는 Sheath가 필요한 경우에는 한정자를 사용하여 동일한 타입에 대해 종속 항목을 정의해야 합니다.
한정자는 특정 타입에 대해 여러 Sheath 컴포넌트가 등록되어 있을 때 그 타입의 특정 종속 항목을 식별하는 데 사용하는 주석입니다.
다음 예를 생각해 보세요. AnalyticsService
호출을 가로채야 한다면 인터셉터와 함께 OkHttpClient
객체를 사용할 수 있습니다. 다른 서비스에서는 호출을
다른 방식으로 가로채야 할 수도 있습니다. 이 경우에는 서로 다른 두 가지 OkHttpClient
구현을 제공하는 방법을 Sheath에 알려야 합니다.
먼저 다음과 같이 @Qualifier
주석을 사용하여 사용할 한정자를 정의합니다.
@Qualifier
annotation class AuthInterceptorOkHttpClient
@Qualifier
annotation class OtherInterceptorOkHttpClient
그런 다음, Sheath는 각 한정자와 일치하는 타입의 인스턴스를 제공하는 방법을 알아야 합니다. 이 경우 Sheath 모듈을 사용할 수 있습니다. 두 메서드 모두 동일한 반환 타입을 갖지만 한정자는 다음과 같이 두 가지의 서로 다른 Sheath 컴포넌트로 메서드에 라벨을 지정합니다.
@Module
object NetworkModule {
@AuthInterceptorOkHttpClient
@Component
fun provideAuthInterceptorOkHttpClient(
authInterceptor: AuthInterceptor
) = OkHttpClient.Builder()
.addInterceptor(authInterceptor)
.build()
@OtherInterceptorOkHttpClient
@Component
fun provideOtherInterceptorOkHttpClient(
otherInterceptor: OtherInterceptor
) = OkHttpClient.Builder()
.addInterceptor(otherInterceptor)
.build()
}
다음과 같이 프로퍼티 또는 매개변수에 해당 한정자로 주석을 지정햐여 필요한 특정 종속 항목을 삽입할 수 있습나다.
// Sheath 모듈의 함수에 삽입 시
@Module
object AnalyticsModule {
@Component
fun provideAnalyticsService(
@AuthInterceptorOkHttpClient okHttpClient: OkHttpClient
): AnalyticsService = Retrofit.Builder()
.baseUrl("https://example.com")
.client(okHttpClient)
.build()
.create(AnalyticsService::class.java)
}
// 생성자 삽입 시
@Component
class ExampleService(
@AuthInterceptorOkHttpClient private val okHttpClient: OkHttpClient
) : ...
// 프로퍼티 삽입 시
class ExampleClass {
@AuthInterceptorOkHttpClient
@Inject
private lateinit var okHttpClient: OkHttpClient
...
}
// Android 클래스에 삽입 시
class ExampleActivity : AppCompatActivity() {
private val okHttpClient: OkHttpClient by inject(qualifier = AuthInterceptorOkHttpClient::class)
...
}
종속 항목인지 판단하는 기준은 타입과 한정자입니다. 만약 매개변수 혹은 프로퍼티에 한정자가 설정되지 않았다면 타입만을 기준으로 종속 항목인지 판단합니다. 만약 종속 항목이 모호하다면(여러 개의 컴포넌트가 종속 항목일 수 있다면) 런타임 에러가 발생합니다. 따라서 한정자를 타입에 추가한다면 그 타입의 종속 항목을 제공하는 가능한 모든 방법에 한정자를 추가하는 것이 좋습니다.
참고 Sheath 컴포넌트는 타입과 한정자에 의해 구분됩니다. 만약 같은 타입의 컴포넌트를 한정자로 구분짓지 않는다면 Sheath 애플리케이션이 실행된 후 런타임 에러가 발생합니다
Sheath는 애플리케이션 또는 액티비티의 Context
클래스가 필요할 수 있으므로 Context 컴포넌트를 기본적으로 컨테이너에 등록합니다. Context 컴포넌트의
인스턴스는 Sheath 애플리케이션을 실행할 때 인자로 넣은 인스턴스입니다.
R8이나 Proguard를 사용하면 작동하지 않습니다.