📱 Android

[Dagger2] #2 with.Retrofit

콩드로이드 2022. 4. 7. 21:29

안녕하세요, 🐥 오늘은 DI 1탄에 이어서 2탄으로 돌아왔습니다
Rest API(Retrofit)와 Dagger2를 함께 사용하는 예제로 진행하겠습니다

 

이번 포스팅에서 진행할 3가지 기능입니다 
📌 mocky API

📌 Retrofit을 이용해 REST API 연동

📌 Dagger2로 Retrofit 사용 


Mocky 사용법

1. https://designer.mocky.io/에서 NEW MOCK을 클릭하여 이동

 

 

2. 출력하고자 하는 값을 JSON 형태로 Body에 넣어줍니다 

 

3. GENERATE MY HTTP RESPONSE 클릭 

 

 - 이 경우에 BASE_URL은  https://run.mocky.io/v3/ 입니다

 

 


💬 본격적으로 DI를 시작하기 전, Retrofit과 dagger2 사용을 위해서 library를 추가합니다

    //retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation "com.squareup.retrofit2:converter-gson:2.9.0"

    //dagger
    def dagger_version = '2.40.5'
    implementation "com.google.dagger:dagger:$dagger_version"
    kapt "com.google.dagger:dagger-compiler:$dagger_version"

    //rxjava
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
    implementation 'io.reactivex.rxjava2:rxjava:2.2.2'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'

 


REST API 연동

 

1. Response에 맞는 data class 생성

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
data class TestData(
    var date: String? = "",
    var blog: String? = "",
    var title: String? = "",
    var summary: String? = ""
) : Parcelable

 

2. Uri를 명시, Response를 반환하는 interface 생성 (mocky에선 io/뒤에 붙는 주소가 uri)

interface mainAPI {
    @GET("v3/345bfd6f-97e3-4e41-8113-b9584f571efc")
    fun getTestData(): Call<TestData>
}

 

우선, REST API는 위의 두가지만 선언해두고, 아래의 Module에 생성하는 함수를 사용하겠습니다


Module

객체 반환

Module객체를 반환하는 함수를 통해 객체를 공급해주는 클래스입니다.

클래스에만 @Module을 붙일 수 있고, Module class내에서는 객체를 반환하는 함수가 필요합니다.

객체를 반환하는 함수엔 @Provides 혹은 @Binds annotation이 붙습니다.

반환하는 함수명에는 주로 prefix로 provide를 붙입니다.

위의 annotation은 @Module이 선언된 곳에만 사용가능합니다.

 

Retrofit의  Builder는  한번 생성 후 동일한 객체를 이용하므로, Singleton을 annotation으로 선언합니다

import com.di.retrofitdagger.retro.mainAPI
import dagger.Module
import dagger.Provides
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
import javax.inject.Singleton

@Module
class NetworkModule {
    @Provides
    @Singleton
    fun provideAPI(): mainAPI {
        val okHttpClient = OkHttpClient.Builder()
        okHttpClient.connectTimeout(3, TimeUnit.MINUTES)

        return Retrofit.Builder()
            .baseUrl("https://run.mocky.io/")
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .addConverterFactory(GsonConverterFactory.create())
            .client(okHttpClient.build())
            .build()
            .create(mainAPI::class.java)
    }
}

 


Component

객체 생성, 제공

Component는 interface, abstract class에서만 사용 가능

연결된 Module을 이용해 객체를 생성하고, inject로 요청받은 instance에 객체를 제공합니다

즉, DI의 주된 역할을 수행한다고 볼 수 있습니다

그리고 중요한 것은, Component는 modules을 활용해 객체를 생성하기 때문에
@Component를 붙일 때 반드시 modules를 함께 선언해줘야 합니다

 

Component class를 생성한 후 앱을 빌드하게 되면, Dagger + Component 명칭의 네이밍을 가진 클래스가
자동으로 생성됩니다. (ex. DaggerNetworkComponent)

import com.di.retrofitdagger.MainActivity
import dagger.Component
import javax.inject.Singleton

@Component(modules = [NetworkModule::class])
@Singleton
interface NetworkComponent {
    fun inject(activity: MainActivity)
}

 

Component 클래스 안에서 Method는 크게 2가지 유형으로 나눠집니다

 

1. Provision

매개 변수가 없고 Module이 제공하는 객체타입을 반환형으로 가지는 유형

fun makeTest(): Test

 

2. Member-injection Method

인자로 받은 Component 내부에 @inject가 붙은 필드에 DI

fun inject(activity: MainActivity)

 

해당 예제에선 MainActivity 내부에 @inject가 붙은 필드에 직접 주입하는 2번 방식을 사용합니다 

 


MainActivity

fun inject(activity: MainActivity)

 

Component의 해당 함수를 통해 MainActivity 속 @Inject가 선언된 필드에 DI를 확인합니다

Mocky에서 가져온 response를 아래의 화면과 같이 나타내려고 합니다

🔍 

package com.di.retrofitdagger

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import com.di.retrofitdagger.di.DaggerNetworkComponent
import com.di.retrofitdagger.di.NetworkComponent
import com.di.retrofitdagger.retro.mainAPI
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject

class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var api: mainAPI // 1) @Inject를 가진 variable 선언 

    lateinit var component: NetworkComponent

    private val tvTitle: TextView by lazy {
        findViewById(R.id.tvTitle)
    }

    private val tvBlog: TextView by lazy {
        findViewById(R.id.tvBlog)
    }

    private val tvDate: TextView by lazy {
        findViewById(R.id.tvDate)
    }

    private val tvSum: TextView by lazy {
        findViewById(R.id.tvSum)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //di inject network
        component = DaggerNetworkComponent.builder().build()
        component.inject(this) 
        // 2)생성된 Component의 명칭으로 builder 연결 -> inject
        // NetworkComponent에 선언된 inject 함수 사용 

        testStart()
    }

    private fun testStart() {
        api.run { 
        // 3) inject한 api를 통해 View에 setdata
            getTestData().subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({
                    tvTitle.text = it.title ?: "Empty Value"
                    tvBlog.text = it.blog ?: "Empty Value"
                    tvDate.text = it.date ?: "Empty Value"
                    tvSum.text = it.summary ?: "Empty Value"
                }, { e ->
                })
        }
    }
}

 


dagger2를 통해 의존성을 주입하고, retrofit으로 간단한 데이터를 가져오는 예제를 만들어보았습니다

 🙇🏻‍♀️

도움이 되셨다면 공감하기 부탁드려요 🙋🏻‍♀️