(Tutorial Android) Upload Image using Retrofit

Wednesday, November 16, 2016

Upload file atau image dari Android ke server bisa dilakukan dengan berbagai macam cara, salah satunya menggunakan library retrofit.

Pada postingan kali ini saya akan memberikan tutorial upload image menggunakan retrofit.

Pada kasus kali ini kita akan mencoba mengupload image dengan dua metode yakni :

1. Multipart
2. Base64

Saya akan sedikit menjelaskan metode upload image dengan multipart maupun base64 tersebut.

Multipart : Metode yang menggunakan HTTP Request untuk mengirim data ke server. Metode ini biasanya digunakan untuk mengupload file yang sizenya besar.

Base64 : Metode yang digunakan untuk melakukan proses encoding terhadap suatu binary data (contohnya: image). Proses encoding tersebut akan menghasilkan format string. Nah, format string inilah nantinya yang akan dikirim ke server dan dilakukan proses decode untuk mengembalikannya ke binary.

Oke mari kita langsung praktek saja.

Buat API 

- Pertama buat folder di htdocs dengan nama api
- Buat file php dengan nama upload.php, kemudian isikan code berikut :
<?php
 $action = htmlspecialchars($_POST['action']);

 $response = array("success" => FALSE);

 if($action == "multipart") {
     if ($_FILES["photo"]["error"] > 0) {
      $response["success"] = FALSE;
   $response["message"] = "Upload Failed";
     } else {
   $name_file=htmlspecialchars($_FILES['photo']['name']);
   
         if (@getimagesize($_FILES["photo"]["tmp_name"]) !== false) {

    move_uploaded_file($_FILES["photo"]["tmp_name"], $name_file);

    $response["success"] = TRUE;
       $response["message"] = "Upload Successfull";
    
   }else{
    $response["success"] = FALSE;
    $response["message"] = "Upload Failed";
   }

   echo json_encode($response);
     }
 }else if($action == "base64") {
  $photo = htmlspecialchars($_POST['photo']);

  $photo = str_replace('data:image/png;base64,', '', $photo);
  $photo = str_replace(' ', '+', $photo);

  $data = base64_decode($photo);
  $file = uniqid() . '.png';

  file_put_contents($file, $data);

  $response["success"] = TRUE;
  $response["message"] = "Upload Successfull";
  
  echo json_encode($response);
 }

?>
Buat Project Android

Pertama, tambahkan library retrofit dan okhttp3 ke gradle module-level
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.2.1'

    // Network
    compile 'com.squareup.retrofit2:retrofit:2.1.0'
    compile 'com.squareup.retrofit2:converter-gson:2.1.0'
    compile "com.squareup.okhttp3:okhttp:3.4.1"
    compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
}

Buat layout dengan nama activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.wimso.android_uploadimage.MainActivity">

    <Button
        android:id="@+id/btn_choose"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Choose Photo"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <ImageView
        android:id="@+id/img_thumb"
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:layout_below="@+id/btn_choose"
        android:src="@mipmap/ic_launcher"
        android:scaleType="centerCrop"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <Button
        android:id="@+id/btn_upload_1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Upload with Multipart"
        android:layout_below="@+id/img_thumb"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <Button
        android:id="@+id/btn_upload_2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Upload with Base64"
        android:layout_below="@+id/btn_upload_1"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

</RelativeLayout>

Buat package dengan nama network.

Buat kelas dengan nama Config.java di package network. Kelas ini berisi URL dan Endpoint dari API.
/**
 * Created by Wim on 11/14/16.
 */
public class Config {

    public static final String BASE_URL = "http://192.168.2.31"; // Your local IP Address
    public static final String API_DIR = "/api";

    public static final String API_UPLOAD = BASE_URL + API_DIR + "/upload.php";

}
Kemudian buat kelas dengan nama BaseResponse.java
/**
 * Created by Wim on 11/14/16.
 */
public class BaseResponse {

    private boolean success;
    private String message;

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

Kemudian buat sebuah interface dengan nama UploadInterface.java yang berisi method untuk mengupload image ke server. Method untuk mengupload image terdiri dari 2 yakni method dengan teknik multipart dan base46.
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;

/**
 * Created by Wim on 10/6/16.
 */
public interface UploadInterface {

