Dagger2 #6 - Retrofit 사용 예시
Dagger 관련 글
- DI (Dependency Injection) - 의존성 주입
- Dagger2 #1 - 기본 개념
- Dagger2 #2 - Scope / Binds / MultiBinding
- Dagger2 #3 - @Named @Qualifier
- Dagger2 #4 - context 주입방법 @BindsInstance @Component.Builder @Component.Factory
- Dagger2 #5 - Android Dagger 사용방법 3가지
: HasAndroidInjector, DaggerApplication, DispatchingAndroidInjector - Dagger2 #6 - Retrofit + DaggerApplication Factory 예시 (현재 글)
Dagger + Retrofit
- 이전 글, [ Android Dagger 사용방법 ] 중 하나인 DaggerApplication + DaggerActivity와 Retrofit 사용예시
- REST API -> Github API의 User 리스트 (30명)
URL : "https://api.github.com/users"
더보기[ { "login": "mojombo", "id": 1, "html_url": "https://github.com/mojombo", ... }, { "login": "defunkt", "id": 2, "html_url": "https://github.com/defunkt", ... }, { "login": "pjhyett", "id": 3, "html_url": "https://github.com/pjhyett", ... }, ... ... { "login": "bmizerany", "id": 46, "html_url": "https://github.com/bmizerany", ... } ]
: Github API에서 제공해주는 30명의 User 리스트를 얻어오는 Retrofit을 Dagger로 DI(의존성 주입) 구현 예시
User의 여러 정보를 API로 제공해주는데 그 중 Login / id / HTML_URL 3가지만 얻어오겠습니다
- 이전 5번 글 Android Dagger 사용방법의 코드에서 추가로 작성
Project 구조
- activity 패키지 : RetrofitActivity 추가 (이전 글들과 분리를 위해 Activity 추가 선언)
- module 패키지 : RetrofitModule 추가 (Retrofit DI) + ActivityBindingModule 수정 (RetrofitActivity 추가)
- model 패키지 : User 데이터 클래스 추가 (Github API 데이터를 파싱할 Data 클래스)
- network 패키지 : GithubAPI 인터페이스 추가 (Retrofit 함수 선언)
- AppComponent에 RetrofitModule 추가선언 (모듈 등록)
* 빨간색상의 클래스/인터페이스들이 추가된 구성요소입니다
1. Gradle 설정 (Retrofit 라이브러리)
Retrofit을 사용하기 위해 가장 먼저 Gradle에 Retrofit의 Dependencies 추가
implementation 'com.squareup.retrofit2:retrofit:2.6.4' // Retrofit2 라이브러리
implementation 'com.squareup.retrofit2:converter-gson:2.6.4' // GsonConverter 라이브러리
implementation 'com.squareup.okhttp3:logging-interceptor:4.4.1' // loggingInterceptor 라이브러리
Retrofit + 얻어온 데이터를 변환하기 위한 GsonConverter + Rest API 요청 로깅 확인 위한 LoggingInterceptor 추가
2. User 데이터 클래스 선언
REST API로 반환받는 데이터를 변환해서 저장할 Data class 선언
요청할 Github API의 URL주소는 "https://api.github.com/users"이며, 반환타입은 아래와 같습니다
{
"login": "mojombo", // 👈 저장할 속성 1
"id": 1, // 👈 저장할 속성 2
"node_id": "MDQ6VXNlcjE=",
"avatar_url": "https://avatars0.githubusercontent.com/u/1?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/mojombo",
"html_url": "https://github.com/mojombo", // 👈 저장할 속성 3
"followers_url": "https://api.github.com/users/mojombo/followers",
"following_url": "https://api.github.com/users/mojombo/following{/other_user}",
"gists_url": "https://api.github.com/users/mojombo/gists{/gist_id}",
"starred_url": "https://api.github.com/users/mojombo/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/mojombo/subscriptions",
"organizations_url": "https://api.github.com/users/mojombo/orgs",
"repos_url": "https://api.github.com/users/mojombo/repos",
"events_url": "https://api.github.com/users/mojombo/events{/privacy}",
"received_events_url": "https://api.github.com/users/mojombo/received_events",
"type": "User",
"site_admin": false
}
반환 데이터의 구조는 위 처럼 많은 데이터를 포함합니다. 이 중 login / id / html_url 데이터만 저장하기 위한 User 데이터 클래스의 선언은 아래와 같습니다
data class User (
@SerializedName("login")
private var login: String,
@SerializedName("id")
private var id: Int,
@SerializedName("html_url")
private var htmlUrl: String
)
User 데이터 클래스는 3개의 프로퍼티(login, id, htmlUrl)을 포함합니다
프로퍼티명을 자유롭게 사용하기 위해 API 반환데이터 속성과 @SerializedName의 이름을 일치시켜 줍니다
선언한 3개의 속성 외의 값들은 자동으로 버려지게 됩니다
3. GithubApi 인터페이스 선언 (Retrofit 메서드 구현)
Retrofit 구조 명세의 Interface를 선언합니다
interface GithubApi {
@GET("users")
fun getUsers() : Call<List<User>>
}
Dagger에서 Retrofit를 DI(의존성 주입)하여 사용하는 예시를 보기 위해, 간단히 User목록만 조회하는 getUsers() 메서드 하나만 선언했습니다.
@GET 요청으로 단순히 REST API에 데이터만 요청하고, 반환값은 Call<List<User>> 타입으로 반환받도록 구현합니다
4. RetrofitModule 선언
Retrofit 인스턴스를 관리해줄 Module을 새로 선언합니다
@Module
class RetrofitModule {
@Provides
@Singleton
fun provideGithubApi(okHttpClient: OkHttpClient, factory: Converter.Factory): GithubApi { // 1️⃣
return Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(factory)
.client(okHttpClient)
.build()
.create(GithubApi::class.java)
}
@Provides
@Singleton
fun provideOkHttpClient(interceptor: HttpLoggingInterceptor): OkHttpClient { // 2️⃣
return OkHttpClient.Builder().addInterceptor(interceptor).build()
}
@Provides
@Singleton
fun provideLoggingInterceptor(): HttpLoggingInterceptor { // 3️⃣
return HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
}
@Provides
@Singleton
fun provideConverterFactory(): Converter.Factory { // 4️⃣
return GsonConverterFactory.create()
}
}
1️⃣ GithubApi를 반환합니다 (Retrofit 메서드 선언한 interface 구현체)
: Retrofit 인스턴스를 생성하고, create()함수로 Interface 구현체를 반환합니다
Retrofit 인스턴스 생성에 필요한 HttpClient + GsonFactory 객체는 다른 @Provides 메서드를 통해
Component가 제공합니다
2️⃣ OkHttpClient 반환
: HttpLoggingInterceptor를 등록한 OkhttpClient 인스턴스를 반환합니다.
Component에 의해 Retrofit 의존성메서드에 제공됩니다
3️⃣ HttpLoggingInterceptor 반환
: Restful 요청 과정 확인을 위한 Logging Interceptor 인스턴스를 제공합니다
4️⃣ Converter.Factory 반환
: GsonConverterFactory.create()로 GsonConverter.Factory를 생성하지만, Converter.Factory로 캐스팅하여 제공합니다
RetroftModule에서 제공하는 인스턴스는 모두 @Singleton으로 싱글톤 구성을 하도록 설정했습니다.
Component에도 @Singleton을 똑같이 선언해줘야 합니다
Retrofit 인스턴스를 생성하는 Module을 선언하였으니 AppComponent에 등록을 합니다
5. ContextModule 선언
Component에게서 Component가 갖고있는 @BindsInstance application을 제공받아 Context 인스턴스로 캐스팅해서 반환하는 Module
@Module
abstract class ContextModule {
@Binds
abstract fun provideContext(application: Application) : Context // 1️⃣
}
1️⃣ @Binds 메서드로 선언
: 매개변수(application)을 Component에게 요청해서 제공받아 Context 인스턴스로 캐스팅하여 반환
Component는 application을 @BindsInstance로 선언하여 구성요소로 갖고있다가 요청시 제공
6. Component에 RetrofitModule + Component.Factory 선언
// Application 단위 클래스에 사용될 AppComponent : 최상위 Component
@Component(modules = [
// AndroidInjectionModule::class, // HasAndroidInjector, DispatchingAndroidInjector 직접 구현할 때 사용
AndroidSupportInjectionModule::class, // DaggerApplication, DaggerActivity로 HasAndroid,Dispatching 구현을 위임할 때 사용
ActivityBindingModule::class, // SubComponent.Factory 바인딩 목적의 Module
CoffeeModule::class, // Coffee 인스턴스 바인딩을 위한 Module
RetrofitModule::class, // 1️⃣
ContextModule::class // 2️⃣
])
@Singleton // 3️⃣
interface AppComponent : AndroidInjector<MyApp> { // DaggerApplication 사용하려면 AndroidInjector<> 상속 필요
fun coffees() : Map<Class<*>, Coffee> // Coffee MultiBinding의 Provision 메서드
@Component.Factory
interface Factory {
fun create(@BindsInstance application: Application) : AppComponent // 4️⃣
}
}
1️⃣ RetrofitModule 등록
: Retrofit 인스턴스 생성을 관리하는 모듈
2️⃣ ContextModule 등록
: Context 인스턴스 생성을 관리하는 모듈
3️⃣ @Singleton Scope 설정
: RetrofitModule에서 제공하는 Retrofit 인스턴스는 싱글톤으로 구현되도록 선언했기 때문에,
연결된 Component에도 동일한 Scope를 선언해줘야 해당 객체를 제공해줄 수 있습니다
4️⃣ @BindsInstance application: Application
: Dagger가 Application 인스턴스를 가지고 있으면서 Context 인스턴스가 필요한 곳에 주입하기 위해서
@BindsInstance 어노테이션 선언 ( AppComponent가 구성요소로 갖고있으면서 Module이 필요할 경우 제공함)
Component.Factory는 AndroidInjector.Factory<MyApp>을 상속 시 예외 발생
: DaggerApplication의 applicationInjector는 AndroidInjector<out DaggerApplication> 타입으로 create의 반환 Type이 AndroidInjector.Factory로 추상회되면 Dagger가 구분할 수 없기 때문에 정확한 반환Type을 요청합니다
+ ActivityBindingModule에 RetrofitActivity Component 추가
저는 RetrofitActivity를 별도로 사용했기 때문에 ActivityBindingModule에서 추가가 필요합니다
@Module(subcomponents = [MainComponent::class, DetailComponent::class])
abstract class ActivityBindingModule {
@Binds
@IntoMap
@ClassKey(MainActivity::class) // MultiBinding, Map 컬렉션의 Key Type
abstract fun bindMainComponentFactory(factory: MainComponent.Factory): AndroidInjector.Factory<*>
@Binds
@IntoMap
@ClassKey(DetailActivity::class)
abstract fun bindDetailComponentFactory(factory: DetailComponent.Factory): AndroidInjector.Factory<*>
/** SubComponent와 SubComponent.Factory 내부 메서드 X, 부모 X 경우
* 즉, 내용이 없는 경우엔 @ContributesAndroidInjector 애노테이션으로 SubComponent 인터페이스 선언을 대체해서 사용
* : SubComponent 인터페이스 생성하지 않아도 SubComponent 구현을 가능하게 함
* modules, @Scope 설정 모두 동일하게 가능
*/
@ContributesAndroidInjector
abstract fun retoriftActivity() : RetrofitActivity // 1️⃣ 추가
}
전에 선언해놨었던 MainActivity와 DetailActivity는 별도로 SubComponent 인터페이스를 선언해서 사용했지만,
RetrofitActivity는 @ContributesAndroidInjector 어노테이션을 사용해서 간단하게 추가했습니다.
위의 MainComponent와 DetailComponent도 모두 별도의 구현메서드가 없기 때문에 모두 @ContributesAndroidInjector 어노테이션으로 변경이 가능합니다
7. DaggerApplication에서 Component.Factory 사용방법
class MyApp : DaggerApplication() {
/**
* Implementations should return an [AndroidInjector] for the concrete [ ]. Typically, that injector is a [dagger.Component].
*/
// DaggerApplication의 구현메서드 (재정의)
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.factory().create(this@MyApp) // 1️⃣
}
override fun onCreate() {
super.onCreate()
}
}
1️⃣ DaggerAppComponent.factory().create(this@MyApp)
: 기존의 DaggerAppComponent.create()에서 수정, @BindsInstance로 선언한 application 처럼 Component 초기화가
필요할 경우 커스텀 create()함수를 사용하는 방법
[ Component.Factory 또는 Builder + @BindsInstance 사용법 보러가기 ]
8. 의존성 주입 (Activity)
이제 Dagger를 통해 Context 인스턴스와 Retrofit 인스턴스를 의존성주입받아 사용하겠습니다
// DaggerActivity() : dagger-android 패키지로 DaggerComponent.Inject()를 대신하도록 설정
class MainActivity : DaggerActivity() {
val TAG = javaClass.simpleName
...
@Inject
lateinit var context: Context // 1️⃣
override fun onCreate(savedInstanceState: Bundle?) {
...
// Button 클릭리스너 설정 - RetrofitActivity 띄우기
retrofitButton.setOnClickListener {
var intent = Intent(context, RetrofitActivity::class.java) // 2️⃣
startActivity(intent)
}
}
}
1️⃣ Context 인스턴스 의존성주입 요청(@Inject) - Component는 ContextModule에게 Context를 전달받아 의존성 주입
2️⃣ 의존성주입 받은 Context 인스턴스로 Intent 설정
다음으로 RetrofitActivity로 넘어가서 Retrofit 인스턴스를 제공받아 사용합니다
class RetrofitActivity : DaggerActivity() { // 1️⃣
val TAG = javaClass.simpleName
@Inject
lateinit var githubApi: GithubApi // 2️⃣
override fun onCreate(savedInstanceState: Bundle?) {
...
githubApi.getUsers().enqueue(object : Callback<List<User>> { // 3️⃣
override fun onFailure(call: Call<List<User>>, t: Throwable) {
Log.d(TAG, "Retrofit2 onFailure - ${t}")
}
override fun onResponse(call: Call<List<User>>, response: Response<List<User>>) {
if(!response.isSuccessful) return
var userList :List<User> = response.body()!!
Log.d(TAG, "userList Count is ${userList.size}")
for ((index, user) in userList.withIndex())
Log.d(TAG, "user count $index => ${user}")
}
})
}
}
1️⃣ DaggerActivity() : MainAcitivity와 마찬가지로 DaggerActivity를 상속해서 Dagger Injection을 위임
2️⃣ @inject GithubApi : Retrofit 인스턴스로 생성한 GithubApi 구현체를 의존성 주입 요청
3️⃣ 주입받은 GithubApi로 REST API 요청을 합니다
: onFailure(실패) 와 onResponse(성공) 콜백메서드 구현이 필수적으로 필요합니다
REST API 요청 결과