
In this second part of the tutorial, we will create an Android app with Retrofit to upload an image to the Node.js server we created before.
For the first part of the tutorial to create Node.js server to upload Images.
In this second part of the tutorial, we will create an Android app with Retrofit to upload an image to the Node.js server we created before.
Creating Project
Here I have created an Android Studio project with package com.learn2crack.imageupload also Activity as MainActivity and layout as activity_main.
Adding Dependencies
We need to add dependencies for Retrofit and Retrofit GSON converter library. Also, make sure you have Design Support library and Constraint Layout library in your build.gradle. Latest versions of Android Studio adds these libraries on creating new project.
build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion "26.0.1"
defaultConfig {
applicationId "com.learn2crack.imageupload"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.android.support:design:26.0.0'
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.0'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.0'
}
Adding Permissions Required
We need to add Internet permission in our AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET"/>
Creating Layout
Our main layout has a content layout included which has two buttons to upload and view image. It also has a progress bar.
content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.learn2crack.imageupload.MainActivity"
tools:showIn="@layout/activity_main">
<Button
android:id="@+id/btn_select_image"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="47dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:text="Select Image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toTopOf="@+id/btn_show_image" />
<Button
android:id="@+id/btn_show_image"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="Show Image"
app:layout_constraintBottom_toTopOf="@+id/progress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_select_image"
android:visibility="gone"/>
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="8dp"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp"
app:layout_constraintHorizontal_bias="0.5"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@+id/btn_show_image"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="gone"/>
</android.support.constraint.ConstraintLayout>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.learn2crack.imageupload.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main" />
</android.support.design.widget.CoordinatorLayout>
Creating Model class
We are creating a Model Response.java class with message and path fields.
Response.java
package com.learn2crack.imageupload;
public class Response {
private String message;
private String path;
public String getMessage() {
return message;
}
public String getPath() {
return path;
}
}
Creating Retrofit Interface
We have defined a Retrofit interface with Multipart as the type to upload the image.
RetrofitInterface.java
package com.learn2crack.imageupload;
import okhttp3.MultipartBody;
import retrofit2.Call;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
public interface RetrofitInterface {
@Multipart
@POST("/images/upload")
Call<Response> uploadImage(@Part MultipartBody.Part image);
}
Creating Activity
In our MainActivity when the image select button is pressed we start a Intent to pick image. On Result, we will get the Uri of the image. From that Uri, we will open an InputStream and read the image contents as a byte array. From Android Nougat there were some changes which do not allow us to get the Real file path of the image. So we are using this alternate method. With the byte array, we will create an okhttp RequestBody object. From that, we can create a MultipartBody.Part object which can be used with the Retrofit interface to upload the image. We display the message in a Snackbar when the image is uploaded, or any error message is returned.
When the image is uploaded successfully, we will get the path of the uploaded image. We will use this path of the image to construct the URL and launch an external browser to view the image.
MainActivity.java
package com.learn2crack.imageupload;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import com.google.gson.Gson;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class MainActivity extends AppCompatActivity {
public static final String TAG = MainActivity.class.getSimpleName();
private static final int INTENT_REQUEST_CODE = 100;
public static final String URL = "http://10.0.2.2:8080";
private Button mBtImageSelect;
private Button mBtImageShow;
private ProgressBar mProgressBar;
private String mImageUrl = "";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
initViews();
}
private void initViews() {
mBtImageSelect = findViewById(R.id.btn_select_image);
mBtImageShow = findViewById(R.id.btn_show_image);
mProgressBar = findViewById(R.id.progress);
mBtImageSelect.setOnClickListener((View view) -> {
mBtImageShow.setVisibility(View.GONE);
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/jpeg");
try {
startActivityForResult(intent, INTENT_REQUEST_CODE);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
}
});
mBtImageShow.setOnClickListener(view -> {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(mImageUrl));
startActivity(intent);
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == INTENT_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
try {
InputStream is = getContentResolver().openInputStream(data.getData());
uploadImage(getBytes(is));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public byte[] getBytes(InputStream is) throws IOException {
ByteArrayOutputStream byteBuff = new ByteArrayOutputStream();
int buffSize = 1024;
byte[] buff = new byte[buffSize];
int len = 0;
while ((len = is.read(buff)) != -1) {
byteBuff.write(buff, 0, len);
}
return byteBuff.toByteArray();
}
private void uploadImage(byte[] imageBytes) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
RetrofitInterface retrofitInterface = retrofit.create(RetrofitInterface.class);
RequestBody requestFile = RequestBody.create(MediaType.parse("image/jpeg"), imageBytes);
MultipartBody.Part body = MultipartBody.Part.createFormData("image", "image.jpg", requestFile);
Call<Response> call = retrofitInterface.uploadImage(body);
mProgressBar.setVisibility(View.VISIBLE);
call.enqueue(new Callback<Response>() {
@Override
public void onResponse(Call<Response> call, retrofit2.Response<Response> response) {
mProgressBar.setVisibility(View.GONE);
if (response.isSuccessful()) {
Response responseBody = response.body();
mBtImageShow.setVisibility(View.VISIBLE);
mImageUrl = URL + responseBody.getPath();
Snackbar.make(findViewById(R.id.content), responseBody.getMessage(),Snackbar.LENGTH_SHORT).show();
} else {
ResponseBody errorBody = response.errorBody();
Gson gson = new Gson();
try {
Response errorResponse = gson.fromJson(errorBody.string(), Response.class);
Snackbar.make(findViewById(R.id.content), errorResponse.getMessage(),Snackbar.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(Call<Response> call, Throwable t) {
mProgressBar.setVisibility(View.GONE);
Log.d(TAG, "onFailure: "+t.getLocalizedMessage());
}
});
}
}
Screenshots
Complete Project Files
You can download the complete project as zip or fork from our Github repository.
Stay tuned!
Enjoy coding 🙂