developer.android.com 참고
Hilt 란 ?
Hilt는 dependency Injection을 관리(Annotation을 통한 관리) 할 수있게 해주는 library로 spring에서 수행하는 bean 관리를 해주는 역활과 같은 것임
https://developer.android.com/training/dependency-injection/hilt-android?hl=ko
Hilt를 사용한 종속 항목 삽입 | Android 개발자 | Android Developers
Hilt를 사용한 종속 항목 삽입 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Hilt는 프로젝트에서 종속 항목 수동 삽입을 실행하는 상용구를 줄이는 Android용
developer.android.com
Hilt는 프로젝트에서 종속 항목 수동 삽입을 실행하는 상용구를 줄이는 Android용 종속 항목 삽입 라이브러리입니다. 종속 항목 수동 삽입을 실행하려면 모든 클래스와 종속 항목을 수동으로 구성하고 컨테이너를 사용하여 종속 항목을 재사용 및 관리해야 합니다.
Hilt는 프로젝트의 모든 Android 클래스에 컨테이너를 제공하고 수명 주기를 자동으로 관리함으로써 애플리케이션에서 DI를 사용하는 표준 방법을 제공합니다. Hilt는 Dagger가 제공하는 컴파일 시간 정확성, 런타임 성능, 확장성 및 Android 스튜디오 지원의 이점을 누리기 위해 인기 있는 DI 라이브러리인 Dagger를 기반으로 빌드되었습니다. 자세한 내용은 Hilt 및 Dagger를 참조하세요.
이 가이드에서는 Hilt의 기본 개념 및 생성된 컨테이너에 관해 설명합니다. 또한 데모를 통해 기존 앱을 부트스트랩하여 Hilt를 사용하는 방법을 보여줍니다.
종속 항목 추가
먼저 hilt-android-gradle-plugin 플러그인을 프로젝트의 루트 build.gradle 파일에 추가합니다.
그런 다음, Gradle 플러그인을 적용하고 app/build.gradle 파일에 다음 종속 항목을 추가합니다.
...
plugins {
id 'kotlin-kapt'
id 'com.google.dagger.hilt.android'
}
android {
...
}
dependencies {
implementation "com.google.dagger:hilt-android:2.44"
kapt "com.google.dagger:hilt-compiler:2.44"
}
// Allow references to generated code
kapt {
correctErrorTypes true
}
참고: Hilt와 데이터 결합을 모두 사용하는 프로젝트에는 Android 스튜디오 4.0 이상이 필요합니다.
Hilt는 자바 8 기능을 사용합니다. 프로젝트에서 자바 8을 사용 설정하려면 app/build.gradle 파일에 다음을 추가합니다.
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
Hilt 애플리케이션 클래스
Hilt를 사용하는 모든 앱은 @HiltAndroidApp으로 주석이 지정된 Application 클래스를 포함해야 합니다.
@HiltAndroidApp은 애플리케이션 수준 종속 항목 컨테이너 역할을 하는 애플리케이션의 기본 클래스를 비롯하여 Hilt의 코드 생성을 트리거합니다.
생성된 이 Hilt 구성요소는 Application 객체의 수명 주기에 연결되며 이와 관련한 종속 항목을 제공합니다. 또한 이는 앱의 상위 구성요소이므로 다른 구성요소는 이 상위 구성요소에서 제공하는 종속 항목에 액세스할 수 있습니다.
Android 클래스에 종속 항목 삽입
Application 클래스에 Hilt를 설정하고 애플리케이션 수준 구성요소를 사용할 수 있게 되면 Hilt는 @AndroidEntryPoint 주석이 있는 다른 Android 클래스에 종속 항목을 제공할 수 있습니다.
Hilt는 현재 다음 Android 클래스를 지원합니다.
- Application(@HiltAndroidApp을 사용하여)
- ViewModel(@HiltViewModel을 사용하여)
- Activity
- Fragment
- View
- Service
- BroadcastReceiver
Android 클래스에 @AndroidEntryPoint로 주석을 지정하면 이 클래스에 종속된 Android 클래스에도 주석을 지정해야 합니다. 예를 들어 프래그먼트에 주석을 지정하면 이 프래그먼트를 사용하는 활동에도 주석을 지정해야 합니다.
참고: Android 클래스에 관한 Hilt 지원에는 다음과 같은 예외가 적용됩니다.
- Hilt는 AppCompatActivity와 같은 ComponentActivity를 확장하는 활동만 지원합니다.
- Hilt는 androidx.Fragment를 확장하는 프래그먼트만 지원합니다.
- Hilt는 보존된 프래그먼트를 지원하지 않습니다.
@AndroidEntryPoint는 프로젝트의 각 Android 클래스에 관한 개별 Hilt 구성요소를 생성합니다. 이러한 구성요소는 구성요소 계층 구조에 설명된 대로 해당하는 각 상위 클래스에서 종속 항목을 받을 수 있습니다.
구성요소에서 종속 항목을 가져오려면 다음과 같이 @Inject 주석을 사용하여 필드 삽입을 실행합니다.
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
@Inject lateinit var analytics: AnalyticsAdapter
...
}
참고: Hilt가 삽입한 필드는 비공개일 수 없습니다. Hilt를 사용하여 비공개 필드를 삽입하려고 하면 컴파일 오류가 발생합니다.
Hilt가 삽입하는 클래스에는 삽입도 사용하는 다른 기본 클래스가 있을 수 있습니다. 이러한 클래스는 추상적인 경우 @AndroidEntryPoint 주석이 필요하지 않습니다.
Android 클래스가 삽입되는 수명 주기 콜백에 관한 자세한 내용은 구성요소 전체 기간을 참고하세요.
Hilt 결합 정의
필드 삽입을 실행하려면 Hilt가 해당 구성요소에서 필요한 종속 항목의 인스턴스를 제공하는 방법을 알아야 합니다. 결합에는 특정 유형의 인스턴스를 종속 항목으로 제공하는 데 필요한 정보가 포함됩니다.
Hilt에 결합 정보를 제공하는 한 가지 방법은 생성자 삽입입니다. 다음과 같이 클래스의 생성자에서 @Inject 주석을 사용하여 클래스의 인스턴스를 제공하는 방법을 Hilt에 알려줍니다.
class AnalyticsAdapter @Inject constructor(
private val service: AnalyticsService
) { ... }
주석이 지정된 클래스 생성자의 매개변수는 그 클래스의 종속 항목입니다. 이 예에서 AnalyticsAdapter에는 AnalyticsService가 종속 항목으로 있습니다. 따라서 Hilt는 AnalyticsService의 인스턴스를 제공하는 방법도 알아야 합니다.
참고: 빌드 시간에 Hilt는 Android 클래스용 Dagger 구성요소를 생성합니다. 그런 다음, Dagger는 코드를 검토하고 다음 단계를 실행합니다.
- 종속 항목 그래프를 빌드하고 그 유효성을 검사하여 만족스럽지 않은 종속 항목과 종속 항목 주기가 없도록 합니다.
- 런타임 시 실제 객체 및 종속 항목을 만드는 데 사용되는 클래스를 생성합니다.
Hilt 모듈
때로 유형을 생성자 삽입할 수 없는 상황도 있습니다. 이러한 상황은 여러 가지 이유로 인해 발생할 수 있습니다. 예를 들어 인터페이스를 생성자 삽입할 수 없습니다. 또한 외부 라이브러리의 클래스와 같이 소유하지 않은 유형도 생성자 삽입할 수 없습니다. 이럴 때는 Hilt 모듈을 사용하여 Hilt에 결합 정보를 제공할 수 있습니다.
Hilt 모듈은 @Module로 주석이 지정된 클래스입니다. Dagger 모듈과 마찬가지로 이 모듈은 특정 유형의 인스턴스를 제공하는 방법을 Hilt에 알려줍니다. 그러나 Dagger 모듈과는 달리, Hilt 모듈에 @InstallIn 주석을 지정하여 각 모듈을 사용하거나 설치할 Android 클래스를 Hilt에 알려야 합니다.
참고: Hilt 모듈은 Gradle 모듈과 다릅니다.
Hilt 모듈에 제공하는 종속 항목은 Hilt 모듈을 설치하는 Android 클래스와 연결되어 있는 모든 생성된 구성요소에서 사용할 수 있습니다.
참고: Hilt의 코드를 생성하려면 Hilt를 사용하는 모든 Gradle 모듈에 액세스해야 하므로 Application 클래스를 컴파일하는 Gradle 모듈에는 전이 종속 항목에 모든 Hilt 모듈과 생성자 삽입 클래스도 있어야 합니다.
@Binds를 사용하여 인터페이스 인스턴스 삽입
AnalyticsService 예를 생각해 보세요. AnalyticsService가 인터페이스라면 이 인터페이스를 생성자 삽입할 수 없습니다. 대신 Hilt 모듈 내에 @Binds로 주석이 지정된 추상 함수를 생성하여 Hilt에 결합 정보를 제공합니다.
@Binds 주석은 인터페이스의 인스턴스를 제공해야 할 때 사용할 구현을 Hilt에 알려줍니다.
주석이 지정된 함수는 Hilt에 다음 정보를 제공합니다.
- 함수 반환 유형은 함수가 어떤 인터페이스의 인스턴스를 제공하는지 Hilt에 알려줍니다.
- 함수 매개변수는 제공할 구현을 Hilt에 알려줍니다.
interface AnalyticsService {
fun analyticsMethods()
}
// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
class AnalyticsServiceImpl @Inject constructor(
...
) : AnalyticsService { ... }
@Module
@InstallIn(ActivityComponent.class)
abstract class AnalyticsModule {
@Binds
abstract fun bindAnalyticsService(
analyticsServiceImpl: AnalyticsServiceImpl
): AnalyticsService
}
Hilt가 AnalyticsModule의 종속 항목을 ExampleActivity에 삽입하기를 원하기 때문에 Hilt 모듈 AnalyticsModule에 @InstallIn(ActivityComponent.class) 주석을 지정합니다. 이 주석은 AnalyticsModule의 모든 종속 항목을 앱의 모든 활동에서 사용할 수 있음을 의미합니다.
@Provides를 사용하여 인스턴스 삽입
유형을 생성자 삽입할 수 없는 것은 인터페이스만이 아닙니다. 클래스가 외부 라이브러리에서 제공되므로 클래스를 소유하지 않은 경우(Retrofit, OkHttpClient 또는 Room 데이터베이스와 같은 클래스) 또는 빌더 패턴으로 인스턴스를 생성해야 하는 경우에도 생성자 삽입이 불가능합니다.
이전 예를 생각해 보세요. AnalyticsService 클래스를 직접 소유하지 않으면 Hilt 모듈 내에 함수를 생성하고 이 함수에 @Provides 주석을 지정하여 이 유형의 인스턴스를 제공하는 방법을 Hilt에 알릴 수 있습니다.
주석이 달린 함수는 Hilt에 다음 정보를 제공합니다.
- 함수 반환 유형은 함수가 어떤 유형의 인스턴스를 제공하는지 Hilt에 알려줍니다.
- 함수 매개변수는 해당 유형의 종속 항목을 Hilt에 알려줍니다.
- 함수 본문은 해당 유형의 인스턴스를 제공하는 방법을 Hilt에 알려줍니다. Hilt는 해당 유형의 인스턴스를 제공해야 할 때마다 함수 본문을 실행합니다.
@Module
@InstallIn(ActivityComponent.class)
object AnalyticsModule {
@Provides
fun provideAnalyticsService(
// Potential dependencies of this type
): AnalyticsService {
return Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(AnalyticsService::class.java)
}
}
동일한 유형에 대해 여러 결합 제공
종속 항목과 동일한 유형의 다양한 구현을 제공하는 Hilt가 필요한 경우에는 Hilt에 여러 결합을 제공해야 합니다. 한정자를 사용하여 동일한 유형에 대해 여러 결합을 정의할 수 있습니다.
한정자는 특정 유형에 대해 여러 결합이 정의되어 있을 때 그 유형의 특정 결합을 식별하는 데 사용하는 주석입니다.
다음 예를 생각해 보세요. AnalyticsService 호출을 가로채야 한다면 인터셉터와 함께 OkHttpClient 객체를 사용할 수 있습니다. 다른 서비스에서는 호출을 다른 방식으로 가로채야 할 수도 있습니다. 이 경우에는 서로 다른 두 가지 OkHttpClient 구현을 제공하는 방법을 Hilt에 알려야 합니다.
먼저 다음과 같이 @Binds 또는 @Provides 메서드에 주석을 지정하는 데 사용할 한정자를 정의합니다.
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient
그런 다음, Hilt는 각 한정자와 일치하는 유형의 인스턴스를 제공하는 방법을 알아야 합니다. 이 경우에는 @Provides와 함께 Hilt 모듈을 사용할 수 있습니다. 두 메서드 모두 동일한 반환 유형을 갖지만 한정자는 다음과 같이 두 가지의 서로 다른 결합으로 메서드에 라벨을 지정합니다.
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@AuthInterceptorOkHttpClient
@Provides
fun provideAuthInterceptorOkHttpClient(
authInterceptor: AuthInterceptor
): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(authInterceptor)
.build()
}
@OtherInterceptorOkHttpClient
@Provides
fun provideOtherInterceptorOkHttpClient(
otherInterceptor: OtherInterceptor
): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(otherInterceptor)
.build()
}
}
다음과 같이 필드 또는 매개변수에 해당 한정자로 주석을 지정하여 필요한 특정 유형을 삽입할 수 있습니다.
// As a dependency of another class.
@Module
@InstallIn(ActivityComponent.class)
object AnalyticsModule {
@Provides
fun provideAnalyticsService(
@AuthInterceptorOkHttpClient okHttpClient: OkHttpClient
): AnalyticsService {
return Retrofit.Builder()
.baseUrl("https://example.com")
.client(okHttpClient)
.build()
.create(AnalyticsService::class.java)
}
}
// As a dependency of a constructor-injected class.
class ExampleServiceImpl @Inject constructor(
@AuthInterceptorOkHttpClient private val okHttpClient: OkHttpClient
) : ...
// At field injection.
@AndroidEntryPoint
class ExampleActivity: AppCompatActivity() {
@AuthInterceptorOkHttpClient
@Inject lateinit var okHttpClient: OkHttpClient
}
한정자를 유형에 추가한다면 그 종속 항목을 제공하는 가능한 모든 방법에 한정자를 추가하는 것이 좋습니다. 기본 또는 일반 구현을 한정자 없이 그대로 두면 오류가 발생하기 쉬우며 Hilt가 잘못된 종속 항목을 삽입할 수 있습니다.
Hilt의 사전 정의된 한정자
Hilt는 몇 가지 사전 정의된 한정자를 제공합니다. 예를 들어 애플리케이션 또는 활동의 Context 클래스가 필요할 수 있으므로 Hilt는 @ApplicationContext 및 @ActivityContext 한정자를 제공합니다.
예의 AnalyticsAdapter 클래스에 활동 컨텍스트가 필요하다고 가정해 보겠습니다. 다음 코드는 AnalyticsAdapter에 활동 컨텍스트를 제공하는 방법을 보여줍니다.
class AnalyticsAdapter @Inject constructor(
@ActivityContext private val context: Context,
private val service: AnalyticsService
) { ... }
Hilt에서 사용 가능한 사전 정의된 다른 결합은 구성요소 기본 결합을 참고하세요.
Android 클래스용으로 생성된 구성요소
필드 삽입을 실행할 수 있는 각 Android 클래스마다 @InstallIn 주석에 참조할 수 있는 관련 Hilt 구성요소가 있습니다. 각 Hilt 구성요소는 해당 Android 클래스에 결합을 삽입해야 합니다.
이전 예에서는 Hilt 모듈에서 ActivityComponent를 사용하는 방법을 보여주었습니다.
Hilt는 다음 구성요소를 제공합니다.
==== 좀더 세부적으로 이해하기 위해서는 아직... ===============
Room Database 란?
Spring framework의 JPA와 동일한 역활(기능)을 수행 : 같은 것 같은데...
https://developer.android.com/training/data-storage/room?hl=ko
Room을 사용하여 로컬 데이터베이스에 데이터 저장 | Android 개발자 | Android Developers
Room 라이브러리를 사용하여 더 쉽게 데이터를 유지하는 방법 알아보기
developer.android.com
상당한 양의 구조화된 데이터를 처리하는 앱은 데이터를 로컬에 유지하여 매우 큰 이익을 얻을 수 있습니다. 가장 일반적인 사용 사례는 기기가 네트워크에 액세스할 수 없을 때도 사용자가 오프라인 상태로 계속 콘텐츠를 탐색할 수 있도록 관련 데이터를 캐시하는 것입니다.
Room 지속성 라이브러리는 SQLite를 완벽히 활용하면서 원활한 데이터베이스 액세스가 가능하도록 SQLite의 추상화 계층을 제공합니다. 특히 Room을 사용하면 다음과 같은 이점이 있습니다.
- SQL 쿼리의 컴파일 시간 확인
- 반복적이고 오류가 발생하기 쉬운 상용구 코드를 최소화하는 편의 주석
- 간소화된 데이터베이스 이전 경로
이러한 점을 고려할 때 SQLite API를 직접 사용하는 대신 Room을 사용하는 것이 좋습니다.
설정
앱에서 Room을 사용하려면 앱의 build.gradle 파일에 다음 종속 항목을 추가합니다.
dependencies {
def room_version = "2.5.0"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
// To use Kotlin annotation processing tool (kapt)
kapt "androidx.room:room-compiler:$room_version"
// To use Kotlin Symbol Processing (KSP)
ksp "androidx.room:room-compiler:$room_version"
// optional - RxJava2 support for Room
implementation "androidx.room:room-rxjava2:$room_version"
// optional - RxJava3 support for Room
implementation "androidx.room:room-rxjava3:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"
// optional - Test helpers
testImplementation "androidx.room:room-testing:$room_version"
// optional - Paging 3 Integration
implementation "androidx.room:room-paging:$room_version"
}
기본 구성요소
Room에는 다음 3가지 주요 구성요소가 있습니다.
- 데이터베이스 클래스: 데이터베이스를 보유하고 앱의 영구 데이터와의 기본 연결을 위한 기본 액세스 포인트 역할을 합니다.
- 데이터 항목: 앱 데이터베이스의 테이블을 나타냅니다.
- 데이터 액세스 객체(DAO): 앱이 데이터베이스의 데이터를 쿼리, 업데이트, 삽입, 삭제하는 데 사용할 수 있는 메서드를 제공합니다.
데이터베이스 클래스는 데이터베이스와 연결된 DAO 인스턴스를 앱에 제공합니다. 그러면 앱은 DAO를 사용하여 데이터베이스의 데이터를 연결된 데이터 항목 객체의 인스턴스로 검색할 수 있게 됩니다. 앱은 정의된 데이터 항목을 사용하여 상응하는 테이블의 행을 업데이트하거나 삽입할 새 행을 만들 수도 있습니다. 그림 1은 다양한 Room 구성요소 간 관계를 보여줍니다.
그림 1. Room 라이브러리 아키텍처 다이어그램
샘플 구현
이 섹션에서는 단일 데이터 항목과 단일 DAO가 있는 Room 데이터베이스의 구현 예를 보여줍니다.
데이터 항목
다음 코드는 User 데이터 항목을 정의합니다. 각 User 인스턴스는 앱 데이터베이스의 user 테이블에 있는 행 하나를 나타냅니다.
@Entity
data class User(
@PrimaryKey val uid: Int,
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
Room의 데이터 항목에 관한 자세한 내용은 Room 항목을 사용하여 데이터 정의를 참고하세요.
데이터 액세스 객체(DAO)
다음 코드는 UserDao라는 DAO를 정의합니다. UserDao는 앱의 나머지 부분이 user 테이블의 데이터와 상호작용하는 데 사용하는 메서드를 제공합니다.
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAll(): List<User>
@Query("SELECT * FROM user WHERE uid IN (:userIds)")
fun loadAllByIds(userIds: IntArray): List<User>
@Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
"last_name LIKE :last LIMIT 1")
fun findByName(first: String, last: String): User
@Insert
fun insertAll(vararg users: User)
@Delete
fun delete(user: User)
}
DAO에 관한 자세한 내용은 Room DAO를 사용하여 데이터 액세스를 참고하세요.
데이터베이스
다음 코드는 데이터베이스를 보유할 AppDatabase 클래스를 정의합니다. AppDatabase는 데이터베이스 구성을 정의하고 영구 데이터에 대한 앱의 기본 액세스 포인트 역할을 합니다. 데이터베이스 클래스는 다음 조건을 충족해야 합니다.
- 클래스에는 데이터베이스와 연결된 데이터 항목을 모두 나열하는 entities 배열이 포함된 @Database 주석이 달려야 합니다.
- 클래스는 RoomDatabase를 확장하는 추상 클래스여야 합니다.
- 데이터베이스와 연결된 각 DAO 클래스에서 데이터베이스 클래스는 인수가 0개이고 DAO 클래스의 인스턴스를 반환하는 추상 메서드를 정의해야 합니다.
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
참고: 앱이 단일 프로세스에서 실행되면 AppDatabase 객체를 인스턴스화할 때 싱글톤 디자인 패턴을 따라야 합니다. 각 RoomDatabase 인스턴스는 리소스를 상당히 많이 소비하며 단일 프로세스 내에서 여러 인스턴스에 액세스해야 하는 경우는 거의 없습니다.
앱이 여러 프로세스에서 실행되는 경우 데이터베이스 빌더 호출에 enableMultiInstanceInvalidation()을 포함하세요. 이렇게 하면 각 프로세스에 AppDatabase 인스턴스가 있을 때 한 프로세스에서 공유 데이터베이스 파일을 무효화할 수 있으며 이 무효화는 다른 프로세스 내의 AppDatabase 인스턴스로 자동 전파됩니다.
사용
데이터 항목과 DAO, 데이터베이스 객체를 정의한 후에는 다음 코드를 사용하여 데이터베이스 인스턴스를 만들 수 있습니다.
val db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "database-name"
).build()
그런 다음 AppDatabase의 추상 메서드를 사용하여 DAO 인스턴스를 가져올 수 있습니다. 결과적으로 DAO 인스턴스의 메서드를 사용하여 데이터베이스와 상호작용할 수 있습니다.
추가 리소스
'Android-jetpack Compose' 카테고리의 다른 글
jetpack compose - dependency injection, Room 참고 자료-사이트 (0) | 2023.04.02 |
---|---|
Android Coroutine (1) | 2023.04.02 |
jackpack compose - Navigation : 정의, 개발 절차 샘플 (0) | 2023.03.30 |
jackpack compose - Stateful vs Stateless 비교 (0) | 2023.03.19 |
jetpack compose 시작하기, 관련 링크, 교육 자료 등 (0) | 2023.03.18 |