Using Camera in Android - Kotlin

Nowadays, most of our Android app require access to Camera to capture picture or Video. We can do that in two ways.
                                (i) Using built-in Camera,
                                (ii) Using custom Camera.

Using Built-in Camera

In this post, Let's see how to use the built-in camera to capture images and save it.

Its a simple way to capture pics. We can use the mobile's built-in camera app using Intent. 

 val REQUEST_IMAGE_CAPTURE = 1

 private fun takePictureIntent() {
      val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
      if (takePictureIntent.resolveActivity(packageManager) != null) {
           startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
      }
 }


After the picture has been captured, the result will received on onActivityResult method. There, we can get the data, in Bitmap form.


override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode == Activity.RESULT_OK) {
            when (requestCode) {
                CAMERA_REQUEST_CODE -> {

                    val extras = data?.getExtras()
                    val imageBitmap = extras?.get("data") as Bitmap
                    image.setImageBitmap(imageBitmap)

                }
            }
        }
    }

Implementation:

AndroidManifest.xml



<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<uses-feature android:name="android.hardware.camera"  android:required="true" />

camera_intent.xml

Design a layout with a button, while clicking on it, Capture the picture and show it in an ImageView. Also add a TextView to display URI of the captured picture in it.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/image"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginTop="20dp"
        android:src="@drawable/chris_tree"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/capture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:background="@color/colorPrimary"
        android:padding="5dp"
        android:text="Capture"
        android:textColor="@color/white"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/image" />

    <TextView
        android:id="@+id/uriText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:padding="5dp"
        android:textColor="@color/colorPrimary"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/capture" />

</android.support.constraint.ConstraintLayout>

CameraIntentActivity.kt

This class is to open the camera app while clicking on a button. Before that, don't forget to check for permissions of Camera and Storage.

And on onActivityResult(), get the intent data in the form of Bitmap and display that in an ImageView.

class CameraIntentActivity : AppCompatActivity(), View.OnClickListener {

    private val CAMERA_REQUEST_CODE = 12345
    private val REQUEST_GALLERY_CAMERA = 54654


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.camera_intent)

        capture.setOnClickListener(this)
    }


    override fun onClick(v: View?) {
        when (v) {
            capture -> {
                if (Build.VERSION.SDK_INT >= 23) {
                    if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                        ActivityCompat.requestPermissions(
                                this,
                                arrayOf(android.Manifest.permission.CAMERA, android.Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_GALLERY_CAMERA)
                    } else {
                        openCamera()
                    }
                } else {
                    openCamera()
                }
            }
        }
    }

    private fun openCamera() {
        val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        if (intent.resolveActivity(packageManager) != null)
            startActivityForResult(intent, CAMERA_REQUEST_CODE)
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_GALLERY_CAMERA) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
                openCamera()
            } else {
                Toast.makeText(this@CameraIntentActivity, getString(R.string.permission_denied), Toast.LENGTH_SHORT).show()
            }
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode == Activity.RESULT_OK) {
            when (requestCode) {
                CAMERA_REQUEST_CODE -> {

                    val extras = data?.getExtras()
                    val imageBitmap = extras?.get("data") as Bitmap
                    image.setImageBitmap(imageBitmap)

                }
            }
        }
    }
}


 


More Customization:

Above example is for simpler use like just displaying the captured picture in an ImageView. But in our real-time application, we need more than that. i.e., atleast URI of the captured picture. We can also use custom filepath, where the captured image can be saved.



    val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
    if (intent.resolveActivity(packageManager) != null) {  
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)            
        startActivityForResult(intent, CAMERA_REQUEST_CODE)
    }

Here, imageUri will be the custom path of captured image.

file_paths.xml

We have to create directory xml and inside that, a file named file_paths.xml have to be created. In which, the external file path will be added.

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images"
 path="Android/data/com.yamikrish.app/files/Pictures" />
</paths>

AndroidManifest.xml

Add the following lines in AndroidManifest.xml



        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.yamikrish.app.android.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"></meta-data>
        </provider>

CameraIntentActivity.kt

We have to pass the custom URI as Intent's extra while opening the camera. Also we have grant permission to access FileProvider as per device's version. Otherwise, it won't work properly as expected.

Also, remember that the path we are passing in that function should be as same as the one we have declared in file_paths.xml.

Incase, if you would like to compress the image before upload it to the server, you can do that. I have included a class, CompressImage() to do that for you. That's it!! We 're done!!!


class CameraIntentActivity : AppCompatActivity(), View.OnClickListener {

