Mengambil Gambar dengan Kamera | Nougat Supported

Fitur kamera penting sekali kegunaannya dalam sebuah aplikasi, semisal untuk mengambil foto wajah kita untuk dijadikan profile picture atau untuk mengambil gambar makanan untuk kita unggah di instagram dan sebagainya. Namun fitur kamera ini agak berbeda dalam pembuatannya ketika aplikasi diharuskan support android versi nougat.

Bagi developer yang pernah membuat aplikasi yang menggunakan fitur kamera dan memperbarui aplikasinya untuk support android versi nougat, kemungkinan besar aplikasinya akan crash jika kode yang digunakan belum sesuai untuk versi nougat.

Oleh karena itu di tutorial kali ini saya akan membagikan langkah-langkahnya supaya aplikasi dapat mengambil gambar dengan kamera di versi nougat atau di versi lainnya (belum testing semua). Saya juga akan membagikan source code aplikasi yang akan kita buat kali ini pada bagian akhir tutorial, jadi simak terus ya sampai akhir.

Untuk tampilan aplikasinya sangat sederhana, hanya butuh 1 activity dengan tampilan sebagai berikut :

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.andevindo.nougatcamera.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.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:src="@drawable/ic_photo_camera_white_24dp" />

</android.support.design.widget.CoordinatorLayout>

content_main.xml

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.andevindo.nougatcamera.MainActivity"
    tools:showIn="@layout/activity_main">

    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</android.support.constraint.ConstraintLayout>

Di tampilan di atas terdapat :

  • ImageView untuk menampilkan hasil jepretan dari kamera
  • FloatingActionButton untuk menampilkan kamera

Lalu perlu dibuat sebuah file .xml yang disimpan di folder res/xml, kodenya seperti di bawah :

provider_paths.xml

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

Lalu tambahkan beberapa kode di manifest :

Permissions

<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.camera"/>

Provider(di dalam application)

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="${applicationId}.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths"/>
</provider>

Untuk kode javanya yaitu sebagai berikut :

MainActivity.java

package com.andevindo.nougatcamera;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import static android.content.pm.PackageManager.PERMISSION_GRANTED;

public class MainActivity extends AppCompatActivity {

    private ImageView mImage;
    private String mCurrentPhotoPath;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        mImage = findViewById(R.id.image);
        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (checkPermission())
                    show(1);
            }
        });
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("path", mCurrentPhotoPath);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        mCurrentPhotoPath = savedInstanceState.getString("path");
        mImage.setImageBitmap(scaleBitmap(getCurrentPhotoBitmap(), 0.5f));
    }

    void show(int requestCode) {
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        try {
            Uri outPutUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider", createImageFile());
            intent.putExtra(MediaStore.EXTRA_OUTPUT, outPutUri);
            startActivityForResult(intent, requestCode);
        } catch (IOException e) {
            e.printStackTrace();
        }


    }
    
    boolean checkPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                != PERMISSION_GRANTED
                || ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    1);
            return false;
        } else {
            return true;
        }
    }

    File createImageFile() throws IOException {
        String timeStamp = new SimpleDateFormat("dd_MM_yyyy_HH_mm_ss").format(new Date());
        String imageFileName = "JPEG_" + timeStamp + "_";
        File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        File image = File.createTempFile(
                imageFileName,  /* prefix */
                ".jpg",         /* suffix */
                storageDir      /* directory */
        );

        mCurrentPhotoPath = image.getAbsolutePath();
        return image;
    }

    Bitmap getCurrentPhotoBitmap() {
        return BitmapFactory.decodeFile(mCurrentPhotoPath);
    }

    Bitmap scaleBitmap(Bitmap bitmap, float scaleRatio) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();

        Matrix matrix = new Matrix();
        matrix.postScale(scaleRatio, scaleRatio);

        Bitmap resizedBitmap = Bitmap.createBitmap(
                bitmap, 0, 0, width, height, matrix, false);
        bitmap.recycle();
        return resizedBitmap;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == 1) {
            if (grantResults.length > 1
                    && grantResults[0] == PERMISSION_GRANTED && grantResults[1] == PERMISSION_GRANTED) {
                show(1);
            }
        }

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 1) {
            if (resultCode == RESULT_OK) {
                if (getCurrentPhotoBitmap() != null)
                    mImage.setImageBitmap(scaleBitmap(getCurrentPhotoBitmap(), 0.5f));
                else
                    Toast.makeText(this, "Null", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

Penjelasan kode di atas :

  • onSaveInstanceState dan onRestoreInstanceState digunakan untuk menyimpan dan mengembalikan nilai mCurrentPhotoPath. Ini diperlukan karena ketika tampilan berotasi atau activity berhenti(onStop()) maka mCurrentPhotoPath akan null kembali.
  • boolean checkPermission() berisikan kode yang digunakan untuk mengecek permission CAMERA dan WRITE_EXTERNAL_CODE. Akan bernilai true jika 2 permission tersebut disetujui.
  • void Show(int requestCode), fungsi ini berisikan kode untuk menampilkan kamera. Parameter requestCode bertipe int yang digunakan untuk menyeleksi nilai balikan dari kamera di fungsi onActivityResult. Lalu di fungsi ini terdapat pemanggilan fungsi lain yaitu createImageFile().
  • File createImageFile() ini fungsinya untuk membuat file temporary yang nantinya akan berisi gambar yang kita ambil dari kamera.
  • Bitmap getCurrentPhotoBitmap() ini fungsinya untuk mendecode file dari path yang dibuat dari fungsi createImageFile(). Nilai kembaliannya berupa Bitmap.
  • Bitmap scaleBitmap(Bitmap bitmap, float scaleRatio), fungsi ini berguna untuk mengubah ukuran gambar sesuai dengan skala yang dimasukkan.
  • Gambar yang diambil dari kamera akan ditangkap pada fungsi onActivityResult().

Kode lengkap bisa dilihat diĀ https://github.com/andevindo/nougat-camera