DEV Community

EdRome
EdRome

Posted on

How to create an Android App: picture picking

Hi again (x2) everyone!

The last post we talk about the datepicker and name text view, because we already review radio buttons then we’ll check profile photo changing. We’ll use the Circular Image View from mikhaellopez package; Glide, an easy way to load and caching images; Image Cropper, a really useful library if you want to crop a image to make it fit inside a component; and Dexter, a really easy way to ask for permissions.

Let’s start with the following implementation inside app build gradle

implementation 'com.mikhaellopez:circularimageview:4.0.1'
implementation 'com.github.bumptech.glide:glide:4.9.0'
implementation 'com.karumi:dexter:5.0.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
api 'com.theartofdev.edmodo:android-image-cropper:+'

On our content main, go to text edition and add the CiruclarImageView component, define a size on width and height attributes. Also select an image to visualize the component in the design layout.

<com.mikhaellopez.circularimageview.CircularImageView
    android:id="@+id/profile_imageView"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:layout_marginStart="8dp"
    android:layout_marginLeft="8dp"
    android:layout_marginEnd="8dp"
    android:layout_marginRight="8dp"
    android:layout_marginBottom="8dp"
    android:contentDescription="@string/nav_header_desc"
    android:src="@drawable/baseline_person_outline_24px"
    android:onClick="showPictureDialog"
    app:layout_constraintBottom_toTopOf="@+id/profile_username_edittext"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toEndOf="@+id/profile_photo_textview" />

I set a default image to circular image. I get it from material.io resources are free.

The component need a logic to allow user take a picture or select one from gallery. The functionality is done by using android imagecropper, additional, into the manifest, permissions will be added.

<manifest>

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

    <application>
        ....
        <activity
            android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
            android:theme="@style/Base.Theme.AppCompat" />
    </application>

</manifest>

To maintain readability in the project, we will create a package named utils where photo selection logic is going to be located. Package creation is done by right clicking over main package, then new, and package. Enter a name, in my case is utils.

Creating Package

Once the package is created, right click again over utils package and select the Java Class option

Creating Class

Pick up a name to the class (mine is photo), after this process we’ll write down a small functionally that invoke dialog where let the user take a photo or pick it from various sources. The method is static because it’s not necessary to create an object and store it in memory.

public static void choosePhoto(Activity activity) {
    CropImage.activity()
             .setGuidelines(CropImageView.Guidelines.ON)
             .start(activity);
}

Subsequently, in Main activity java class, we’ll add severus methods explained below.

First, a method to be called when Circular image view is clicked. As I said in the previous post, to create a method to be called an onClick event it has to be public, void, with a formal parameter of type View. Inside this method, it’ll call our method displaying the dialog to the user to let him select a source for his photo.

public void showPictureDialog(View view) {
    photo.choosePhoto(this);
}

When the image has been selected, then we override an onActivityResult method that put the image onto the Circular Image View when selected.

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {

    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode == this.RESULT_CANCELED) {
        return;
    } else if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) {
        CropImage.ActivityResult result = CropImage.getActivityResult(data);
        if (resultCode == RESULT_OK) {
            Uri resultUri = result.getUri();
            try {
                Bitmap bitmap = MediaStore.Images.Media
                                 .getBitmap(this.getContentResolver(),resultUri);
                Glide.with(this).load(bitmap).into(imageview);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) {
            Exception error = result.getError();
        }
    }
}

Moreover, the user needs to give us the sufficient permissions to access to his media content. Dexter is another library that simplify this task by implementing a couple of methods. (I’ll explain the easiest part, so if you want to go in deep with all available functions, please refer to its documentation, I’ll list all libraries at the end of this post)

private void  requestMultiplePermissions(){
    Dexter.withActivity(this)
          .withPermissions(
                    Manifest.permission.CAMERA,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    Manifest.permission.READ_EXTERNAL_STORAGE)
           .withListener(
               new MultiplePermissionsListener() {
                   @Override
                   public void onPermissionsChecked(MultiplePermissionsReport report) {
                       // check if all permissions are granted
                       if (report.areAllPermissionsGranted()) {
                           Toast.makeText(getApplicationContext(), 
                                       "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

                        }
                    }

                    @Override
                    public void onPermissionRationaleShouldBeShown(
                                   List<PermissionRequest> permissions, 
                                   PermissionToken token) {
                        token.continuePermissionRequest();
                    }
                }).
                withErrorListener(new PermissionRequestErrorListener() {
                    @Override
                    public void onError(DexterError error) {
                        Toast.makeText(getApplicationContext(), 
                                      "Some Error! ", 
                                      Toast.LENGTH_SHORT).show();
                    }
                })
          .onSameThread()
          .check();
}

We use the main object Dexter, telling them that we want permission on camera, in addition to write and read storage. It’s possible to implement a listener, there you can implement actions when the user deny the access or you want to show a permissions dialog to the user. Finally, when an error occurs, you can show a message linked to more routines.
The method we just created is going to be called inside onCreate method, important thing to note, you have to initialize the image view, otherwise you’ll get a null pointer exception.

And that’s it, now you have a full functional picture picker either taking it with the camera or selecting it from internal/external storage.

As I said, here’s the links to all libraries’ documentation.

Dexter
Glide
CircularImageView
Image Cropper
material.io

Here it is the link to the github project

Top comments (0)