본문 바로가기

안드로이드

retrofit2 ,okhttp 적용했던거 koin 적용해서 리팩토링 해보자

koin 사실 여기저기 다써야겠지만 일단 시작점으로 retrofit2 작성했던거에 리팩토링으로 koin을 적용해서 시작해보도록 하겠다

 

참고한 자료는 

https://medium.com/@sunminlee89/koin%EC%9C%BC%EB%A1%9C-api%ED%98%B8%EC%B6%9C-%EB%AA%A8%EB%93%88-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85%ED%95%98%EA%B8%B0-cec9c1ace45f

 

Koin으로 API호출 모듈 의존성 주입하기

Koin은 Dagger와 같은 의존성 주입 라이브러리이다(엄밀히 말하면 Koin은 의존성 주입 라이브러리라고 하기는 어렵다고 한다)

medium.com

와 문다빈의 뇌이다.

 

근데 위 블로그 이상하게 작성해놓아서 이렇게 다시 글을 정리해본다.

 

 

koin의 장점이나 사용의 의미는 전에 정리해놓은 글을 참고하면되고

 

실제로 이제 적용해보자.


 

koin은 객체 우리가 직접 작성해서 만들어야했던거 modules 로 정리해놓으면 자동화해주는 시스템으로 생각하고 일단 사용하면될거같다.

 

 

자 우선 리펙토링 전의 코드를 살펴보겠다

package changhwan.experiment.sopthomework

import okhttp3.Interceptor
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object ServiceCreator {

    private val headerInterceptor = Interceptor{
        val request = it.request()
            .newBuilder()
            .addHeader("Content-Type","application/json")
            .build()
        return@Interceptor it.proceed(request)
    }

    val client: OkHttpClient = OkHttpClient.Builder()
        .addInterceptor(headerInterceptor)
        .build()

    private const val  SOPT_BASE_URL= "https://asia-northeast3-we-sopt-29.cloudfunctions.net/api/"

    private val SoptRetrofit :Retrofit = Retrofit.Builder()
        .baseUrl(SOPT_BASE_URL)
        .client(client)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

    val signUpService :SignUpService = SoptRetrofit.create(SignUpService::class.java)
    val signInService :SignInService = SoptRetrofit.create(SignInService::class.java)


    private const val  GITHUB_BASE_URL="https://api.github.com/"

    private  val GitHubRetrofit : Retrofit = Retrofit.Builder()
        .baseUrl(GITHUB_BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()


    val gitHubService :GitHubService = GitHubRetrofit.create(GitHubService::class.java)
}

이것을 실제 객체를 만들어 사용할것들을 준비하는 과정이다. 

 

이부분을 koin으로 대체할것이다.


 

1.dependencies 추가

 

//koin
implementation "org.koin:koin-android:2.1.1"
//koin에서 viewmodel 사용할꺼면 적용
implementation 'org.koin:koin-androidx-viewmodel:2.1.1'

우선 koin사용을 위해서 dependencies를 추가해준다. -> 버전은 당연히 매번 찾아서 ㅎㅎ

 

2. Manifests name 설정 및 Application상속 class생성

manifests 에 name 설정해준다  -> 이거 이름앞에 . 찍어줘야하고 그 이름과 Application 클래스를 상속 받는 클래스의 이름이 같아야한다.

<application
        android:name=".MainApplication"
        android:usesCleartextTraffic="true"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Design.Light.NoActionBar">
        <activity
            android:name=".DetailActivity"
            android:exported="true" />
        <activity
            android:name=".HomeActivity"
            android:exported="true" />
        <activity
            android:name=".SignUpActivity"
            android:exported="true" />
        <activity
            android:name=".SignInActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

 

class MainApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@MainApplication)
            modules(soptNetworkModule,viewModelModule)
        }
    }
}

 

 

3. 사용하고싶은것들 모듈화 시켜서 modules에 추가하기

앞전에 글들에서 로그찍고 여타 기능들이있지만 사실 필수적인 기능들은

androidcontext와 modules이다

startKoin은 당연히 Koin을 활성화시켜주는것이고

 

다음 androidContext는 this 넣어서 application 을 설정해주면된다

그리고 modules는 기능 단위이니 원하는것들을 설정해서 modules에 넣어주는것이다.

 

이번에는 retrofit 관련된것들을 주입해줄것이기에 그부분과

viewmodel에서는 왜인지 모르겠지만 직빵으로 inject를 쓸수없기에 

외부에서 인자로 retrofit에서 구현한 SigninService와 Signupservice 를 넣어주기위해 viewmodel도 module로 만들어주었다.

 

package changhwan.experiment.sopthomework

import android.app.Application
import okhttp3.OkHttpClient
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.context.startKoin
import org.koin.dsl.module
import retrofit2.Converter
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.create

class MainApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@MainApplication)
            modules(soptNetworkModule,viewModelModule)
        }
    }
}

val soptNetworkModule = module {
    single {
        OkHttpClient.Builder()
            .addInterceptor(HeaderInterceptor())
            .build()
    }
    single {
        GsonConverterFactory.create() as Converter.Factory
    }

    single<Retrofit> {
        Retrofit.Builder()
            .client(get())
            .addConverterFactory(get())
            .baseUrl("https://asia-northeast3-we-sopt-29.cloudfunctions.net/api/")
            .build()
    }

    single<SignUpService> {
        get<Retrofit>().create(SignUpService::class.java)
    }

    single<SignInService> {
        get<Retrofit>().create(SignInService::class.java)
    }
}

