[Android] Apollo-Client 파일 업로드하기

H43RO·2021년 9월 27일
2

Android 와 친해지기

목록 보기
15/26
post-thumbnail

File Upload

서버와의 통신 동작을 구현하다보면 간단한 쿼리, 뮤테이션으로 정보를 주고받는데에서 더욱 나아가 파일을 업로드해야 하는 경우도 발생하곤 한다. 이미지를 업로드하거나, 녹음 파일을 업로드하는 등 폭넓은 기능을 생각해볼 수 있기 때문에 반드시 클라이언트 개발자는 서버에 파일을 업로드하는 방법을 알아야 한다.

기존의 Retrofit2 에서의 REST API 통신 방식에서는, MultipartBody 를 활용하여 다양한 형식의 파일을 서버에 업로드하곤 했었다. 그러나 Apollo-Client 에서는 방법이 조금 다르다. 따라서 이번 포스팅에선, GraphQL 통신에 있어 Apollo-Client 를 사용할 경우 파일을 어떻게 업로드하는지에 대해 알아보고자 한다.

Input Object

GraphQL 에서는 클라이언트에서 서버로 어떠한 데이터를 보낼 때, 각 쿼리 및 뮤테이션에 맞는 Input 객체를 만들어서 넘기도록 되어있다. 즉, Input 객체에 전송할 데이터를 채워넣어서 보내는 것이다.

Apollo-Client 에서는 이를 간편히 수행할 수 있도록, 서버에서 정의한 스키마에 따라 모든 쿼리와 뮤테이션에 맞는 Input 클래스를 data class 형태로 자동 생성해준다. 따라서 안드로이드 개발자들이 해야할 일은, 자동으로 생성된 클래스를 활용해서 서버로 전송할 데이터를 채워넣는 것이다.

그러니까!

파일을 업로드하는 것도 마찬가지로, 이러한 Input 객체에 파일을 담아서 전송하는 형식으로 진행하면 된다. 아래와 같은 형식으로 진행하면 된다.

하나씩 따라해보기

1. Mutation 작성

우선, 파일 업로드를 필요로 하는 뮤테이션을 .graphql 파일에 작성해준다. 이 예제에서는 이미지 파일을 업로드하는 기능을 예로 들겠다.

# 이미지 파일 업로드 뮤테이션
mutation UploadImageFileMutation($uploadImageFileInput: UploadImageInput!) {
    uploadImageFile(input: $uploadImageFileInput)
}

2. Gradle 설정하기

뮤테이션을 작성하고 프로젝트 리빌드를 했다면, 자동으로 해당 뮤테이션에 해당하는 Input 클래스가 생성됐을 것이다. 이 포스팅의 예제에 따르면 UploadImageInput 이라는 클래스가 자동으로 생성된다.

build.gradle 에서 이 Input 클래스의 이름을 기입해주고, 해당 Input 이 파일 업로드 동작을 수행함을 명시해줘야 한다.

아래와 같이 com.apollographql.apollo.api.FileUpload 을 매핑해준다.

apollo {
    generateKotlinModels = true
    customTypeMapping = [
            "UploadImageInput" : "com.apollographql.apollo.api.FileUpload"
    ]
}

3. Mutation 호출 시 파일 넣기

자, 이제 호출해주는 일만 남았다. 필자는 아래와 같이 '뮤테이션을 호출하는 함수'를 만들어보았다.

fun uploadImageFile(filePath: String): Single<Response<UploadImageFileMutation.Data>> =
    apolloClient.rxMutate(
        UploadImageFileMutation(
            UploadImageInput(file = FileUpload("image/*", filePath))
        )
    )

예제를 살펴보면, UploadImageInput 객체의 생성자로 FileUpload 라는 객체를 사용함을 알 수 있다. 해당 클래스는 필자가 만든 것이 아니고, Apollo-Client 의 기본 기능이다. 아래는 FileUpload 클래스의 내부 코드이다.

package com.apollographql.apollo.api

import okio.BufferedSink

/**
 * A class that represents a file upload in a multipart upload
 * See https://github.com/jaydenseric/graphql-multipart-request-spec
 *
 * This class is heavily inspired by [okhttp3.RequestBody]
 */
open class FileUpload(val mimetype: String, val filePath: String? = null) {
  /**
   * Returns the number of bytes that will be written to `sink` in a call to [.writeTo],
   * or -1 if that count is unknown.
   */
  open fun contentLength(): Long {
    return -1
  }

  /**
   *  Writes the content of this request to `sink`.
   */
  open fun writeTo(sink: BufferedSink) {
    throw UnsupportedOperationException("ApolloGraphQL: if you're not passing a `filePath` parameter, you must override `FileUpload.writeTo`")
  }

  /**
   * The fileName to send to the server. Might be null
   */
  open fun fileName(): String? {
    throw UnsupportedOperationException("ApolloGraphQL: if you're not passing a `filePath` parameter, you must override `FileUpload.fileName`")
  }

  companion object {

  }
}

이 내부 코드에서 알 수 있는 것은, 이 FileUpload 동작은 OkHttp3RequestBody 클래스에서 영감을 받았다는 사실이다. (RequestBody 클래스는 그 유명한 MultipartBody 의 슈퍼 클래스이다)

FileUpload 객체를 생성할 때는 업로드하려는 파일의 MIME 타입과, 파일의 경로 (URI 형태) 를 필요로 한다. 이렇게 객체를 생성하여 Input 객체의 생성자로 넣게 되면 성공적으로 서버에 파일을 업로드할 수 있다.


이번 포스팅에선 Apollo-Client 를 활용하여 GraphQL 서버에 파일을 업로드하는 방법에 대해 알아보았다. 앞으로도 차근차근 Apollo 관련 안드로이드 꿀팁을 작성해볼 예정이다.

profile
어려울수록 기본에 미치고 열광하라

0개의 댓글