이전에 내가 코루틴에 대하여 포스팅한 글이 있다. [안드로이드] 코루틴에 대하여
이 글에서 내가 suspend를 기깔나게 쓰려고 DI와 Retrofit 그리고 Coroutines를 콜라보레이션 하여 예제로 보여주겠다고 했는데, 회사에서 집으로 튀어와서 호다다닥 완성해 보았다. 자 그럼 차례대로 설명 들어가겠다.
app / project의 build.gradle에서 implementation 해주기
당연하게도 Retrofit, Hilt, Coroutine을 사용하려면, '저 이거 쓸거니까 불러와주세요~' 라고 선언해 줘야 한다. 그 작업을 먼저 해준다.
//Project build.gradle
buildscript {
ext.timber_version = '4.7.1'
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.30"
classpath("com.google.dagger:hilt-android-gradle-plugin:2.38.1")
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
android {
compileSdk 31
...
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
dataBinding true
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// Architectural Components
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
// Coroutine Lifecycle Scopes
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"
// Activity KTX for viewModels()
implementation "androidx.activity:activity-ktx:1.4.0"
//Dagger - Hilt
implementation "com.google.dagger:hilt-android:2.38.1"
kapt "com.google.dagger:hilt-android-compiler:2.38.1"
implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"
kapt "androidx.hilt:hilt-compiler:1.0.0"
implementation 'android.arch.lifecycle:extensions:1.1.1'
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation "com.squareup.okhttp3:logging-interceptor:4.5.0"
// Logging
implementation "com.jakewharton.timber:timber:$timber_version"
}
Permission 추가하기
안드로이드에서는 앱 내에서 Network를 사용하려면 INTERNET Permission을 추가해줘야 한다. 그 Permission을 AndroidManifest의 application 태그 위에 살포시 얹어준다.
<uses-permission android:name="android.permission.INTERNET" />
Application Class 만들어주기
Hilt를 사용하려면, Application에서 @HiltAndroidApp 어노테이션을 붙여줘야 한다. 왜냐하면 해당 어노테이션을 붙여줘야지 앱을 컴파일 할 때 필요한 클래스들을 초기화 하기 때문이다. 그리고 이렇게 Application 클래스를 만들면, AndroidManifest에 가서 Application Class를 등록해줘야한다. 그 클래스 등록은 application 태그 내에서 android:name 속성으로 주면 된다. 그 예시를 HiltApplication class 아래에 마련해 두었다. 확인해보기 바란다.
@HiltAndroidApp
class HiltApplication:Application() {
override fun onCreate() {
super.onCreate()
}
}
<application
android:name=".core.HiltApplication"
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.RetrofitTest">
ApiService 만들어주기
ApiService는 실질적으로 Call할 API를 정의 하는 부분이라고 생각하면 된다. (예제를 위해서 나는 회사에서 쓰고있는 API를 사용했다.) 인터넷에 Open API 정부에서 제공하는 API가 있는데, 서버가 없다면 그걸 써보는걸 추천한다.
interface ApiService {
@FormUrlEncoded
@POST("actCheckSms")
suspend fun onCheckSMS(
@Field("telno") num: String, @Field("randomNumber") randNumber: String
): Response<ActGetSMSCheck>
}
Repository 만들어주기
class MainRepository @Inject constructor(private val apiService: ApiService) {
suspend fun checkSMS(param1: String, param2: String) =
apiService.onCheckSMS(param1, param2)
}
ApiModule 만들어주기
여기서 부터 정신을 바짝 차리고 설명을 봐야 한다. 다들 알다시피 Hilt에는 Module 어노테이션(@Module)이 존재한다. 이 모듈 어노테이션이 존재하는 이유는 다음과 같다.
- 우리가 의존성을 주입할 때 외부 라이브러리는 Hilt가 인스턴스를 생성하지 못하는 경우가 있다.
- 해당 인스턴스는 어떻게 생성해야 하는지 개발자가 알려줘야 한다.
위와 같은 이유 때문에 우리는 @Module을 사용해서 인스턴스를 만들고 Hilt가 의존성을 주입할 때 생성자를 생성하는 방법을 모를때 어떻게 생성하는지 가이드라인을 제공하는 것이다.
이렇게 가이드 라인을 생성하고 나면 우리는 이 모듈을 어떤 컴포넌트에 제공할 것인지 정해야 한다. 이번에는 Retrofit에 관한 모듈이니까 SingletonComponent를 사용하겠다.
아래와 같은 코드에서 우리는 Hilt가 의존성을 주입할 때 생성하지 못하는 생성자에 대하여 가이드 라인을 만들어서, 어떻게 생성하라고 지시를 내려주게 되었다.
@Module
@InstallIn(SingletonComponent::class)
object ApiModule {
@Provides
fun provideBaseUrl() = Constants.BASE_URL
@Singleton
@Provides
fun provideOkHttpClient() = if (BuildConfig.DEBUG) {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
} else {
OkHttpClient.Builder().build()
}
@Singleton
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.client(okHttpClient)
.baseUrl(provideBaseUrl())
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
@Singleton
fun provideApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
@Singleton
@Provides
fun provideMainRepository(apiService:ApiService)= MainRepository(apiService)
}
MainViewModel에서 사용하기
생성자를 보면, 생성자에는 repository : MainRepository 가 존재한다. 이러한 생성자는 ApiModule.kt 에서 어떻게 인스턴스를 가져오는지 미리 선언을 해 두어서 주입을 시켰다. 이후에 MainActivity.kt에서 getData()를 호출함과 동시에 Observer를 설정하여 viewModelScope안에서 호출하는 checkSMS의 결과값을 바로 반영할 수 있게 했다.
@HiltViewModel
class MainViewModel @Inject constructor(private val repository: MainRepository) : ViewModel() {
var liveData = MutableLiveData<String>("응답 없음")
fun getData() = liveData
init {
loadData()
}
private fun loadData() {
viewModelScope.launch {
val data = repository.checkSMS("param1", "param2")
when (data.isSuccessful) {
true -> {
liveData.postValue(data.body().toString())
}
else -> {
Timber.e("TEST -> ${data.body()}")
}
}
}
}
}
혹시 잘 안되나요?
이거 잘 안될 수 있다. 솔직히 처음 접하는 사람들 한테는 어렵다. 그래서 깃헙에다가 소스코드를 올려두었다. 소스코드 보면서 내가 원하는데로 적용시켜보면서 감을 익히길 바란다. 혹시라도 개념이 잘 이해가 안된다면, 그냥 이렇게 써야 된느거구나 하고 사용하고나서 개념을 들여다 보길 바란다. 훨씬 이해가 잘 된다.
https://github.com/Uni-Stark/retrofit_example