val viewModelModule = module {
    viewModel {
        SignViewModel(get(),get())
    }
}

코드는 이렇게 나온다

 

이게 맨처음에 올렸던 object에 retrofit 객체를 구현하던것을 대체하는것이다 이렇게 만들어놓으면 

koin이 알아서 객체를 생성해서 주입해준다.

 

그리고 기존에 intercepter 코드는 따로 class를 파서 작성해서 넣어준다.

package changhwan.experiment.sopthomework

import okhttp3.Interceptor
import okhttp3.Response

class HeaderInterceptor: Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
            .newBuilder()
            .addHeader("Content-Type","application/json")
            .build()
        return chain.proceed(request)
    }
}

it으로 작성햇던부분을 chain으로 바꿔준다.

 

그래서 많이 쓰이는 부분들은 single로 만들어놓고(결국 다single 였지만)

 

그리고 사용처였던 

(밑에 사진에 signUpService 를 koin에서 주입시켜줘야했다)

SignViewModel에서 SignUpService와 SignInService 를 ServiceCreator이 아닌 koin으로 inject해서 넣어주려고 

뷰모델내에서 val signInService : SignInService by inject()로 koin으로 주입하려한 결과 clazz 어쩌구저쩌구 하는 오류가 떳다 근데 viewModel에서만 안되고 같은 코드를 액티비티나 프래그먼트에서는 주입해줄수있다. 

그래서 이를 대처하기위해 viewModel 에 매개변수로 signInService signUpService 를 넣어주고 koin에서 viewModel 모듈을 만들어서 get으로 매개변수 알아서 받아서 사용처에서 주입해주는 방법으로 해결했다.

class SignViewModel(private val signInService : SignInService, private val signUpService: SignUpService) : ViewModel() {


    private val _viewEmail = MutableLiveData<String>()
    private val _viewName = MutableLiveData<String>()
    private val _viewPassword = MutableLiveData<String>()
    private val _conSuccess = MutableLiveData<Boolean>()

    val viewEmail: LiveData<String>
        get() = _viewEmail
    val viewName: LiveData<String>
        get() = _viewName
    val viewPassword: LiveData<String>

이렇게  매개변수를 두개 만들어주고 

 

koin에서 viewmodel 만들어주는 module을 

package changhwan.experiment.sopthomework

import android.app.Application
import okhttp3.OkHttpClient
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.context.startKoin
import org.koin.dsl.module
import retrofit2.Converter
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.create

class MainApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@MainApplication)
            modules(soptNetworkModule,viewModelModule)
        }
    }
}

val soptNetworkModule = module {
    single {
        OkHttpClient.Builder()
            .addInterceptor(HeaderInterceptor())
            .build()
    }
    single {
        GsonConverterFactory.create() as Converter.Factory
    }

    single<Retrofit> {
        Retrofit.Builder()
            .client(get())
            .addConverterFactory(get())
            .baseUrl("https://asia-northeast3-we-sopt-29.cloudfunctions.net/api/")
            .build()
    }

    single<SignUpService> {
        get<Retrofit>().create(SignUpService::class.java)
    }

    single<SignInService> {
        get<Retrofit>().create(SignInService::class.java)
    }
}

val viewModelModule = module {
    viewModel {
        SignViewModel(get(),get())
    }
}

이렇게 위쪽 모듈들에서 어짜피 service다 만들어놨기에 get으로 받아서 넣어주는걸로 해결했다.

 

 

그래서 최종적으로 

 

이 viewmodel 을 사용하고있던 SignUpActivity와 SignInActivity에서 viewModel을 koin으로 inject하는 방법을 통해 viewmodel을 주입해서 사용함으로써 ViewModelFactory 같은걸 구현안하고 인자를 넣어줄수있었다.

 

이게 원래코드

package changhwan.experiment.sopthomework

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import changhwan.experiment.sopthomework.databinding.ActivitySignInBinding

class SignInActivity : AppCompatActivity() {

    private lateinit var binding: ActivitySignInBinding
    private lateinit var  getResult : ActivityResultLauncher<Intent>
    private val signInViewModel by viewModels<SignViewModel>()
    val signInEmail = MutableLiveData<String>()
    val signInPassword = MutableLiveData<String>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this,R.layout.activity_sign_in)

 

 

koin으로 주입하는코드

package changhwan.experiment.sopthomework

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import changhwan.experiment.sopthomework.databinding.ActivitySignInBinding
import org.koin.android.ext.android.inject
import org.koin.java.KoinJavaComponent.inject

class SignInActivity : AppCompatActivity() {

    private lateinit var binding: ActivitySignInBinding
    private lateinit var getResult: ActivityResultLauncher<Intent>
    private val signInViewModel: SignViewModel by inject()
    val signInEmail = MutableLiveData<String>()
    val signInPassword = MutableLiveData<String>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_sign_in)

 

이렇게해서 retrofit 객체를 koin으로 주입해서 의존성을 주입시켜줄수 있었다.

또한 그 과정에서 viewmodel도 koin으로 주입하여 주게 되었다.


 

참고 이미지