    @Multipart
    @POST(Config.API_UPLOAD)
    Call<BaseResponse> uploadPhotoMultipart(
            @Part("action") RequestBody action,
            @Part MultipartBody.Part photo);

    @FormUrlEncoded
    @POST(Config.API_UPLOAD)
    Call<BaseResponse> uploadPhotoBase64(
            @Field("action") String action,
            @Field("photo") String photo);

}

Kemudian buat kelas UploadService.java untuk konfigurasi retrofit dan method dari interface.
/**
 * Created by Wim on 10/7/16.
 */
public class UploadService {

    private UploadInterface uploadInterface;

    public UploadService() {
        OkHttpClient.Builder okhttpBuilder = new OkHttpClient().newBuilder();
        okhttpBuilder.connectTimeout(60, TimeUnit.SECONDS);
        okhttpBuilder.writeTimeout(60, TimeUnit.SECONDS);
        okhttpBuilder.readTimeout(60, TimeUnit.SECONDS);
        okhttpBuilder.retryOnConnectionFailure(true);

        if (BuildConfig.DEBUG) {
            HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
            interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            okhttpBuilder.addInterceptor(interceptor);
        }

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(Config.BASE_URL)
                .client(okhttpBuilder.build())
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        uploadInterface = retrofit.create(UploadInterface.class);
    }

    public void uploadPhotoMultipart(RequestBody action, MultipartBody.Part photo, Callback callback) {
        uploadInterface.uploadPhotoMultipart(action, photo).enqueue(callback);
    }

    public void uploadPhotoBase64(String action, String photo, Callback callback) {
        uploadInterface.uploadPhotoBase64(action, photo).enqueue(callback);
    }

}

Selanjutnya buat beberapa kelas utility berikut :

FileUtils.java
(Kelas ini saya modifikasi dari https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java )
import android.content.ContentUris;
import android.content.Context;
import android.content.CursorLoader;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
 * Created by Wim on 9/29/16.
 */
public class FileUtils {

