[Android] Hilt로 마이그레이션하기

이제일·2022년 8월 31일
0

Android

목록 보기
9/15
post-thumbnail

googlecodelabs에서 hilt에 관한 예제를 원래 코드(main branch)와 Hilt를 사용한 코드(solution branch)를 살펴보겠습니다.

해당 깃허브 레포

Hilt에 대한 내용은 이전 포스팅을 참고해주세요

프로젝트 살펴보기

UI는 왼쪽의 ButtonFragment와 오른쪽의 LogsFragement가 있습니다.

프로젝트 구조는 아래와 같습니다

마이그레이션

solution에서 제시한 memory(LoggerInMemoryDataSource) 저장 방식과 ContentProvider는 제외했습니다.

LogApplication

기존

/* hilt/LogApplication.kt */
class LogApplication : Application() {

    lateinit var serviceLocator: ServiceLocator

    override fun onCreate() {
        super.onCreate()
        serviceLocator = ServiceLocator(applicationContext)
    }
}

어플 사용 중 유지되어야 할 객체에 대해서 초기화합니다.

/* hilt/ServiceLocator.kt */
class ServiceLocator(applicationContext: Context) {

    private val logsDatabase = Room.databaseBuilder(
        applicationContext,
        AppDatabase::class.java,
        "logging.db"
    ).build()

    val loggerLocalDataSource = LoggerLocalDataSource(logsDatabase.logDao())

    fun provideDateFormatter() = DateFormatter()

    fun provideNavigator(activity: FragmentActivity): AppNavigator {
        return AppNavigatorImpl(activity)
    }
}

DateFormatter는 Long형 timestamp를 String 형으로 바꿔주는 커스텀 클래스입니다.
LoggerLocalDataSource는 아래의 인터페이스를 구체화한 클래스입니다.

interface LoggerDataSource {
    fun addLog(msg: String)
    fun getAllLogs(callback: (List<Log>) -> Unit)
    fun removeLogs()
}

AppNavigatorImpl는 아래의 인터페이스를 구체화한 클래스입니다.

interface AppNavigator {
    // Navigate to a given screen.
    fun navigateTo(screen: Screens)
}

Hilt

/* hilt/LogApplication.kt */
@HiltAndroidApp
class LogApplication : Application()

Hilt Module로 관리하기에 Hilt를 사용하기 위한 @HiltAndroidApp만 사용

/* hilt/di/LoggingDatabaseModule.kt */
@InstallIn(SingletonComponent::class)
@Module
abstract class LoggingDatabaseModule {

    @Singleton
    @Binds
    abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LoggerDataSource
}

val loggerLocalDataSource = LoggerLocalDataSource(logsDatabase.logDao())
@Binds 방식으로 interface의 구현체를 종속시킨다.
"logsDatabase.logDao()"인 파라미터를 넘겨주는 부분은 이후에 다룸

/* hilt/util/DateFormatter.kt */
class DateFormatter @Inject constructor() {
	...
    fun formatDate(timestamp: Long): String {
        ...
    }
}

fun provideDateFormatter() = DateFormatter()
는 별도의 파라미터가 없고, 함수 호출 시 생성되기에 Scope를 지정하지 않고 정의

/* hilt/di/NavigationModule.kt */
@InstallIn(ActivityComponent::class)
@Module
abstract class NavigationModule {

    @Binds
    abstract fun bindNavigator(impl: AppNavigatorImpl): AppNavigator
}

fun provideNavigator(activity: FragmentActivity): AppNavigator { return AppNavigatorImpl(activity) }
또한 @Binds 방식으로 interface의 구현체를 종속시킨다.
"activity"인 파라미터는 이후에 다룸

Activity, Fragment

기존
applicationContext를 접근해서 데이터를 가져옵니다.

/* hilt/ui/MainActivity.kt */
class MainActivity : AppCompatActivity() {
    private lateinit var navigator: AppNavigator
    
    override fun onCreate(savedInstanceState: Bundle?) {
    ...
            navigator = (applicationContext as LogApplication).serviceLocator.provideNavigator(this)
	}
    ...
    
/* hilt/ui/ButtonsFragment.kt */
class ButtonsFragment : Fragment() {
    private lateinit var logger: LoggerLocalDataSource
    private lateinit var navigator: AppNavigator
    
