ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Codelabs Tutorial] Android 앱에서 Hilt 사용해보기 (4) with EntryPoint
    프로그래밍/Android 2022. 5. 19. 21:42

    @EntryPoint 주석

    Codelab의 이 섹션에서는 Hilt에서 지원하지 않는 클래스에 종속 항목을 삽입하기 위해 사용하는 @EntryPoint 주석의 사용법을 알아봅니다.

    앞에서 본 바와 같이 Hilt는 가장 일반적인 Android 구성요소를 지원합니다. 그러나, Hilt에서 직접 지원하지 않거나 Hilt를 사용할 수 없는 클래스에 필드를 삽입해야 할 수 있습니다.

    이러한 경우에는 @EntryPoint를 사용하면 됩니다. 진입점은 종속 항목을 삽입하는 데 Hilt를 사용할 수 없는 코드에서 Hilt가 제공하는 객체를 가져올 수 있는 경계 지점입니다. 이는 Hilt에서 관리하는 컨테이너에 코드가 처음 진입하는 지점입니다.

    사용 사례

    애플리케이션 프로세스 외부로 로그를 내보내려고 합니다. 이를 위해 ContentProvider를 사용해야 합니다. 현재 소비자가 특정한 하나의 로그(id 지정)를 쿼리하거나 ContentProvider를 사용하는 앱의 모든 로그를 쿼리하는 것만 허용하고 있습니다. Room 데이터베이스를 사용하여 데이터를 가져올 예정입니다. 따라서, LogDao 클래스는 Cursor 데이터베이스를 사용하여 필수 정보를 반환하는 메서드를 노출해야 합니다. LogDao.kt 파일을 열고 인터페이스에 다음 메서드를 추가합니다.

     
    @Dao
    interface LogDao {
        ...
    
        @Query("SELECT * FROM logs ORDER BY id DESC")
        fun selectAllLogsCursor(): Cursor
    
        @Query("SELECT * FROM logs WHERE id = :id")
        fun selectLogById(id: Long): Cursor?
    }

    그런 다음 새로운 ContentProvider 클래스를 만들고 query 메서드를 재정의하여 로그와 함께 Cursor를 반환해야 합니다. 새 contentprovider 디렉터리 아래 LogsContentProvider.kt라는 새 파일을 만들고 다음 콘텐츠를 포함합니다.

     
    package com.example.android.hilt.contentprovider
    
    import android.content.ContentProvider
    import android.content.ContentUris
    import android.content.ContentValues
    import android.content.Context
    import android.content.UriMatcher
    import android.database.Cursor
    import android.net.Uri
    import com.example.android.hilt.data.LogDao
    import dagger.hilt.EntryPoint
    import dagger.hilt.EntryPoints
    import dagger.hilt.InstallIn
    import dagger.hilt.android.components.ApplicationComponent
    import java.lang.UnsupportedOperationException
    
    /** The authority of this content provider.  */
    private const val LOGS_TABLE = "logs"
    
    /** The authority of this content provider.  */
    private const val AUTHORITY = "com.example.android.hilt.provider"
    
    /** The match code for some items in the Logs table.  */
    private const val CODE_LOGS_DIR = 1
    
    /** The match code for an item in the Logs table.  */
    private const val CODE_LOGS_ITEM = 2
    
    /**
     * A ContentProvider that exposes the logs outside the application process.
     */
    class LogsContentProvider: ContentProvider() {
    
        private val matcher: UriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
            addURI(AUTHORITY, LOGS_TABLE, CODE_LOGS_DIR)
            addURI(AUTHORITY, "$LOGS_TABLE/*", CODE_LOGS_ITEM)
        }
    
        override fun onCreate(): Boolean {
            return true
        }
    
        /**
         * Queries all the logs or an individual log from the logs database.
         *
         * For the sake of this codelab, the logic has been simplified.
         */
        override fun query(
            uri: Uri,
            projection: Array<out String>?,
            selection: String?,
            selectionArgs: Array<out String>?,
            sortOrder: String?
        ): Cursor? {
            val code: Int = matcher.match(uri)
            return if (code == CODE_LOGS_DIR || code == CODE_LOGS_ITEM) {
                val appContext = context?.applicationContext ?: throw IllegalStateException()
                val logDao: LogDao = getLogDao(appContext)
    
                val cursor: Cursor? = if (code == CODE_LOGS_DIR) {
                    logDao.selectAllLogsCursor()
                } else {
                    logDao.selectLogById(ContentUris.parseId(uri))
                }
                cursor?.setNotificationUri(appContext.contentResolver, uri)
                cursor
            } else {
                throw IllegalArgumentException("Unknown URI: $uri")
            }
        }
    
        override fun insert(uri: Uri, values: ContentValues?): Uri? {
            throw UnsupportedOperationException("Only reading operations are allowed")
        }
    
        override fun update(
            uri: Uri,
            values: ContentValues?,
            selection: String?,
            selectionArgs: Array<out String>?
        ): Int {
            throw UnsupportedOperationException("Only reading operations are allowed")
        }
    
        override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
            throw UnsupportedOperationException("Only reading operations are allowed")
        }
    
        override fun getType(uri: Uri): String? {
            throw UnsupportedOperationException("Only reading operations are allowed")
        }
    }

    getLogDao(appContext) 호출이 컴파일되지 않는 것을 볼 수 있습니다. Hilt 애플리케이션 컨테이너에서 LogDao 종속 항목을 가져와 구현해야 합니다. 하지만, Hilt에서는 @AndroidEntryPoint로 활동을 사용하여 삽입하는 것처럼 즉시 ContentProvider에 삽입하는 것을 지원하지 않습니다.

    @EntryPoint 주석이 달린 새 인터페이스를 만들어 액세스해야 합니다.

    @EntryPoint의 실제 사례

    진입점은 원하는 각 결합 유형에 접근자 메서드를 사용하는 인터페이스입니다(한정자 포함). 또한, 진입점을 설치할 구성요소를 지정하려면 인터페이스에 @InstallIn 주석을 달아야 합니다.

    권장사항은 이를 사용하는 클래스 내에 새 진입점 인터페이스를 추가하는 것입니다. 따라서, LogsContentProvider.kt 파일에 인터페이스를 포함합니다.

     
    class LogsContentProvider: ContentProvider() {
    
        @InstallIn(ApplicationComponent::class)
        @EntryPoint
        interface LogsContentProviderEntryPoint {
            fun logDao(): LogDao
        }
    
        ...
    }

    Application 컨테이너의 인스턴스에서 종속 항목을 가져와야 하므로 인터페이스를 @EntryPoint로 주석 처리하고 ApplicationComponent에 설치합니다. 이 인터페이스 내에서 액세스하려는 결합 관련 메서드(여기서는 LogDao)를 노출합니다.

    진입점에 액세스하려면 EntryPointAccessors의 적절한 정적 메서드를 사용하세요. 매개변수는 구성요소 인스턴스이거나 구성요소 소유자 역할을 하는 @AndroidEntryPoint 객체여야 합니다. 매개변수로 전달하는 구성요소와 EntryPointAccessors 정적 메서드가 모두 @EntryPoint 인터페이스의 @InstallIn 주석에 있는 Android 클래스와 일치하는지 확인합니다.

    이제 위의 코드에서 누락된 getLogDao 메서드를 구현할 수 있습니다. 위의 LogsContentProviderEntryPoint 클래스에서 정의한 진입점 인터페이스를 사용해 보겠습니다.

     
    class LogsContentProvider: ContentProvider() {
        ...
    
        private fun getLogDao(appContext: Context): LogDao {
            val hiltEntryPoint = EntryPointAccessors.fromApplication(
                appContext,
                LogsContentProviderEntryPoint::class.java
            )
            return hiltEntryPoint.logDao()
        }
    }

    오늘은 코드랩에 있는 내용을 그대로 가지고 왔는데요. 제가 멍청한건지 이해하려면 몇 번 더 읽어봐야하지 않나 싶네요 :)

    댓글

Designed by Tistory.