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 :
<?phpBuat Project Android
$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);
}
?>
Pertama, tambahkan library retrofit dan okhttp3 ke gradle module-level
dependencies {Buat layout dengan nama activity_main.xml
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'
}
<?xml version="1.0" encoding="utf-8"?>Buat package dengan nama network.
<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 kelas dengan nama Config.java di package network. Kelas ini berisi URL dan Endpoint dari API.
/**Kemudian buat kelas dengan nama BaseResponse.java
* 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 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.
* 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;
}
}
import okhttp3.MultipartBody;Kemudian buat kelas UploadService.java untuk konfigurasi retrofit dan method dari interface.
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);
}
/**Selanjutnya buat beberapa kelas utility berikut :
* 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);
}
}
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;ImageUtils.java
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);
}
}
import android.graphics.Bitmap;Sekarang Buka MainActivity.java dan lengkapi dengan kode berikut :
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);
}
}
import android.app.Activity;Terakhir, tambahkan permission di AndroidManifest.xml
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;
}
}
}
}
<uses-permission android:name="android.permission.INTERNET" />Build dan jalankan aplikasi kemudian coba upload image dengan 2 metode :
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
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 :)