    override fun onAttach(context: Context) {
        super.onAttach(context)
        populateFields(context)
    }
    private fun populateFields(context: Context) {
        logger = (context.applicationContext as LogApplication).
            serviceLocator.loggerLocalDataSource

        navigator = (context.applicationContext as LogApplication).
            serviceLocator.provideNavigator(requireActivity())
    }
    ...
/* hilt/ui/LogsFragment.kt */
class LogsFragment : Fragment() {
    private lateinit var logger: LoggerLocalDataSource
    private lateinit var dateFormatter: DateFormatter
    
    override fun onAttach(context: Context) {
        super.onAttach(context)

        populateFields(context)
    }

    private fun populateFields(context: Context) {
        logger = (context.applicationContext as LogApplication).serviceLocator.loggerLocalDataSource
        dateFormatter =
            (context.applicationContext as LogApplication).serviceLocator.provideDateFormatter()
    }
    ...

Hilt
Hilt에게서 주입 받기에 @inject를 지정 후 바로 사용할 수 있습니다.

/* hilt/ui/MainActivity.kt */
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject lateinit var navigator: AppNavigator
    ...
/* hilt/ui/LogsFragment.kt */
@AndroidEntryPoint
class LogsFragment : Fragment() {
    @Inject lateinit var logger: LoggerDataSource
    @Inject lateinit var dateFormatter: DateFormatter
    ...
/* hilt/ui/ButtonsFragment.kt */
@AndroidEntryPoint
class ButtonsFragment : Fragment() {
    @Inject lateinit var logger: LoggerDataSource
    @Inject lateinit var navigator: AppNavigator
    ...

AppNavigator

기존

/* hilt/navigator/AppNavigatorImpl.kt */
class AppNavigatorImpl(private val activity: FragmentActivity) : AppNavigator {
	...
}

Hilt

/* hilt/navigator/AppNavigatorImpl.kt */
class AppNavigatorImpl @Inject constructor(private val activity: FragmentActivity) : AppNavigator {
	...
}

activityContext의 경우는 @ActivityContext 어노테이션 없이 Hilt가 종속항목을 제공해줍니다.

Database

기존

/* hilt/data/LoggerLocalDataSource.kt */
class LoggerLocalDataSource(private val logDao: LogDao) {
	...
}

Hilt

/* hilt/data/LoggerLocalDataSource.kt */
class LoggerLocalDataSource @Inject constructor(private val logDao: LogDao) : LoggerDataSource {
	...
}

기존의 ServiceLocator에서 아래와 같이 초기화하여 제공했습니다.

  private val logsDatabase = Room.databaseBuilder(
        applicationContext,
        AppDatabase::class.java,
        "logging.db"
    ).build()

    val loggerLocalDataSource = LoggerLocalDataSource(logsDatabase.logDao())

이처럼 Database 객체는 Application의 필드로, dao를 생성 후 제공하기 위해서
아래와 같이 Database Module을 생성합니다.

/* hilt/di/DatabaseModule.kt */
@InstallIn(SingletonComponent::class)
@Module
object DatabaseModule {

    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext appContext: Context): AppDatabase {
        return Room.databaseBuilder(
            appContext,
            AppDatabase::class.java,
            "logging.db"
        ).build()
    }

    @Provides
    fun provideLogDao(database: AppDatabase): LogDao {
        return database.logDao()
    }
}

class LoggerLocalDataSource @Inject constructor(private val logDao: LogDao)
의 생성자를 Hilt로 주기 위해 @Provides를 이용해 외부라이브러리(Room)을 생성 후 제공합니다.

Database 객체를 Singleton component로 생성 하고 이를 아래 provideLogDao의 생성자로 제공합니다.
이후 provideLogDao의 반환이 LoggerLocalDataSource의 생성자로 들어가게 됩니다.


해당 예제
https://github.com/googlecodelabs/android-hilt

제시한 solution 및 다양한 활용은 다음과 같습니다.
https://github.com/googlecodelabs/android-hilt/tree/solution

profile
세상 제일 이제일

0개의 댓글