Android Retrofit

Heejin Ryu·2021년 4월 8일
0

android

목록 보기
3/3
post-thumbnail

🌟🌱 알고 계실 것 🌱🌟

본 게시글은 저의 학습을 위한 용도라 부족한 점이 많을 수 있어요.
만약 틀린 부분이 있다면 따뜻한 조언과 피드백 부탁드립니다:) 🧚


Api를 연결할 때 retrofit2를 자주 사용한다.
제대로 알고 연결한 적이 없어서 이번 기회를 통해서 정리를 확실히 해두려 한다.

참고

  • Rx, Repository pattern, hilt 사용

0-1 라이브러리 추가

build.gradle에 retrofit 라이브러리를 추가한다.
retorofit github에 가서 최신 버전을 확인!!

implementation 'com.squareup.retrofit2:retrofit:(insert latest version)'

0-2 인터넷 권한 설정

Manifiest에 인터넷 권한설정을 해준다.

<uses-permission android:name="android.permission.INTERNET" />

0-3 Builder 만들기.

object MobalRetrofit {

    const val API_END_POINT = "http://"

    fun <T> create(
        service: Class<T>,
        client: OkHttpClient,
        httpUrl: String = API_END_POINT
    ): T = Retrofit.Builder()
        .baseUrl(httpUrl)
        .client(client)
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
        .build()
        .create(service)

    inline fun <reified T : Any> create(
        client: OkHttpClient,
        httpUrl: String = API_END_POINT
    ): T {
        require(httpUrl.isNotBlank()) { "Parameter httpUrl cannot be blank." }
        return create(service = T::class.java, httpUrl = httpUrl, client = client)
    }
}

함께 개발하는 팀원이 위 과정을 만들어놔서 나는 아래만 진행하면 됐다.

1. DTO를 만든다.

내가 지금 진행하고 있는 프로젝트에서 사용하는 DTO를 예시로 들자면, POST Dto를 만든다.
이 프로젝트에서는 폴더 구조를 (프로젝트 패키지) -> data -> dto -> PostDto.kt 이렇게 가져갔다.

여기서 간단하게 Dto 란?
Data Transfer Object 로서 계층간 데이터 교환을 위한 객체이다. DB에서 데이터를 얻어 Service나 Controller 등으터 보낼 때 사용하는 객체를 말한다.
serialized를 사용하는 것을

data class PostDto(
    @SerializedName("post_id") val postId: Int,
    @SerializedName("user_id") val userId: Int,
    @SerializedName("post_image") val postImage: String? = null,
    @SerializedName("title") val title: String,
    @SerializedName("post_description") val description: String? = null,
    @SerializedName("goal") val goalPrice: Int,
    @SerializedName("created_at") val createdAt: Long? = null,
    @SerializedName("update_at") val updatedAt: Long? = null,
    @SerializedName("started_at") val startedAt: Long,
    @SerializedName("end_at") val endAt: Long
)

2. API interface를 만든다.

본 프로젝트에서는 api interface를 Service라고 네이밍 하였다.
Post를 만들어서 보내야하므로, POST방식으로 보내게 되고, 내부 body에는 RequestBody형식을 넣어주어서 함수를 사용할 때 body를 정의해준다. 폴더 구조는 (프로젝트 패키지) > network > service > CreateDonationService.kt 였다.

interface CreateDonationService {

    @POST("/posts")
    fun createDonation(@Body body: RequestBody): Single<Response<PostDto>>
}

이 프로젝트의 경우 Rx를 사용하고 있어서, response또한 Single형식으로 받도록 지정했다. Response는 아래 코드와 같이 정의되어 있다.

data class Response<T>(
    val code: Int? = null,
    val data: T? = null,
    val message: String? = null
)

@CheckReturnValue
@SchedulerSupport(SchedulerSupport.NONE)
fun <T : Any> Single<Response<T>>.onErrorResponse(tag: String? = null): Single<Response<T>> =
    onErrorResumeNext { t -> t.toErrorResponse<T>(tag)?.let { Single.just(it) } ?: Single.error(t) }

3. Repository를 정의한다.

위에서 정의한 service를 인자로 받고, createDonation함수를 정의한다. 함수의 인자론, Dto에 정의된 데이터들을 인자로 받는다.
내부에서는 service에서 정의한 함수를 body를 정의하여 api call을 보낸다. 그러면 반환되는것은 single 형식의 response가 올 것이다.

class CreateDonationRepository @Inject constructor(private val service: CreateDonationService) {
    fun createDonation(
        title: String,
        description: String?,
        postImage: String?,
        goal: Int,
        startedAt: Long,
        endAt: Long
    ): Single<Response<PostDto>> {
         return service.createDonation(
            requestBodyOf {
                "title" to title
                description?.let { "post_description" to it }
                postImage?.let { "post_image" to it }
                "goal" to goal
                "startedAt" to startedAt
                "endAt" to endAt
            }
        )
    }
}

4.Viewmodel에서 Repository를 가져와서 사용한다.

createDonationRepository을 생성자에 주입하고, 아래서 사용합니다.

class CreateDonationViewModel @Inject constructor(
    schedulerProvider: BaseSchedulerProvider,
    private val createDonationRepository: CreateDonationRepository,
    private val fileRepository: FileRepository
) : BaseViewModel(schedulerProvider) {
...
// 함수들
fun createDonation(context: Context) {
        _createDonationInputSubject.firstOrError()
            .subscribeOnIO()
            .flatMap { createDonationInput ->
            	// null이 아니라면
                if (!createDonationInput.description.isNullOrBlank() &&
                	...
                    // input에 들어있는 값 null체크.
                ) {
                    fileRepository.uploadImage(context, createDonationInput.postImage).flatMap {
                        createDonationRepository.createDonation(
                            title = createDonationInput.productName,
                            description = createDonationInput.description,
                            postImage = it,
                            goal = createDonationInput.fundAmount,
                            startedAt = createDonationInput.startDate,
                            endAt = createDonationInput.dueDate
                        )
                    }
		// null이라면 
                } else {
                    Single.error(
                        IllegalArgumentException(
                            "create donation failed: createDonationInput: $createDonationInput"
                        )
                    )
                }
            }
            // response로 받아와서 완료 데이터 생성.
            .subscribeWithErrorLogger { response ->
                if (response.data != null) {
                    _createCompleteInputSubject.onNext(
                        // 완료 데이터 발행
                    )
                } else {
                    response.message?.let { _createDonationErrorMessageSubject.onNext(it) }
                }

            }
            .addToDisposables()
    }

사실 Hilt, Dagger, Rx, 다 생소하기도 하고, 앞 두 개는 아예 모르는 부분이라서 코드 설명을 하기가 좀 어렵다...
일반적으로 나와있는 예시 보다 더 뎁스가 들어가서 모듈화 되어있는 부분도 코드가 이해안되는 것에 한 몫하는 것 같다.
이 프로젝트가 끝나고, 꼭 코드를 복기하는 과정을 거쳐야겠다고 생각했다.

profile
Chocolate lover🍫 & Junior Android developer🤖

0개의 댓글