Taking a Photo: Android Studio

Introduction

The following post has been motivated by the fact that the method startForActivityResult() in the android studio is currently being deprecated and a new class called ActivityResultLauncher<Intent> has been introduced in its place. The main difference is that the latter has only one argument in its onActivityResult() overridden method, which is of type ActivityResult and as such reduces the number of arguments that have to be used after the image has been captured to process and fetch data from the camera intent.

@Override
public void onActivityResult(ActivityResult result){}

We will need the following dependencies in our app module build.gradle file before proceeding. We will use butterknife library to bind our views in our activity. Our main annotations will come from androidx.

dependencies{

implementation fileTree(dir: ‘libs’, include: [‘*.jar’]) implementation ‘androidx.appcompat:appcompat:1.2.0’ implementation ‘androidx.appcompat:appcompat:1.3.0-alpha02’ implementation ‘com.jakewharton:butterknife:10.2.3’ annotationProcessor ‘com.jakewharton:butterknife-compiler:10.2.3’

}

check code on github

Check out the code in GitHub

Android Manifest file

Add the following permissions in your AndroidManifest.xml file:

#AndroidManifest.xml<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.CAMERA" />
  1. We need the android.permission.CAMERA permission to enable the android device to allow the application to use the device’s camera feature.
  2. We need the android.permission.WRITE_EXTERNAL_STORAGE since we will need our device to allow the application to create a temporary file that will hold the contents of our captured image.
  3. Because of the android.permission.WRITE_EXTERNAL_STORAGE dependency, we need to add a code snippet in our AndroidManifest.xml file that will contain the details of the file containing the paths of the temporary file that is created. To avail a file for use, we need a file provider. The Android FileProvider component generates content URIs for files. The provider should hence be placed in the AndroidManifest.xml before compiling or running our application. After the file has been created, it can be broadcasted to other applications or uploaded to a server. Therefore add the following to the manifest file within the application tags:
#AndroidManifest.xml<application>
....
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.mycamera.photo.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
...
</application>

In the line com.mycamera.photo.fileprovider , the section com.mycamera.photo represents the package name of your main application. The fileprovider suffixed represents the custom file provider attribute that we will use in our activity. If the exact line “com.mycamera.photo.fileprovider” will not be referenced from the activity, then the application will not have any authority to write the captured image data in the temporary external file.

The line

android:grantUriPermissions="true"

allows the application to fetch the image URI from the saved temporary image file that will be created.

The lines

<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />

in essence, will provide the basic paths for any image file that will be created. In short according to google documentation:

The <meta-data> child element of the <provider> points to an XML file that specifies the directories you want to share. The android:resource attribute is the path and name of the file, without the .xml extension

The path string is therefore laid out in an XML file under the @xml folder which is found within the layout resource folder generally know as res. You may modify the path statements as you please. To create the file ‘file_paths.xml’, within the res folder, create an xml folder within the res folder first, then add the following XML file.

#file_paths.xml<?xml version="1.0" encoding="utf-8"?>
<paths >
<external-path
name="my_images"
path="."/>

</paths>

In this example, the <external-path> tag shares directories within the files/ directory of your app's external storage. The path attribute shares the my_images/ subdirectory of files/.

The final image sample URI will therefore read as follows if our image is CAPTURED_IMAGE.jpg:

content://com.mycamera.photo.fileprovider/my_images/CAPTURED_IMAGE.jpg

NOTE: If the permissions are not listed in the AndroidManifest.xml file, our camera application may not function correctly.

  1. Adding build.gradle(app module) dependencies
  2. Adding necessary permissions in the AndroidManifest.xml file
  3. Including a FileProvider to make the captured image directory sharable.

Let us create a resource layout file for our MainActivity. This can easily be generated using android studio when generating a new activity. But the contents should be modified as follows in our example.

activity_main.xml

#activity_main.xml<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<Button
android:id="@+id/takePhotoButton"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

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

</LinearLayout>

Our MainActivity.java will look as follows


@SuppressLint("NonConstantResourceId")
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@BindView(R.id.takePhotoButton) Button mTakePhotoButton;
@BindView(R.id.capturedPhotoImageView) ImageView mCapturedImageView;
String pictureFilePath;
File imageFile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Butterknife.bind(this);
mTakePhotoButton.setOnCliclListener(this);}@Override
public void onClick(View v) {
if(v == mTakePhotoButton){
launchCameraForImageCapture();
}
}

In the above code snippet, we have set the OnclickListener to the button and implemented the View.OnclickListener in the MainActivity.java file.

If the button is clicked we should open the camera but, first, we need to check if the application has the permissions to use the camera. This is where the permissions we included in AndroidManifest.xml comes in handy.

We will therefore modify our onClick(View v) method as follows:

@Overridepublic void onClick(View v){if(v == mTakePhotoButton){if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED){launchCameraForImageCapture();}else{String[] permissionRequest = {Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE};
requestPermissions(permissionRequest,CAMERA_PERMISSION_REQUEST_CODE);
}}}

Launching The Camera

