[Android / Kotlin] Hilt 의존성 주입하기

Subeen·2024년 2월 21일
0

Android

목록 보기
65/73

[Android / Kotlin] Hilt 의존성 작성하기에 이어 Hilt 의존성을 주입 하는 부분도 정리해보고자 한다.
Hilt를 접하기 전 낯설기도 하고 어려워 보여 겁을 먹었는데 예상대로 복잡하고 어렵기는 하지만..🤯 내용을 정리하며 조금은 이해할 수 있게 된 것 같다. 백프로 이해할 수 있게 틈틈이 정리한 내용을 보며 의존성을 주입하고 어노테이션을 추가하는 부분을 익숙해지게 만들어야할 것 같다 🥹

의존성 주입

앱 모듈의 의존성을 ImageSearchRepository에 주입하고 ImageSearchRepository 인스턴스는 ViewModel에 주입한다. 그리고, 각 Fragment에 ViewModel 인스턴스를 주입하면 된다.

Repository에 의존성 주입

ImageSearchRepositoryImpl에 @Singleton을 붙여서 의존성 주입 가능한 스코프로 지정해주고 @Inject constructor를 추가해주면 이 안의 객체들은 Hilt가 주입하게 된다.
➡️ 다시 정리해보면 ! 클래스 생성자에서 @Inject를 사용하여 의존성을 주입 받고 있다. 주입되는 의존성으로는 KaKaoSearchApi와 SharedPreferences가 있다.

@Singleton
class ImageSearchRepositoryImpl @Inject constructor(
    private val api: KaKaoSearchApi,
    private val sharedPreferences: SharedPreferences
) : ImageSearchRepository {

	// PagingSource도 Repository로부터 Retrofit 객체를 전달 받도록 수정한다. 
    override fun searchImagePaging(query: String, sort: String): Flow<PagingData<SearchModel>> {
        val pagingSourceFactory = { ImageSearchPagingSource(api, query, sort) }
        return Pager(
            config = PagingConfig(
                pageSize = MAX_SIZE_IMAGE,
                enablePlaceholders = false,
                maxSize = MAX_SIZE_IMAGE * 3
            ),
            pagingSourceFactory = pagingSourceFactory
        ).flow
    }

    /*
     * 이미지 검색 화면에서 이미지를 클릭하면 보관함에 저장하기 위한 함수
     * id를 비교해서 존재하지 않을 경우에만 아이템을 추가한다.
     */
    override suspend fun saveStorageItem(searchModel: SearchModel) {
        val favoriteItems = getPrefsStorageItems().toMutableList()
        val findItem = favoriteItems.find { it.id == searchModel.id }

        if (findItem == null) {
            favoriteItems.add(searchModel)
            savePrefsStorageItems(favoriteItems)
        }
    }

    /*
     * 보관함에 저장된 이미지를 삭제하기 위한 함수
     * 해당 아이템이 보관함에 존재하면 아이템을 삭제한다.
     */
    override suspend fun removeStorageItem(searchModel: SearchModel) {
        val favoriteItems = getPrefsStorageItems().toMutableList()
        favoriteItems.removeAll { it.id == searchModel.id }
        savePrefsStorageItems(favoriteItems)
    }

    // 보관함에 저장되어 있는 아이템을 리스트 목록으로 가져온다.
    override suspend fun getStorageItems(): List<SearchModel> {
        return getPrefsStorageItems()
    }

    private fun getPrefsStorageItems(): List<SearchModel> {
        val jsonString = sharedPreferences.getString(Constants.STORAGE_ITEMS, "")
        return if (jsonString.isNullOrEmpty()) {
            emptyList()
        } else {
            // Gson()을 사용하여 Json 문자열을 SearchModel 객체로 변환
            Gson().fromJson(jsonString, object : TypeToken<List<SearchModel>>() {}.type)
        }
    }

    // SearchModel 객체 아이템을 Json 문자열로 변환한 후 저장
    private fun savePrefsStorageItems(items: List<SearchModel>) {
        val jsonString = Gson().toJson(items)
        sharedPreferences.edit().putString(Constants.STORAGE_ITEMS, jsonString).apply()
    }

     //검색 키워드 저장
    override suspend fun saveSearchData(searchWord: String) {
        sharedPreferences.edit {
            putString(Constants.SEARCH_WORD, searchWord)
        }
    }

    // 검색 키워드 불러오기
    override suspend fun loadSearchData(): String? =
        sharedPreferences.getString(Constants.SEARCH_WORD, "")

}

