Welcome to Kotlin Upload Image From Gallery To Server Android Example.
In this example, you will learn how to upload image from gallery to remote server using Kotlin in android app.
We do need to use any external library in this example.
PHP scripts will help us to make connection between android device and remote server (PHP-MySQL).
We will upload image from galley in this example, if you want to capture camera and to upload it, read this kotlin camera image upload tutorial.
First of all, see the below youtube video.
Writing PHP files
We will create a couple of PHP files to interact with server and database.
So, make a new PHP file and give it a name like “config.php”
This “config.php” should have the below source lines
<?php $host="localhost"; $user="your username"; $password="your password"; $db = "your db name"; $con = mysqli_connect($host,$user,$password,$db); // Check connection if (mysqli_connect_errno()) { echo "Failed to connect to MySQL: " . mysqli_connect_error(); }else{ //echo "Connect"; } ?>
This file includes some basic configuration to connect PHP file with the MySQL database.
Now create another PHP file with the name uploadfile.php and it should have below lines
<?php if($_SERVER['REQUEST_METHOD']=='POST'){ // echo $_SERVER["DOCUMENT_ROOT"]; // /home1/demonuts/public_html //including the database connection file include_once("config.php"); //$_FILES['image']['name'] give original name from parameter where 'image' == parametername eg. city.jpg //$_FILES['image']['tmp_name'] temporary system generated name $originalImgName= $_FILES['filename']['name']; $tempName= $_FILES['filename']['tmp_name']; $folder="uploadedFiles/"; $url = "https://www.demonuts.com/Demonuts/JsonTest/Tennis/uploadedFiles/".$originalImgName; //update path as per your directory structure if(move_uploaded_file($tempName,$folder.$originalImgName)){ $query = "INSERT INTO upload_image_video (pathToFile) VALUES ('$url')"; if(mysqli_query($con,$query)){ $query= "SELECT * FROM upload_image_video WHERE pathToFile='$url'"; $result= mysqli_query($con, $query); $emparray = array(); if(mysqli_num_rows($result) > 0){ while ($row = mysqli_fetch_assoc($result)) { $emparray[] = $row; } echo json_encode(array( "status" => "true","message" => "Successfully file added!" , "data" => $emparray) ); }else{ echo json_encode(array( "status" => "false","message" => "Failed!") ); } }else{ echo json_encode(array( "status" => "false","message" => "Failed!") ); } //echo "moved to ".$url; }else{ echo json_encode(array( "status" => "false","message" => "Failed!") ); } } ?>
Above file take the image from android device and it will upload the image and will also add some record in database.
Step 1. Make a New Project
Open your android studio and create a new android studio project. While doing this, make sure that you select “Empty activity” as the default activity.
Also, you should consider to select the Kotlin as the primary source language for the project.
Step 2. GRADLE Files
First, go to your build.gradle(Project: your project name) file. Here, you need to add below line
maven { url 'https://jitpack.io' }
in the following block
allprojects { repositories { google() jcenter() }
So, the last source line for build.gradle(Project: your project name) file is as the below
buildscript { ext.kotlin_version = '1.3.31' repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.4.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { google() jcenter() maven { url 'https://jitpack.io' } } } task clean(type: Delete) { delete rootProject.buildDir }
Now it is time for the second gradle file. Open build.gradle(Module: app) file.
This build.gradle(Module: app) file need to have below lines
android{ useLibrary 'org.apache.http.legacy' } packagingOptions { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' }
in the root or main android { …. } block
Other than this, add the following lines
implementation 'com.karumi:dexter:5.0.0' implementation group: 'org.apache.httpcomponents' , name: 'httpclient-android' , version: '4.3.5.1' implementation('org.apache.httpcomponents:httpmime:4.3') { exclude module: "httpclient" }
in the dependencies { … } block.
Here, first line is for dexter library which will help us to simplify the process of runtime permissions.
All other lines will help us to write the Multipart REQUEST CLASS. It will make http calls to the remote server.
So the final code block for build.gradle(Module: app) file is as the below
apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 28 defaultConfig { applicationId "com.example.uploadgallerykotlin" minSdkVersion 15 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } android{ useLibrary 'org.apache.http.legacy' } packagingOptions { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation 'com.github.kittinunf.fuel:fuel-android:1.15.1' implementation 'com.karumi:dexter:5.0.0' implementation group: 'org.apache.httpcomponents' , name: 'httpclient-android' , version: '4.3.5.1' implementation('org.apache.httpcomponents:httpmime:4.3') { exclude module: "httpclient" } }
Step 3. Manifest File Change
In your AndroidManifest.xml file, add the below permission lines
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.INTERNET" />
Three permissions are there. Internet, Read external storage and Write external storage.
Among these three, internet is less dangerous for user privacy thus, we do not need to implement runtime for it.
But we need to write runtime for read and write external storage permissions. We will do this in the main activity.
Step 4. Making Interface
Make a new Kotlin file and give it a name like AsyncTaskCompleteListener.kt
You should write the below lines in this AsyncTaskCompleteListener.kt file.
interface AsyncTaskCompleteListener { fun onTaskCompleted(response: String, serviceCode: Int) }
As you can see that above file is an interface. We will implement this interface in the main activity and will override onTaskCompleted() method.
Step 5. Writing Multipart File
Create a new Kotlin file and set it’s name as MultipartRequester.kt
Following is the main source code for MultipartRequester.kt
import android.app.Activity import android.app.ActivityManager import android.content.Context import android.os.AsyncTask import android.util.Log import android.widget.Toast import org.apache.http.client.HttpClient import org.apache.http.client.methods.HttpPost import org.apache.http.entity.ContentType import org.apache.http.entity.mime.MIME import org.apache.http.entity.mime.MultipartEntityBuilder import org.apache.http.impl.client.DefaultHttpClient import org.apache.http.params.HttpConnectionParams import org.apache.http.util.EntityUtils import java.io.File class MultiPartRequester( private val activity: Activity, private val map: MutableMap<String, String>, private val serviceCode: Int, asyncTaskCompleteListener: AsyncTaskCompleteListener ) { private var mAsynclistener: AsyncTaskCompleteListener? = null private var httpclient: HttpClient? = null private var request: AsyncHttpRequest? = null init { // is Internet Connection Available... mAsynclistener = asyncTaskCompleteListener as AsyncTaskCompleteListener request = AsyncHttpRequest().execute(map["url"]) as AsyncHttpRequest } internal inner class AsyncHttpRequest : AsyncTask<String, Void, String>() { override fun doInBackground(vararg urls: String): String? { map.remove("url") try { val httppost = HttpPost(urls[0]) httpclient = DefaultHttpClient() HttpConnectionParams.setConnectionTimeout( httpclient!!.getParams(), 600000 ) val builder = MultipartEntityBuilder .create() for (key in map.keys) { if (key.equals("filename", ignoreCase = true)) { val f = File(map[key]) builder.addBinaryBody( key, f, ContentType.MULTIPART_FORM_DATA, f.getName() ) } else { builder.addTextBody( key, map[key], ContentType .create("text/plain", MIME.DEFAULT_CHARSET) ) } Log.d("TAG", key + "---->" + map[key]) // System.out.println(key + "---->" + map.get(key)); } httppost.setEntity(builder.build()) val manager = activity .getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager if (manager.memoryClass < 25) { System.gc() } val response = httpclient!!.execute(httppost) return EntityUtils.toString( response.getEntity(), "UTF-8" ) } catch (e: Exception) { e.printStackTrace() } catch (oume: OutOfMemoryError) { System.gc() Toast.makeText( activity.parent.parent, "Run out of memory please colse the other background apps and try again!", Toast.LENGTH_LONG ).show() } finally { if (httpclient != null) httpclient!!.getConnectionManager().shutdown() } return null } override fun onPostExecute(response: String) { if (mAsynclistener != null) { mAsynclistener!!.onTaskCompleted(response, serviceCode) } } } private fun showToast(msg: String) { Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show() } }
This class is helpful to make http request to the remote server.
Multipart will allow us to send file through the URL.
Step 6. Final Writings
Now we are left with only two files : activity_main.xml and MainActivity.kt
Add the below source lines in activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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:orientation="vertical"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btn" android:layout_gravity="center_horizontal" android:layout_marginTop="0dp" android:textAppearance="?android:attr/textAppearanceLarge" android:text="Select or Capture Image" /> <ImageView android:layout_width="300dp" android:layout_height="200dp" android:layout_gravity="center" android:layout_marginTop="20dp" android:scaleType="fitXY" android:src="@mipmap/ic_launcher" android:id="@+id/iv"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="30dp" android:layout_marginLeft="30dp" android:text="Below is the URL of uploaded image" android:textColor="#000" android:textSize="20sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/tv" android:layout_marginTop="20dp" android:layout_marginLeft="30dp" android:text="dddd" android:textColor="#000" android:textSize="20sp" /> </LinearLayout>
This file includes one button, one image view and two text views.
Button click will allow user to select image from gallery which he wants to upload to server.
Image view will preview the selected image.
One text view is static. Another is variable. Second text view will hold the URL of the uploaded image.
Now, in your MainActivity.kt file, you should write down the below coding lines
import android.Manifest import android.content.Intent import android.graphics.Bitmap import android.media.MediaScannerConnection import android.net.Uri import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.os.Environment import android.provider.MediaStore import android.util.Log import android.view.View import android.widget.Button import android.widget.ImageView import android.widget.TextView import android.widget.Toast import java.io.ByteArrayOutputStream import java.io.File import java.io.FileOutputStream import java.io.IOException import java.util.* import com.karumi.dexter.PermissionToken import com.karumi.dexter.MultiplePermissionsReport import com.karumi.dexter.listener.multi.MultiplePermissionsListener import com.karumi.dexter.Dexter import com.karumi.dexter.listener.PermissionRequest import org.json.JSONException import org.json.JSONObject class MainActivity : AppCompatActivity() ,AsyncTaskCompleteListener { private var btn: Button? = null private var tv: TextView? = null private var imageview: ImageView? = null private val GALLERY = 1 internal var uploadURL = "https://demonuts.com/Demonuts/JsonTest/Tennis/uploadfile.php" var arraylist: ArrayList<HashMap<String, String>>? = null override fun onCreate(savedInstanceState:Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) requestMultiplePermissions() btn = findViewById<View>(R.id.btn) as Button tv = findViewById<View>(R.id.tv) as TextView imageview = findViewById<View>(R.id.iv) as ImageView btn!!.setOnClickListener { choosePhotoFromGallary() } } fun choosePhotoFromGallary() { val galleryIntent = Intent( Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) startActivityForResult(galleryIntent, GALLERY) } public override fun onActivityResult(requestCode:Int, resultCode:Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == GALLERY) { if (data != null) { val contentURI = data!!.data try { val bitmap = MediaStore.Images.Media.getBitmap(this.contentResolver, contentURI) val path = saveImage(bitmap) Toast.makeText(this@MainActivity, "Image Saved!", Toast.LENGTH_SHORT).show() imageview!!.setImageBitmap(bitmap) uploadImage(path) } catch (e: IOException) { e.printStackTrace() Toast.makeText(this@MainActivity, "Failed!", Toast.LENGTH_SHORT).show() } } } } private fun uploadImage(path: String) { val map = HashMap<String, String>() map.put("url", "https://demonuts.com/Demonuts/JsonTest/Tennis/uploadfile.php") map.put("filename", path) MultiPartRequester(this, map, GALLERY, this) } override fun onTaskCompleted(response: String, serviceCode: Int) { Log.d("respon", response.toString()) when (serviceCode) { GALLERY -> if (isSuccess(response)) { val url = getURL(response) tv!!.text = url tv!!.setOnClickListener(View.OnClickListener { val browserIntent = Intent(Intent.ACTION_VIEW) browserIntent.data = Uri.parse(url) startActivity(browserIntent) }) } } } fun isSuccess(response: String): Boolean { try { val jsonObject = JSONObject(response) return jsonObject.optString("status") == "true" } catch (e: JSONException) { e.printStackTrace() } return false } fun getURL(response:String):String { var url = "" try { val jsonObject = JSONObject(response) jsonObject.toString().replace("\\\\", "") if (jsonObject.getString("status").equals("true")) { arraylist = ArrayList<HashMap<String, String>>() val dataArray = jsonObject.getJSONArray("data") for (i in 0 until dataArray.length()) { val dataobj = dataArray.getJSONObject(i) url = dataobj.optString("pathToFile") } } } catch (e: JSONException) { e.printStackTrace() } return url } fun saveImage(myBitmap: Bitmap):String { val bytes = ByteArrayOutputStream() myBitmap.compress(Bitmap.CompressFormat.JPEG, 90, bytes) val wallpaperDirectory = File( (Environment.getExternalStorageDirectory()).toString() + IMAGE_DIRECTORY) // have the object build the directory structure, if needed. Log.d("fee",wallpaperDirectory.toString()) if (!wallpaperDirectory.exists()) { wallpaperDirectory.mkdirs() } try { Log.d("heel",wallpaperDirectory.toString()) val f = File(wallpaperDirectory, ((Calendar.getInstance() .getTimeInMillis()).toString() + ".jpg")) f.createNewFile() val fo = FileOutputStream(f) fo.write(bytes.toByteArray()) MediaScannerConnection.scanFile(this, arrayOf(f.getPath()), arrayOf("image/jpeg"), null) fo.close() Log.d("TAG", "File Saved::--->" + f.getAbsolutePath()) return f.getAbsolutePath() } catch (e1: IOException) { e1.printStackTrace() } return "" } companion object { private val IMAGE_DIRECTORY = "/demonuts_upload" } private fun requestMultiplePermissions() { Dexter.withActivity(this) .withPermissions( Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE ) .withListener(object : MultiplePermissionsListener { override fun onPermissionsChecked(report: MultiplePermissionsReport) { // check if all permissions are granted if (report.areAllPermissionsGranted()) { Toast.makeText(applicationContext, "All permissions are granted by user!", Toast.LENGTH_SHORT) .show() } // check for permanent denial of any permission if (report.isAnyPermissionPermanentlyDenied) { // show alert dialog navigating to Settings //openSettingsDialog() } } override fun onPermissionRationaleShouldBeShown(permissions: List<PermissionRequest>, token: PermissionToken) { token.continuePermissionRequest() } }).withErrorListener { Toast.makeText(applicationContext, "Some Error! ", Toast.LENGTH_SHORT).show() } .onSameThread() .check() } }
Reading Above Code
First of all, focus on the below code
private var btn: Button? = null private var tv: TextView? = null private var imageview: ImageView? = null private val GALLERY = 1 internal var uploadURL = "https://demonuts.com/Demonuts/JsonTest/Tennis/uploadfile.php" var arraylist: ArrayList<HashMap<String, String>>? = null
First line is the object of the button class.
Similarly, second is for text view object and third is for image view object.
Fourth line is the variable with name GALLERY and value 1.
Fifth is also a variable which have the value as the URL to the PHP web service.
And last line is the array list of the Hash map.
Compiler will call the requestMultiplePermissions() method at the starting of onCreate() method.
Below is the code for requestMultiplePermissions() method
private fun requestMultiplePermissions() { Dexter.withActivity(this) .withPermissions( Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE ) .withListener(object : MultiplePermissionsListener { override fun onPermissionsChecked(report: MultiplePermissionsReport) { // check if all permissions are granted if (report.areAllPermissionsGranted()) { Toast.makeText(applicationContext, "All permissions are granted by user!", Toast.LENGTH_SHORT) .show() } // check for permanent denial of any permission if (report.isAnyPermissionPermanentlyDenied) { // show alert dialog navigating to Settings //openSettingsDialog() } } override fun onPermissionRationaleShouldBeShown(permissions: List<PermissionRequest>, token: PermissionToken) { token.continuePermissionRequest() } }).withErrorListener { Toast.makeText(applicationContext, "Some Error! ", Toast.LENGTH_SHORT).show() } .onSameThread() .check() }
This method uses the classes of the dexter library. In this method, I have written the code for asking the runtime permission.
I have asked for read and write external storage permissions.
Now see the below code structure
btn!!.setOnClickListener { choosePhotoFromGallary() }
This is the button click event. Compiler will call choosePhotoFromGallary() method whose source is as the below
fun choosePhotoFromGallary() { val galleryIntent = Intent( Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) startActivityForResult(galleryIntent, GALLERY) }
This method simply open the gallery intent so that user can choose from all the gallery images.
When the user selects an image, compiler will call onActivityResult() method.
Code block for onActivityResult() method is as the below
public override fun onActivityResult(requestCode:Int, resultCode:Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == GALLERY) { if (data != null) { val contentURI = data!!.data try { val bitmap = MediaStore.Images.Media.getBitmap(this.contentResolver, contentURI) val path = saveImage(bitmap) Toast.makeText(this@MainActivity, "Image Saved!", Toast.LENGTH_SHORT).show() imageview!!.setImageBitmap(bitmap) uploadImage(path) } catch (e: IOException) { e.printStackTrace() Toast.makeText(this@MainActivity, "Failed!", Toast.LENGTH_SHORT).show() } } } }
Compiler will get the data of the selected image in this method. It will get the uri from this image data.
Then it will convert this URI into the bitmap. Using this bitmap compiler will save the image into the external storage using the saveImage() method.
After saving the image, compiler will set the image into the image view.
After this, compiler will call uploadImage() method which has the following code.
private fun uploadImage(path: String) { val map = HashMap<String, String>() map.put("url", "https://demonuts.com/Demonuts/JsonTest/Tennis/uploadfile.php") map.put("filename", path) MultiPartRequester(this, map, GALLERY, this) }
Here, compiler will create one hash map. This hash map will have two maps. One is for URL to the web service and another is for the image data ( It will get image data from image path ).
After making the successful http call, compiler will execute ontaskCompleted() method.
One thing to notice is that we have implemented an interface as the below
class MainActivity : AppCompatActivity() ,AsyncTaskCompleteListener {
So for this interface, we need to override ontaskCompleted() method. Below is the code for ontaskCompleted() method.
override fun onTaskCompleted(response: String, serviceCode: Int) { Log.d("respon", response.toString()) when (serviceCode) { GALLERY -> if (isSuccess(response)) { val url = getURL(response) tv!!.text = url tv!!.setOnClickListener(View.OnClickListener { val browserIntent = Intent(Intent.ACTION_VIEW) browserIntent.data = Uri.parse(url) startActivity(browserIntent) }) } } }
In this method, compiler will get the URL of the uploaded image usign getURL() method.
This method is fetching the URL from the JSON response.
I have also written click event for the text view. A text view is having the text value as the URL to the uploaded image.
When the user clicks this text view, compiler will open this URL in the web browser.