The above method ensures that the application has the permissions to use the camera to perform image capture and that it also has permission to write to external storage. If the permissions are not present, then a request is made for the same and if they are present, the device camera is launched using the method launchCameraForImageCapture();

Please recall that a temporary file has to be created wherein the image data will be stored after image capture. The method to do that is shown below:

@SuppressLint("SimpleDateFormat")
private File createImageFile() throws IOException {
String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
String pictureFile = "assets_" + timeStamp;
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(pictureFile, ".jpg", storageDir);
pictureFilePath = image.getAbsolutePath();
return image;
}

In the above code, we create part of the picture file path to contain a date-time signature so that each photo taken will be unique owing to the fact each image-capture time is unique. We then create a file which we name the storageDir which refers to the external environment storage directory. We then create an image from the directory and format it to include a ‘.jpg’ extension. The method then returns the image. The method also is able to fetch the absolute path and store it in the string pictureFilePath.

Creating Intent for Image Capture

We then start the intent for capturing the image using the device’s camera. The method can be implemented as follows:

@SuppressLint("QueryPermissionsNeeded")
private void launchCameraForImageCapture() {
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (cameraIntent.resolveActivity(getPackageManager()) != null) {

File pictureFile = null;
try {
pictureFile = createImageFile();
Toast.makeText(MainActivity.this, "Created a file", Toast.LENGTH_LONG).show();
} catch (IOException ex) {
Toast.makeText(this,
"Photo file can't be created, please try again",
Toast.LENGTH_SHORT).show();
return;
}
if (pictureFile != null) {
Uri photoURI = FileProvider.getUriForFile(MainActivity.this,
this.getApplicationContext().getPackageName()+".fileprovider",
pictureFile);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
capturedImageResultLauncher.launch(cameraIntent);


}
}

In the above code, we create an implicit intent that is to access the camera of the device. This statement “MediaStore.ACTION_IMAGE_CAPTURE” defines that implicit camera intent. We then initialize a variable, pictureFile , of type File that will hold the image file returned from the createimagefile() method. We then show a toast to notify us if an image file was created successfully or not. If the method did not return a null value, we then proceed to fetch the photo URI from the file created using the authority that was specified in the AndroidManifest.xml; that is the statement “this.getapplicationContext().getPackageName()+”.fileprovider” should perfectly mirror the AndroidManifest.xml statement that was specified in the provider tag:

android:authorities="com.mycamera.photo.fileprovider"

If the two statements will not rhyme, then you will not be able to fetch the Uri and you may end up with a Null Pointer Exception at some point in the course of developing your application. We then add our photoUri as an extra which in this case is MediaStore.ExtraOutput before proceeding to create the result launcher for our image capture intent.

Captured Image Result

That was quite a read, right! But this is the sweetest section. It brings to view how easy it is to fetch the results of the image using the class ActivityResultLauncher<Intent> as compared to the former method which had three arguments to be processed as seen below.

public void onActivityResult(int requestCode, int resultCode, Intent data)

So instead of using the above method to fetch the results of the image capture action, we instead use the below method.

ActivityResultLauncher<Intent> capturedImageResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == RESULT_OK){
imageFile = new File(pictureFilePath);
Uri fileUriTaken = Uri.fromFile(imageFile); #fileUriTaken = result.getData().getData();
try {
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), fileUriTaken);
mCapturedImageView.setImageBitmap(Bitmap.createScaledBitmap(bitmap, 180, 180, false));
} catch (IOException e) {
e.printStackTrace();
}

}

}
});

It is clear from the above method that the method registerForActivityResult(), takes two main arguments; a method, startForActivityForResult(), that is obtained from the ActivityResultContracts and a callback function that should return the results fetched from the camera capture intent. Again allow me to quote google on the issue of ActivityResultContracts definition:

An ActivityResultContract defines the input type needed to produce a result along with the output type of the result. The APIs provide default contracts for basic intent actions like taking a picture, requesting permissions, and so on. You can also create your own custom contracts.

From the above definition, since our intent contained an extra that was a file URI, it is clear that the output to be fetched should also be of type URI. Do you now see the advantage of using ActivityResultLauncher? you clearly know what to fetch as output from the extras that you added to your implicit intent. this is clearly shown by the commented out statement. Most of the variables such as imageFile and pictureFilePath could have been declared as local variables, but assuming that other classes or methods within the main class may need them, they have been declared as global. We then use the URI fetched to create an image of type Bitmap.

The below code snippet statement is required if the application is to display in the mCapturedImageView view for the latest Android APIs. This is because it scales the height and width of the bitmap image to the desired dimensions before displaying it in the image view.

mCapturedImageView.setImageBitmap(Bitmap.createScaledBitmap(bitmap, 180, 180, false));

Otherwise, some images may be too big and thus may not display in the ImageView; that is if the statement were left as follows:

mCapturedImageView.setImageBitmap(bitmap);

Conclusion

This is all we need to capture an image and display it in the ImageView using ActivityResultLauncher<Intent> class.

You can follow me on:

Github

LinkedIn

My Portfolio

Android and Web Developer