Upload file cùng với json sử dụng retrofit2

Retrofit là một thư viện giúp việc tương tác với server phổ biến nhất hiện nay. Việc upload file và gửi cùng với các thông tin khác lên server là một bài toán phổ biến khi làm việc với úng dụng client server. Trong hầu hết các tutorial của retrofit thì hầu hết mình nhận thấy các bài đều đề cập về việc upload một hay nhiều file lên server sử dụng multipart nhưng nó không đề cập đến việc upload file cùng với các thông tin của object đặc biệt khi các bạn đã quen sử dụng kiểu encode raw, RequestBody với type application/json. Khi chúng ta chỉ có 1 file json với nhiều json object lồng nhau thì việc sử dụng retrofit để gửi request tới server thật dễ dàng với RequestBody nhưng khi chúng ta cần gửi thêm 1 file thì bài toán sẽ phức tạp hơn .

Giả sử chúng ta có file json định dạng như sau:

{
  "product_name": "mouse",
  "price": 60000,
  "product_stores": [{
    "store_id": 1,
    "name": "Store 1"
  },
  {
    "store_id": 2,
    "name": "Store 2"
  },
  {
    "store_id": 3,
    "name": "Store 3"
  },
  {
    "store_id": 4,
    "name": "Store 4"
  }], 
  "amount":[23, 12, 34],
  "image": Image file body
}

Như trên chúng ta sẽ không thể sử dụng RequestBody như cách thông thường làm việc với json object được vì chúng ta có một trường là image với type là file. Như vậy để gửi request với data trên chúng ta cần dùng MultipartBody. Mỗi một trường trong file json ở trên sẽ tương ứng với 1 field trong form-data, việc quan trọng chúng ta cần làm đó là xác định key của mỗi trường này. Trước khi thực hiện code chúng ta nên sử dụng postman một tool rất hưu ích cho việc check xem liệu api có chạy đúng và dữ liệu gửi lên cần như thế nào

Chúng ta có thể quan sát kĩ hơn dưới đây.

product_name:mouse
price:1000
product_store[][store_id]:1
product_store[][name]:store 1
product_store[][store_id]:2
product_store[][name]:store 2
product_store[][store_id]:3
product_store[][name]:store 3
amount[]:23
amount[]:12
amount[]:34

Khi đã test qua với postman chúng ta sẽ có cái nhìn rõ ràng hơn với những việc cần phải làm

    @POST("products")
    Observable<Product> createProduct(@Body RequestBody in);

Tạo RequestBody sử dụng MultipartBody.Builder

  public RequestBody makeRequestBody() {
        MultipartBody.Builder builder = new MultipartBody.Builder();
        builder.setType(MultipartBody.FORM);
        builder.addFormDataPart("product_name", "mouse");
        builder.addFormDataPart("price", "1000");
        builder.addFormDataPart("product_store[][store_id]", "1");
        builder.addFormDataPart("product_store[][name]", "store 1");
        builder.addFormDataPart("product_store[][store_id]", "2");
        builder.addFormDataPart("product_store[][name]", "store 2");
        builder.addFormDataPart("amount[]", "23");
        builder.addFormDataPart("amount[]", "12");
        builder.addFormDataPart("amount[]", "34");
        if (file != null && file.exists()) {
            RequestBody fileBody = RequestBody.create(MediaType.parse("image"), file);
            builder.addFormDataPart("image", file.getName(),
                    fileBody);
        }
        builder.build();
    }

Việc cuối cùng rất đơn giản chỉ việc gọi method trong interface và truyền vào RequestBody được tạo từ hàm trên .