    private val CAMERA_REQUEST_CODE = 12345
    private val REQUEST_GALLERY_CAMERA = 54654
    var imageUri = ""


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.camera_intent)

        capture.setOnClickListener(this)
    }


    override fun onClick(v: View?) {
        when (v) {
            capture -> {
                if (Build.VERSION.SDK_INT >= 23) {
                    if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                        ActivityCompat.requestPermissions(
                                this,
                                arrayOf(android.Manifest.permission.CAMERA, android.Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_GALLERY_CAMERA)
                    } else {
                        openCamera()
                    }
                } else {
                    openCamera()
                }
            }
        }
    }


    private fun openCamera() {
        val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)

        if (intent.resolveActivity(packageManager) != null) {
            var photoFile: File? = null
            try {
                photoFile = createImageFile()
            } catch (ex: IOException) {
                ex.printStackTrace()
            }

            if (photoFile != null) {
                val photoURI = FileProvider.getUriForFile(this,
                        "com.yamikrish.app.android.fileprovider",
                        photoFile)
                intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
                } else {
                    val resInfoList = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
                    for (resolveInfo in resInfoList) {
                        val packageName = resolveInfo.activityInfo.packageName
                        grantUriPermission(packageName, photoURI, Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
                    }
                }
                intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
                startActivityForResult(intent, CAMERA_REQUEST_CODE)
            }
        }
    }

    @Throws(IOException::class)
    private fun createImageFile(): File {
        val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
        val imageFileName = "JPEG_" + timeStamp + "_"
        val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
        val image = File.createTempFile(
                imageFileName, /* prefix */
                ".jpg", /* suffix */
                storageDir      /* directory */
        )

        // Save a file: path for use with ACTION_VIEW intents
        imageUri = image.absolutePath
        return image
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_GALLERY_CAMERA) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
                openCamera()
            } else {
                Toast.makeText(this@CameraIntentActivity, getString(R.string.permission_denied), Toast.LENGTH_SHORT).show()
            }
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode == Activity.RESULT_OK) {
            when (requestCode) {
                CAMERA_REQUEST_CODE -> {
                    //CompressImage().execute(imageUri) //If you would like to compress the image size before upload it to server, use that class
                    uriText.text = imageUri
                }
            }
        }
    }

    internal inner class CompressImage : AsyncTask<String, Void, String>() {

        override fun onPreExecute() {
            super.onPreExecute()

        }

        override fun doInBackground(vararg params: String): String {
            val result = getRealPathFromURI(params[0])
            val file = File(result!!)
            val bitmap = decodeImageFile(file)
            val stream = ByteArrayOutputStream()
            bitmap!!.compress(Bitmap.CompressFormat.PNG, 100, stream)


            val image = stream.toByteArray()
            return Base64.encodeToString(image, Base64.DEFAULT).replace("\n".toRegex(), "")
        }

        override fun onPostExecute(result: String) {
            super.onPostExecute(result)
            try {
                val jObject = JSONObject()
                //Here you can upload the compressed image to your server
            } catch (e: JSONException) {
                e.printStackTrace()
            }


        }
    }


    /* To get real path from URI of File */
    private fun getRealPathFromURI(contentURI: String): String? {
        val contentUri = Uri.parse(contentURI)
        val cursor = contentResolver.query(contentUri, null, null, null, null)
        if (cursor == null) {
            return contentUri.path
        } else {
            var res: String? = null
            if (cursor.moveToFirst()) {
                val column_index = cursor
                        .getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
                res = cursor.getString(column_index)
            }
            cursor.close()
            return res
        }
    }

    /* To convert Image to Bitmap */
    fun decodeImageFile(f: File): Bitmap? {
        try {
            val o = BitmapFactory.Options()
            o.inJustDecodeBounds = true
            BitmapFactory.decodeStream(FileInputStream(f), null, o)
            val REQUIRED_SIZE = 150
            var scale = 1
            while (o.outWidth / scale / 2 >= REQUIRED_SIZE && o.outHeight / scale / 2 >= REQUIRED_SIZE)
                scale *= 2
            val o2 = BitmapFactory.Options()
            o2.inSampleSize = scale

            return BitmapFactory.decodeStream(FileInputStream(f), null, o2)
        } catch (e: FileNotFoundException) {
            e.printStackTrace()
        }

        return null
    }
}

Run Application:

                      


Comments

Post a Comment

Popular posts from this blog

SOAP Client using ksoap2 in Android - Kotlin

RecyclerView with different number of columns using SpanSizeLookup

Databinding in RecyclerView - Android - Kotlin

Map, Location update and AutoComplete Places - Kotlin

Room with LiveData, ViewModel - Android Architecture Components - Kotlin

Stripe Integration in Android - Kotlin

Braintree Integration in Android - Kotlin

Android JetPack - Scheduling Tasks with WorkManager

Using RxJava, Retrofit in Android - Kotlin