저장소 패턴

jericho·2024년 1월 30일

Android

목록 보기
9/15

레트로핏 예제 포스팅에서 클래스명 등 좀 바뀌었다.

ImageSearchRepository

//
/**
 * Repository that fetch search query data from kakaoImageApi.
 */
interface ImageSearchRepository {
    /** Fetches search query data from kakaoImageApi */
    suspend fun searchImage(
        query: String,
        sort: String? = null,
        page: Int? = null,
        size: Int? = null
    ): Response<KakaoData>
}

/**
 * Network Implementation of Repository that fetch search query data from kakaoImageApi.
 */
class NetworkImageSearchRepository(
    private val kakaoImageApiService: KakaoImageApiService
) : ImageSearchRepository {
    /** Fetches search query data from kakaoImageApi*/
    override suspend fun searchImage(
        query: String,
        sort: String?,
        page: Int?,
        size: Int?
    ): Response<KakaoData> = kakaoImageApiService.searchImage(query, sort, page, size)
}

AppContainer

//
/*
DefaultAppContainer의 인스턴스는 애플리케이션 클래스에 들어있어야 하는데
뷰모델 팩토리에서 저장소 인자 넣을 때 this[APPLICATION_KEY] as KakaoImageSearchApplication 이렇게 접근해서
애플리케이션의 container의 저장소를 넘겨주는데,
as KakaoImageSearchApplication 캐스팅 할 때 터진다.
컴포즈 관련인지 뭔지 모르겠음. 그냥 전역으로 만들어뒀다.
 */
/**
 * Dependency Injection container at the application level.
 */
interface AppContainer {
    val imageSearchRepository: ImageSearchRepository
}

/**
 * Implementation for the Dependency Injection container at the application level.
 *
 * Variables are initialized lazily and the same instance is shared across the whole app.
 */
class DefaultAppContainer : AppContainer {
    private val baseUrl = "https://dapi.kakao.com"

    private fun createOkHttpClient(): OkHttpClient {
        val interceptor = HttpLoggingInterceptor()

        if (BuildConfig.DEBUG)
            interceptor.level = HttpLoggingInterceptor.Level.BODY
        else
            interceptor.level = HttpLoggingInterceptor.Level.NONE

        return OkHttpClient.Builder().also {
            it.connectTimeout(20, TimeUnit.SECONDS)
            it.readTimeout(20, TimeUnit.SECONDS)
            it.writeTimeout(20, TimeUnit.SECONDS)
            it.addNetworkInterceptor(interceptor)
        }.build()
    }

    /**
     * Use the Retrofit builder to build a retrofit object using a GsonConverterFactory
     */
    private val retrofit: Retrofit = Retrofit.Builder().also {
        it.addConverterFactory(GsonConverterFactory.create())
        it.baseUrl(baseUrl)
        it.client(createOkHttpClient())
    }.build()

    /**
     * Retrofit service object for creating api calls
     */
    private val retrofitService: KakaoImageApiService by lazy {
        retrofit.create(KakaoImageApiService::class.java)
    }

    /**
     * DI implementation for Mars photos repository
     */
    override val imageSearchRepository: ImageSearchRepository by lazy {
        NetworkImageSearchRepository(retrofitService)
    }
}

MainActivity

// 원래는 Application 클래스에 있는 변수.
/** AppContainer instance used by the rest of classes to obtain dependencies */
val myContainer: AppContainer = DefaultAppContainer()

뷰모델

// 뷰모델에 파라미터 추가
(private val imageSearchRepository: ImageSearchRepository)

// 팩토리는 컴패니언 오브젝트로.
/**
 * Factory for [ImageSearchViewModel] that takes [ImageSearchRepository] as a dependency
 */
companion object {
    val Factory: ViewModelProvider.Factory = viewModelFactory {
        initializer {
            ImageSearchViewModel(imageSearchRepository = myContainer.imageSearchRepository)
        }
    }
}


// 프래그먼트에서 뷰모델 생성할 때 팩토리 넣는 법:
private val viewModel: ImageSearchViewModel by viewModels { ImageSearchViewModel.Factory }


// 뷰모델에서 searchImage 사용 방법:
imageSearchRepository.searchImage(query, sort, page, size)

아직도 잘 모르겠다..

참고

0개의 댓글