    /**
     * @return Whether the URI is a local one.
     */
    public static boolean isLocal(String url) {
        if (url != null && !url.startsWith("http://") && !url.startsWith("https://")) {
            return true;
        }
        return false;
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     * @author paulburke
     */
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     * @author paulburke
     */
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     * @author paulburke
     */
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is Old Google Photos.
     */
    public static boolean isGoogleOldPhotosUri(Uri uri) {
        return "com.google.android.apps.photos.content".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is New Google Photos.
     */
    public static boolean isGoogleNewPhotosUri(Uri uri) {
        return "com.google.android.apps.photos.contentprovider".equals(uri.getAuthority());
    }

    public static String getDataColumn(Context context, Uri uri, String selection,
                                       String[] selectionArgs) {

        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {
                column
        };

        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                    null);
            if (cursor != null && cursor.moveToFirst()) {
                final int column_index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(column_index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }

    public static String getPath(final Context context, final Uri uri) {
        // DocumentProvider
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            if(DocumentsContract.isDocumentUri(context, uri)) {
                // ExternalStorageProvider
                if (isExternalStorageDocument(uri)) {
                    final String docId = DocumentsContract.getDocumentId(uri);
                    final String[] split = docId.split(":");
                    final String type = split[0];

                    if ("primary".equalsIgnoreCase(type)) {
                        return Environment.getExternalStorageDirectory() + "/" + split[1];
                    }

                    // TODO handle non-primary volumes
                }
                // DownloadsProvider
                else if (isDownloadsDocument(uri)) {

                    final String id = DocumentsContract.getDocumentId(uri);
                    final Uri contentUri = ContentUris.withAppendedId(
                            Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

                    return getDataColumn(context, contentUri, null, null);
                }
                // MediaProvider
                else if (isMediaDocument(uri)) {
                    final String docId = DocumentsContract.getDocumentId(uri);
                    final String[] split = docId.split(":");
                    final String type = split[0];

                    Uri contentUri = null;
                    if ("image".equals(type)) {
                        contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                    }

                    final String selection = "_id=?";
                    final String[] selectionArgs = new String[]{
                            split[1]
                    };

                    return getDataColumn(context, contentUri, selection, selectionArgs);
                }
            }
            // MediaStore (and general)
            else if ("content".equalsIgnoreCase(uri.getScheme())) {

                // Return the remote address
                if (isGoogleOldPhotosUri(uri)) {
                    // return http path, then download file.
                    return uri.getLastPathSegment();
                }
                else if (isGoogleNewPhotosUri(uri)) {
                    if(getDataColumn(context,  uri, null, null) == null) {
                        return getDataColumn(context,  Uri.parse(getImageUrlWithAuthority(context,uri)), null, null);
                    }else{
                        return getDataColumn(context,  uri, null, null);
                    }
                }

                return getDataColumn(context, uri, null, null);
            }
            // File
            else if ("file".equalsIgnoreCase(uri.getScheme())) {
                return uri.getPath();
            }
        }else{
            String[] proj = { MediaStore.Images.Media.DATA };
            String result = null;

            CursorLoader cursorLoader = new CursorLoader(
                    context,
                    uri, proj, null, null, null);
            Cursor cursor = cursorLoader.loadInBackground();

            if(cursor != null){
                int column_index =
                        cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
                cursor.moveToFirst();
                result = cursor.getString(column_index);
            }

            return result;
        }


        return null;
    }

    public static File getFile(Context context, Uri uri) {
        if (uri != null) {
            String path = getPath(context, uri);
            if (path != null && isLocal(path)) {
                return new File(path);
            }
        }
        return null;
    }

    public static String getImageUrlWithAuthority(Context context, Uri uri) {
        InputStream is = null;
        if (uri.getAuthority() != null) {
            try {
                is = context.getContentResolver().openInputStream(uri);
                Bitmap bmp = BitmapFactory.decodeStream(is);

                return writeToTempImageAndGetPathUri(context, bmp).toString();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }finally {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    public static Uri writeToTempImageAndGetPathUri(Context inContext, Bitmap inImage) {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
        String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, "Title", null);
        return Uri.parse(path);
    }
}
ImageUtils.java
import android.graphics.Bitmap;
import android.util.Base64;

import java.io.ByteArrayOutputStream;

/**
 * Created by Wim on 11/14/16.
 */
public class ImageUtils {

    public static String bitmapToBase64String(Bitmap bmp, int quality) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bmp.compress(Bitmap.CompressFormat.JPEG, quality, baos);
        byte[] bytes = baos.toByteArray();
        return Base64.encodeToString(bytes, Base64.DEFAULT);
    }

}
Sekarang Buka MainActivity.java dan lengkapi dengan kode berikut :
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import com.wimso.android_uploadimage.network.BaseResponse;
import com.wimso.android_uploadimage.network.UploadService;

import java.io.File;
import java.io.IOException;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private static final int PICK_IMAGE = 1;
    private static final int PERMISSION_REQUEST_STORAGE = 2;

    private static final String TYPE_1 = "multipart";
    private static final String TYPE_2 = "base64";

    private ImageView imgThumb;

    private Button btnChoose;
    private Button btnUpload1;
    private Button btnUpload2;

    private UploadService uploadService;
    private Uri uri;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imgThumb = (ImageView) findViewById(R.id.img_thumb);
        btnChoose = (Button) findViewById(R.id.btn_choose);
        btnUpload1 = (Button) findViewById(R.id.btn_upload_1);
        btnUpload2 = (Button) findViewById(R.id.btn_upload_2);

        btnChoose.setOnClickListener(this);
        btnUpload1.setOnClickListener(this);
        btnUpload2.setOnClickListener(this);
    }

    private void choosePhoto() {
        if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED
                && ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {

            ActivityCompat.requestPermissions(this,
                    new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    PERMISSION_REQUEST_STORAGE);

        }else{
            openGallery();
        }
    }

    public void openGallery() {
        Intent intent = new Intent();
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        startActivityForResult(Intent.createChooser(intent, "Select Image"), PICK_IMAGE);
    }

    @Override
    public void onClick(View view) {
        if(view == btnChoose) {
            choosePhoto();
        }else if(view == btnUpload1) {
            if(uri != null) {
                File file = FileUtils.getFile(this, uri);
                uploadMultipart(file);
            }else{
                Toast.makeText(this, "You must choose the image", Toast.LENGTH_SHORT).show();
            }
        }else if(view == btnUpload2) {
            if(uri != null) {
                Bitmap bitmap = null;
                try {
                    bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
                } catch (IOException e) {
                    e.printStackTrace();
                }

                String encoded = ImageUtils.bitmapToBase64String(bitmap, 100);
                uploadBase64(encoded);
            }else{
                Toast.makeText(this, "You must choose the image", Toast.LENGTH_SHORT).show();
            }
        }
    }
    private void uploadMultipart(File file) {
        RequestBody photoBody = RequestBody.create(MediaType.parse("image/*"), file);
        MultipartBody.Part photoPart = MultipartBody.Part.createFormData("photo",
                file.getName(), photoBody);

        RequestBody action = RequestBody.create(MediaType.parse("text/plain"), TYPE_1);

        uploadService = new UploadService();
        uploadService.uploadPhotoMultipart(action, photoPart, new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                BaseResponse baseResponse = (BaseResponse) response.body();

                if(baseResponse != null) {
                    Toast.makeText(MainActivity.this, baseResponse.getMessage(), Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onFailure(Call call, Throwable t) {
                t.printStackTrace();
            }
        });
    }

    private void uploadBase64(String imgBase64) {
        uploadService = new UploadService();
        uploadService.uploadPhotoBase64(TYPE_2, imgBase64, new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                BaseResponse baseResponse = (BaseResponse) response.body();

                if(baseResponse != null) {
                    Toast.makeText(MainActivity.this, baseResponse.getMessage(), Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onFailure(Call call, Throwable t) {
                t.printStackTrace();
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == PICK_IMAGE && resultCode == Activity.RESULT_OK) {
            if(data != null) {
                uri = data.getData();

                imgThumb.setImageURI(uri);
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           String permissions[], int[] grantResults) {
        switch (requestCode) {
            case PERMISSION_REQUEST_STORAGE: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                    openGallery();
                }

                return;
            }
        }
    }

}
Terakhir, tambahkan permission di AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Build dan jalankan aplikasi kemudian coba upload image dengan 2 metode :



Mari kita lihat image yang sudah ter-upload :


- Image baris pertama merupakan hasil dari upload menggunakan Base64.
- Image baris kedua merupakan hasil dari upload menggunakan Multipart.

Dari kedua image tersebut dapat kita lihat bahwa dengan menggunakan Base64, ukuran image menjadi jauh lebih besar dibandingkan dengan ukuran asli image tersebut. Sedangkan dengan menggunakan Multipart, ukuran image tidak terlalu berpengaruh dari ukuran aslinya.

Ukuran asli image sekitar 55 kb.


Kesimpulannya adalah silahkan tentukan sendiri metode apa yang diinginkan sesuai dengan kebutuhan masing-masing :D

Source code lengkap dapat dilihat di https://github.com/wimsonevel/Android-UploadImage

Terima kasih dan semoga bermanfaat.
Happy Coding :)

Share this :

Previous
Next Post »
15 Komentar
avatar

terimakasih ... postingan nya sangat bagus

Balas
avatar

thx postingannya bro, boles mint tolong tutor untuk view update dan delete with image nya ga

Balas
avatar

Sekalian pake mysql gan nanti pathnya dimasukin tabel hehe

Balas
avatar

bang, kalo mau upload dari kamera langsung bisa?

Balas
avatar

blum bisa kalo ini gan, request aja nanti saya buatin tutorialnya -> https://wimsonevel.blogspot.co.id/2017/07/request-tutorial.html

Balas
avatar

bookmark dulu ah, pas waktu senggang dicoba

Balas
avatar

Saya mau tanya untuk di upload ke MySQL gman?

Balas
avatar

Fungsi class file utils itu buat apa ya gan ?

Balas
avatar

simpan alamat folder tmpat nyimpan imagenya aja gan..

Balas

Penulisan markup di komentar
  • Silakan tinggalkan komentar sesuai topik. Komentar yang menyertakan link aktif, iklan, atau sejenisnya akan dihapus.
  • Untuk menyisipkan kode gunakan <i rel="code"> kode yang akan disisipkan </i>
  • Untuk menyisipkan kode panjang gunakan <i rel="pre"> kode yang akan disisipkan </i>
  • Untuk menyisipkan quote gunakan <i rel="quote"> catatan anda </i>
  • Untuk menyisipkan gambar gunakan <i rel="image"> URL gambar </i>
  • Untuk menyisipkan video gunakan [iframe] URL embed video [/iframe]
  • Kemudian parse kode tersebut pada kotak di bawah ini
  • © 2015 Simple SEO ✔

Ads