ImageSearchPagingSource

class ImageSearchPagingSource(
    private val api: KaKaoSearchApi, // 의존성 주입 
    private val query: String,
    private val sort: String
) : PagingSource<Int, SearchModel>() {
    override val keyReuseSupported: Boolean = true
    override fun getRefreshKey(state: PagingState<Int, SearchModel>): Int? {
        return state.anchorPosition?.let { anchorPosition ->
            state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
                ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
        }
    }

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, SearchModel> {
        return try {
            val pageNumber = params.key ?: STARTING_PAGE_INDEX
            val response = api.searchImage(query, sort, pageNumber, params.loadSize)
            val endOfPaginationReached = response.metaData?.isEnd!!

            val data = response.documents?.map { imageDocument ->
                SearchModel(
                    thumbnailUrl = imageDocument.thumbnailUrl,
                    siteName = imageDocument.displaySiteName,
                    datetime = imageDocument.dateTime,
                    itemType = SearchListType.IMAGE

                )
            } ?: emptyList()

            val prevKey = if (pageNumber == STARTING_PAGE_INDEX) null else pageNumber - 1
            val nextKey = if (endOfPaginationReached) {
                null
            } else {
                val calculatedNextKey = pageNumber + 1
                calculatedNextKey
            }
            LoadResult.Page(
                data = data,
                prevKey = prevKey,
                nextKey = nextKey
            )

        } catch (exception: IOException) {
            LoadResult.Error(exception)
        }
    }
}

ViewModel에 의존성 주입

@HiltViewModel을 붙여서 SearchViewModel를 의존성 주입 가능한 스코프로 만들어주고 @Inject constructor를 이용해서 모듈에서 만들어준 Repository 객체를 주입한다.

🫨 다음의 코드를 정리해보면, SearchViewModel은 ImageSearchRepository에 대한 의존성을 갖고 있다. Dagger Hilt는 @HiltViewModel를 통해 의존성을 주입하게 되며 ViewModel을 생성할 때 Dagger Hilt가 알아서 필요한 의존성을 해결해준다.

@HiltViewModel // ViewModel에 대한 의존성 주입을 단순화 하는데 사용된다. 
class SearchViewModel @Inject constructor(
    private val imageSearchRepository: ImageSearchRepository
) : ViewModel() {
	...
    
}

Fragment에 ViewModel 주입

Fragment에 @AndroidEntryPoint를 붙여서 의존성 주입 가능한 스코프로 만들어주고 by viewModels로 ViewModel을 생성한다.
ViewModel이 Delegate에 의해서 만들어지기 때문에 ViewModelFactory가 필요없게 된다.

@AndroidEntryPoint // Hilt가 해당 클래스에 대해 의존성 주입을 처리하도록 지시한다. 
class SearchListFragment : Fragment() {
    companion object {
        fun newInstance() = SearchListFragment()
    }

    private var _binding: FragmentSearchBinding? = null

    private val binding get() = _binding!!

    private val viewModel: SearchViewModel by viewModels ()
    
 	...   
}

MainActivity 설정

@AndroidEntryPoint // Dagger Hilt를 사용하여 의존성을 주입 
class MainActivity : AppCompatActivity() {
    private val binding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }
 
 	...
 }
profile
개발 공부 기록 🌱

0개의 댓글