Taking pictures from camera or gallery is an essential feature for many applications those includes media in their apps. A simple notes app may need a profile picture to make the notes more personal. Getting a thumbnail image from camera is easy, but sometimes you want the full resolution image without storing it in gallery, crop it and avoid the possible memory exceptions.

In this tutorial we are going to learn building a simple social profile UI, choose the profile picture from camera or gallery with crop and image transformation features.

android-image-pick-and-crop-tutorial (1)

1. uCrop – cropping library

For cropping functionality, we are going to use uCrop library. This library is used many popular apps and tested on various devices / OS versions. Even though the library provides best cropping experience, it won’t provide an option to choose the input image from camera or gallery. All it takes is a bitmap and gives back the cropped bitmap.

In this article we use the same cropping library but on top of it, we’ll build a feature to pick the image from camera or gallery.

For information about uCrop, visit the official documentation.

2. Starting New Project / Profile Screen

Our goal is to build a simple social profile UI (like Instagram) and use the image cropping functionality to apply the profile image. You can take picture using camera or choose from gallery, crop and set it as profile image. So let’s start by creating a new project in Android Studio.

1. Create a new project in Android Studio from File β‡’ New Project and select Basic Activity from templates. I have given my package name as info.androidhive.imagepicker

2. Open app/build.gradle and add Dexter, ButterKnife, Glide, CircularImageView and uCrop dependencies.

dependencies {
    // ...

    //Butterknife
    implementation 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'

    //dexter permissions
    implementation "com.karumi:dexter:5.0.0"

    // circular imageview
    implementation 'com.mikhaellopez:circularimageview:3.2.0'

    //Glide
    implementation 'com.github.bumptech.glide:glide:4.7.1'
    implementation 'com.github.bumptech.glide:annotations:4.7.1'
    implementation('com.github.bumptech.glide:okhttp3-integration:4.0.0') {
        exclude group: 'glide-parent'
    }
    annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1'

    implementation 'com.github.yalantis:ucrop:2.2.2'
}

3. Add the below resources to respective strings.xml, dimen.xml and colors.xml files.

<resources>
    <string name="app_name">Image Pick &amp; Crop</string>
    <string name="action_settings">Settings</string>
    <string name="profile_desc">Rowan Sebastian Atkinson CBE is an English actor,  comedian and screenwriter</string>
    <string name="profile_title">Mr Bean</string>
    <string name="posts">posts</string>
    <string name="followers">followers</string>
    <string name="following">following</string>
    <string name="msg_error_unable_select_profile_pic">Unable to set profile image. Please try again!</string>
    <string name="lbl_set_profile_photo">Set profile image</string>
    <string name="lbl_take_camera_picture">Take a picture</string>
    <string name="lbl_choose_from_gallery">Choose from gallery</string>
    <string name="toast_image_intent_null">Image picker option is missing!</string>

    <!-- font families -->
    <string name="font_family_light">sans-serif-light</string>
    <string name="font_family_medium">sans-serif-medium</string>
    <string name="font_family_regular">sans-serif</string>
    <string name="font_family_condensed">sans-serif-condensed</string>
    <string name="font_family_black">sans-serif-black</string>
    <string name="font_family_thin">sans-serif-thin</string>
    <string name="dialog_permission_title">Grant Permissions</string>
    <string name="dialog_permission_message">This app needs permission to use this feature. You can grant them in app settings.</string>
    <string name="go_to_settings">GOTO SETTINGS</string>
</resources>
<resources>
    <dimen name="fab_margin">16dp</dimen>
    <dimen name="toolbar_profile_width">90dp</dimen>
    <dimen name="dimen_20dp">20dp</dimen>
    <dimen name="activity_padding">16dp</dimen>
    <dimen name="profile_title">23dp</dimen>
    <dimen name="profile_desc">13dp</dimen>
    <dimen name="meta_count">20dp</dimen>
    <dimen name="meta_label">12dp</dimen>
    <dimen name="dimen_40dp">40dp</dimen>
    <dimen name="ic_plus_width">28dp</dimen>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#5770f3</color>
    <color name="colorPrimaryDark">#5770f3</color>
    <color name="colorAccent">#D81B60</color>
    <color name="gradient_start">#5770f3</color>
    <color name="gradient_end">#8e5aeb</color>
    <color name="profile_desc">#D1D1FF</color>
    <color name="bg_meta_container">#000000</color>
    <color name="profile_default_tint">#e0e0e0</color>
</resources>

4. Download this res folder and add the contents to your project’s res folder. This folder contains necessary icons needed to build the profile screen.

5. Open the layout file your main activity (activity_main.xml) and add the below code to achieve the profile layout.

<?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=".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" />

        <include layout="@layout/layout_toolbar_profile" />

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

    <include layout="@layout/content_main" />

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

Create a new xml layout layout_toolbar_profile.xml and add the below code.

<?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:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="@dimen/toolbar_profile_width"
        android:layout_height="wrap_content">

        <com.mikhaellopez.circularimageview.CircularImageView
            android:id="@+id/img_profile"
            android:layout_width="@dimen/toolbar_profile_width"
            android:layout_height="@dimen/toolbar_profile_width"
            android:layout_marginTop="@dimen/activity_padding"
            android:layout_marginBottom="@dimen/activity_padding"
            android:scaleType="centerInside"
            android:src="@drawable/baseline_account_circle_black_48"
            app:civ_border_color="@android:color/white"
            app:civ_border_width="2dp" />

        <com.mikhaellopez.circularimageview.CircularImageView
            android:id="@+id/img_plus"
            android:layout_width="@dimen/ic_plus_width"
            android:layout_height="@dimen/ic_plus_width"
            android:layout_alignBottom="@id/img_profile"
            android:layout_alignParentRight="true"
            android:src="@drawable/ic_plus"
            app:civ_shadow="true"
            app:civ_shadow_radius="1" />
    </RelativeLayout>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:fontFamily="@string/font_family_medium"
        android:text="@string/profile_title"
        android:textColor="@android:color/white"
        android:textSize="@dimen/profile_title" />

    <TextView
        android:id="@+id/profile_desc"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="@dimen/dimen_40dp"
        android:gravity="center_horizontal"
        android:paddingLeft="@dimen/dimen_20dp"
        android:paddingRight="@dimen/dimen_20dp"
        android:text="@string/profile_desc"
        android:textColor="@color/profile_desc"
        android:textSize="@dimen/profile_desc" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/bg_meta_container"
        android:orientation="horizontal"
        android:paddingTop="@dimen/activity_padding"
        android:paddingBottom="@dimen/activity_padding"
        android:weightSum="3">

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="1320"
                android:textColor="@android:color/white"
                android:textSize="@dimen/meta_count"
                android:textStyle="bold" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:fontFamily="@string/font_family_condensed"
                android:text="@string/posts"
                android:textColor="@android:color/white"
                android:textSize="@dimen/meta_label" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="4.3m"
                android:textColor="@android:color/white"
                android:textSize="@dimen/meta_count"
                android:textStyle="bold" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:fontFamily="@string/font_family_condensed"
                android:text="@string/followers"
                android:textColor="@android:color/white"
                android:textSize="@dimen/meta_label" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="123"
                android:textColor="@android:color/white"
                android:textSize="@dimen/meta_count"
                android:textStyle="bold" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:fontFamily="@string/font_family_condensed"
                android:text="@string/following"
                android:textColor="@android:color/white"
                android:textSize="@dimen/meta_label" />
        </LinearLayout>
    </LinearLayout>

</LinearLayout>

Now if you run your project, you should able to see the screen as below.

android-social-profile-like-instagram-min

3. Adding Image Pick and Crop functionality

Now as the UI part is done, let’s see how to add the image picking functionality on tapping the profile image or plus icon.

6. Create an xml file named file_paths.xml under res β‡’ xml folder. If you don’t see xml folder under res, create a new folder with the same name. Here we are defining a FileProvider path to store the camera images in a cached location instead of storing them in gallery.

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-cache-path
        name="cache"
        path="camera" />
</paths>

7. Open AndroidManifest.xml and do the below changes.

> Add INTERNET, CAMERA and STORAGE permissions.

> Add UCropActivity intent to launch the crop activity.

> Add FileProvider information using the xml we have defined in the above step.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="info.androidhive.imagepicker">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        tools:ignore="GoogleAppIndexingWarning">
        <activity android:name=".ImagePickerActivity" />
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:screenOrientation="portrait"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- uCrop cropping activity -->
        <activity
            android:name="com.yalantis.ucrop.UCropActivity"
            android:screenOrientation="portrait"
            android:theme="@style/AppTheme.NoActionBar" />

        <!-- cache directory file provider paths -->
        <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/file_paths" />
        </provider>
    </application>

</manifest>

8. As we are using Glide to display the image, create a class named MyGlideModule and annotate the class with @GlideModule.

package info.androidhive.imagepicker;

import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;

@GlideModule
public class MyGlideModule extends AppGlideModule {
}

9. To reduce the complexity, I have written an activity that takes care of choosing the image and cropping. All you have to do is, add this activity to your project and call couple of lines to launch the activity. That’s all.

Create a blank activity ImagePickerActivity.java and add the below code.

package info.androidhive.imagepicker;

import android.Manifest;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;

import com.karumi.dexter.Dexter;
import com.karumi.dexter.MultiplePermissionsReport;
import com.karumi.dexter.PermissionToken;
import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.multi.MultiplePermissionsListener;
import com.yalantis.ucrop.UCrop;

import java.io.File;
import java.util.List;

import static android.support.v4.content.FileProvider.getUriForFile;

public class ImagePickerActivity extends AppCompatActivity {
    private static final String TAG = ImagePickerActivity.class.getSimpleName();
    public static final String INTENT_IMAGE_PICKER_OPTION = "image_picker_option";
    public static final String INTENT_ASPECT_RATIO_X = "aspect_ratio_x";
    public static final String INTENT_ASPECT_RATIO_Y = "aspect_ratio_Y";
    public static final String INTENT_LOCK_ASPECT_RATIO = "lock_aspect_ratio";
    public static final String INTENT_IMAGE_COMPRESSION_QUALITY = "compression_quality";
    public static final String INTENT_SET_BITMAP_MAX_WIDTH_HEIGHT = "set_bitmap_max_width_height";
    public static final String INTENT_BITMAP_MAX_WIDTH = "max_width";
    public static final String INTENT_BITMAP_MAX_HEIGHT = "max_height";


    public static final int REQUEST_IMAGE_CAPTURE = 0;
    public static final int REQUEST_GALLERY_IMAGE = 1;

    private boolean lockAspectRatio = false, setBitmapMaxWidthHeight = false;
    private int ASPECT_RATIO_X = 16, ASPECT_RATIO_Y = 9, bitmapMaxWidth = 1000, bitmapMaxHeight = 1000;
    private int IMAGE_COMPRESSION = 80;
    public static String fileName;

    public interface PickerOptionListener {
        void onTakeCameraSelected();

        void onChooseGallerySelected();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_image_picker);

        Intent intent = getIntent();
        if (intent == null) {
            Toast.makeText(getApplicationContext(), getString(R.string.toast_image_intent_null), Toast.LENGTH_LONG).show();
            return;
        }

        ASPECT_RATIO_X = intent.getIntExtra(INTENT_ASPECT_RATIO_X, ASPECT_RATIO_X);
        ASPECT_RATIO_Y = intent.getIntExtra(INTENT_ASPECT_RATIO_Y, ASPECT_RATIO_Y);
        IMAGE_COMPRESSION = intent.getIntExtra(INTENT_IMAGE_COMPRESSION_QUALITY, IMAGE_COMPRESSION);
        lockAspectRatio = intent.getBooleanExtra(INTENT_LOCK_ASPECT_RATIO, false);
        setBitmapMaxWidthHeight = intent.getBooleanExtra(INTENT_SET_BITMAP_MAX_WIDTH_HEIGHT, false);
        bitmapMaxWidth = intent.getIntExtra(INTENT_BITMAP_MAX_WIDTH, bitmapMaxWidth);
        bitmapMaxHeight = intent.getIntExtra(INTENT_BITMAP_MAX_HEIGHT, bitmapMaxHeight);

        int requestCode = intent.getIntExtra(INTENT_IMAGE_PICKER_OPTION, -1);
        if (requestCode == REQUEST_IMAGE_CAPTURE) {
            takeCameraImage();
        } else {
            chooseImageFromGallery();
        }
    }

    public static void showImagePickerOptions(Context context, PickerOptionListener listener) {
        // setup the alert builder
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(context.getString(R.string.lbl_set_profile_photo));

        // add a list
        String[] animals = {context.getString(R.string.lbl_take_camera_picture), context.getString(R.string.lbl_choose_from_gallery)};
        builder.setItems(animals, (dialog, which) -> {
            switch (which) {
                case 0:
                    listener.onTakeCameraSelected();
                    break;
                case 1:
                    listener.onChooseGallerySelected();
                    break;
            }
        });

        // create and show the alert dialog
        AlertDialog dialog = builder.create();
        dialog.show();
    }

    private void takeCameraImage() {
        Dexter.withActivity(this)
                .withPermissions(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                .withListener(new MultiplePermissionsListener() {
                    @Override
                    public void onPermissionsChecked(MultiplePermissionsReport report) {
                        if (report.areAllPermissionsGranted()) {
                            fileName = System.currentTimeMillis() + ".jpg";
                            Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, getCacheImagePath(fileName));
                            if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
                                startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
                            }
                        }
                    }

                    @Override
                    public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) {
                        token.continuePermissionRequest();
                    }
                }).check();
    }

    private void chooseImageFromGallery() {
        Dexter.withActivity(this)
                .withPermissions(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                .withListener(new MultiplePermissionsListener() {
                    @Override
                    public void onPermissionsChecked(MultiplePermissionsReport report) {
                        if (report.areAllPermissionsGranted()) {
                            Intent pickPhoto = new Intent(Intent.ACTION_PICK,
                                    android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                            startActivityForResult(pickPhoto, REQUEST_GALLERY_IMAGE);
                        }
                    }

                    @Override
                    public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) {
                        token.continuePermissionRequest();
                    }
                }).check();

    }

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case REQUEST_IMAGE_CAPTURE:
                if (resultCode == RESULT_OK) {
                    cropImage(getCacheImagePath(fileName));
                } else {
                    setResultCancelled();
                }
                break;
            case REQUEST_GALLERY_IMAGE:
                if (resultCode == RESULT_OK) {
                    Uri imageUri = data.getData();
                    cropImage(imageUri);
                } else {
                    setResultCancelled();
                }
                break;
            case UCrop.REQUEST_CROP:
                if (resultCode == RESULT_OK) {
                    handleUCropResult(data);
                } else {
                    setResultCancelled();
                }
                break;
            case UCrop.RESULT_ERROR:
                final Throwable cropError = UCrop.getError(data);
                Log.e(TAG, "Crop error: " + cropError);
                setResultCancelled();
                break;
            default:
                setResultCancelled();
        }
    }

    private void cropImage(Uri sourceUri) {
        Uri destinationUri = Uri.fromFile(new File(getCacheDir(), queryName(getContentResolver(), sourceUri)));
        UCrop.Options options = new UCrop.Options();
        options.setCompressionQuality(IMAGE_COMPRESSION);
        options.setToolbarColor(ContextCompat.getColor(this, R.color.colorPrimary));
        options.setStatusBarColor(ContextCompat.getColor(this, R.color.colorPrimary));
        options.setActiveWidgetColor(ContextCompat.getColor(this, R.color.colorPrimary));

        if (lockAspectRatio)
            options.withAspectRatio(ASPECT_RATIO_X, ASPECT_RATIO_Y);

        if (setBitmapMaxWidthHeight)
            options.withMaxResultSize(bitmapMaxWidth, bitmapMaxHeight);

        UCrop.of(sourceUri, destinationUri)
                .withOptions(options)
                .start(this);
    }

    private void handleUCropResult(Intent data) {
        if (data == null) {
            setResultCancelled();
            return;
        }
        final Uri resultUri = UCrop.getOutput(data);
        setResultOk(resultUri);
    }

    private void setResultOk(Uri imagePath) {
        Intent intent = new Intent();
        intent.putExtra("path", imagePath);
        setResult(Activity.RESULT_OK, intent);
        finish();
    }

    private void setResultCancelled() {
        Intent intent = new Intent();
        setResult(Activity.RESULT_CANCELED, intent);
        finish();
    }

    private Uri getCacheImagePath(String fileName) {
        File path = new File(getExternalCacheDir(), "camera");
        if (!path.exists()) path.mkdirs();
        File image = new File(path, fileName);
        return getUriForFile(ImagePickerActivity.this, getPackageName() + ".provider", image);
    }

    private static String queryName(ContentResolver resolver, Uri uri) {
        Cursor returnCursor =
                resolver.query(uri, null, null, null, null);
        assert returnCursor != null;
        int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
        returnCursor.moveToFirst();
        String name = returnCursor.getString(nameIndex);
        returnCursor.close();
        return name;
    }

    /**
     * Calling this will delete the images from cache directory
     * useful to clear some memory
     */
    public static void clearCache(Context context) {
        File path = new File(context.getExternalCacheDir(), "camera");
        if (path.exists() && path.isDirectory()) {
            for (File child : path.listFiles()) {
                child.delete();
            }
        }
    }
}

3.1 Launching copping activity

To show the image picking choices, call ImagePickerActivity.showImagePickerOptions() method.

ImagePickerActivity.showImagePickerOptions(this, new ImagePickerActivity.PickerOptionListener() {
            @Override
            public void onTakeCameraSelected() {
                // launchCameraIntent();
            }

            @Override
            public void onChooseGallerySelected() {
                // launchGalleryIntent();
            }
        });

Once, an option is selected, you can pass Intent data depending on the choice. For example, to pick the image from gallery with 1×1 aspect ratio, the below intent can be used.

Intent intent = new Intent(MainActivity.this, ImagePickerActivity.class);
intent.putExtra(ImagePickerActivity.INTENT_IMAGE_PICKER_OPTION, ImagePickerActivity.REQUEST_IMAGE_CAPTURE);

// setting aspect ratio
intent.putExtra(ImagePickerActivity.INTENT_LOCK_ASPECT_RATIO, true);
intent.putExtra(ImagePickerActivity.INTENT_ASPECT_RATIO_X, 1); // 16x9, 1x1, 3:4, 3:2
intent.putExtra(ImagePickerActivity.INTENT_ASPECT_RATIO_Y, 1);

startActivityForResult(intent, REQUEST_IMAGE);

10. Now we’ll see how this can be applied to our profile activity. Open MainActivity.java and call image picker activity on tapping the profile image or plus icon.

package info.androidhive.imagepicker;

import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.widget.ImageView;

import com.karumi.dexter.Dexter;
import com.karumi.dexter.MultiplePermissionsReport;
import com.karumi.dexter.PermissionToken;
import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.multi.MultiplePermissionsListener;

import java.io.IOException;
import java.util.List;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();
    public static final int REQUEST_IMAGE = 100;

    @BindView(R.id.img_profile)
    ImageView imgProfile;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        getSupportActionBar().setTitle(null);

        loadProfileDefault();

        // Clearing older images from cache directory
        // don't call this line if you want to choose multiple images in the same activity
        // call this once the bitmap(s) usage is over
        ImagePickerActivity.clearCache(this);
    }

    private void loadProfile(String url) {
        Log.d(TAG, "Image cache path: " + url);

        GlideApp.with(this).load(url)
                .into(imgProfile);
        imgProfile.setColorFilter(ContextCompat.getColor(this, android.R.color.transparent));
    }

    private void loadProfileDefault() {
        GlideApp.with(this).load(R.drawable.baseline_account_circle_black_48)
                .into(imgProfile);
        imgProfile.setColorFilter(ContextCompat.getColor(this, R.color.profile_default_tint));
    }

    @OnClick({R.id.img_plus, R.id.img_profile})
    void onProfileImageClick() {
        Dexter.withActivity(this)
                .withPermissions(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                .withListener(new MultiplePermissionsListener() {
                    @Override
                    public void onPermissionsChecked(MultiplePermissionsReport report) {
                        if (report.areAllPermissionsGranted()) {
                            showImagePickerOptions();
                        }

                        if (report.isAnyPermissionPermanentlyDenied()) {
                            showSettingsDialog();
                        }
                    }

                    @Override
                    public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) {
                        token.continuePermissionRequest();
                    }
                }).check();
    }

    private void showImagePickerOptions() {
        ImagePickerActivity.showImagePickerOptions(this, new ImagePickerActivity.PickerOptionListener() {
            @Override
            public void onTakeCameraSelected() {
                launchCameraIntent();
            }

            @Override
            public void onChooseGallerySelected() {
                launchGalleryIntent();
            }
        });
    }

    private void launchCameraIntent() {
        Intent intent = new Intent(MainActivity.this, ImagePickerActivity.class);
        intent.putExtra(ImagePickerActivity.INTENT_IMAGE_PICKER_OPTION, ImagePickerActivity.REQUEST_IMAGE_CAPTURE);

        // setting aspect ratio
        intent.putExtra(ImagePickerActivity.INTENT_LOCK_ASPECT_RATIO, true);
        intent.putExtra(ImagePickerActivity.INTENT_ASPECT_RATIO_X, 1); // 16x9, 1x1, 3:4, 3:2
        intent.putExtra(ImagePickerActivity.INTENT_ASPECT_RATIO_Y, 1);

        // setting maximum bitmap width and height
        intent.putExtra(ImagePickerActivity.INTENT_SET_BITMAP_MAX_WIDTH_HEIGHT, true);
        intent.putExtra(ImagePickerActivity.INTENT_BITMAP_MAX_WIDTH, 1000);
        intent.putExtra(ImagePickerActivity.INTENT_BITMAP_MAX_HEIGHT, 1000);

        startActivityForResult(intent, REQUEST_IMAGE);
    }

    private void launchGalleryIntent() {
        Intent intent = new Intent(MainActivity.this, ImagePickerActivity.class);
        intent.putExtra(ImagePickerActivity.INTENT_IMAGE_PICKER_OPTION, ImagePickerActivity.REQUEST_GALLERY_IMAGE);

        // setting aspect ratio
        intent.putExtra(ImagePickerActivity.INTENT_LOCK_ASPECT_RATIO, true);
        intent.putExtra(ImagePickerActivity.INTENT_ASPECT_RATIO_X, 1); // 16x9, 1x1, 3:4, 3:2
        intent.putExtra(ImagePickerActivity.INTENT_ASPECT_RATIO_Y, 1);
        startActivityForResult(intent, REQUEST_IMAGE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if (requestCode == REQUEST_IMAGE) {
            if (resultCode == Activity.RESULT_OK) {
                Uri uri = data.getParcelableExtra("path");
                try {
                    // You can update this bitmap to your server
                    Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri);

                    // loading profile image from local cache
                    loadProfile(uri.toString());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * Showing Alert Dialog with Settings option
     * Navigates user to app settings
     * NOTE: Keep proper title and message depending on your app
     */
    private void showSettingsDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
        builder.setTitle(getString(R.string.dialog_permission_title));
        builder.setMessage(getString(R.string.dialog_permission_message));
        builder.setPositiveButton(getString(R.string.go_to_settings), (dialog, which) -> {
            dialog.cancel();
            openSettings();
        });
        builder.setNegativeButton(getString(android.R.string.cancel), (dialog, which) -> dialog.cancel());
        builder.show();

    }

    // navigating user to app settings
    private void openSettings() {
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        Uri uri = Uri.fromParts("package", getPackageName(), null);
        intent.setData(uri);
        startActivityForResult(intent, 101);
    }
}

Now run and test the app. You should be able to set the profile image from camera or gallery.

android-image-from-gallery-or-camera-with-crop-min

If you have any queries or suggestions, please do post in the comment section below.

Happy Coding πŸ™‚

Hi there! I am Founder at androidhive and programming enthusiast. My skills includes Android, iOS, PHP, Ruby on Rails and lot more. If you have any idea that you would want me to develop? Let’s talk: ravi@androidhive.info
  • Tigani Mohammed

    awesome like always thanks…..what about android jetpack any plan for it??

  • Pratik

    Hi,
    Can you put up a tutorial on EXO Player?

    Thank you in advance

  • Zakaria Rouf

    Great article.

  • Renan Bolonha

    @ravi8x:disqus Can u make a post about new DESIGN APP (material design 2.0) ? How use, make… ? Sorry for publish this question here

  • anshuman pattnaik

    nice tutorial

  • Ibrahim Samad

    Awesome as always πŸ™‚

  • Zaid Ali

    hello sir, what/where is

    activity_image_picker xml ??

  • yhab shaker

    Thank you
    but when I capture a photo from the camera I get this message :Unfortunately camera has stopped

    • Pls check the LogCat for the errors.

      • yhab shaker

        Could not find class ‘android.support.v4.view.ViewCompat$OnUnhandledKeyEventListenerWrapper’, referenced from method android.support.v4.view.ViewCompat.addOnUnhandledKeyEventListener

        Could not find class ‘android.view.WindowInsets’, referenced from method android.support.v4.view.ViewCompat.dispatchApplyWindowInsets

        Could not find class ‘android.view.WindowInsets’, referenced from method android.support.v4.view.ViewCompat.onApplyWindowInsets

        Could not find class ‘android.view.View$OnUnhandledKeyEventListener’, referenced from method android.support.v4.view.ViewCompat.removeOnUnhandledKeyEventListener

        Could not find class ‘android.support.v4.view.ViewCompat$1’, referenced from method android.support.v4.view.ViewCompat.setOnApplyWindowInsetsListener

        Could not find class ‘android.graphics.drawable.RippleDrawable’, referenced from method android.support.v7.widget.AppCompatImageHelper.hasOverlappingRendering

        Could not find class ‘android.app.AppOpsManager’, referenced from method android.support.v4.app.AppOpsManagerCompat.noteOp

        Could not find class ‘android.app.AppOpsManager’, referenced from method android.support.v4.app.AppOpsManagerCompat.noteOpNoThrow

        Could not find class ‘android.app.AppOpsManager’, referenced from method android.support.v4.app.AppOpsManagerCompat.noteProxyOp

        Could not find class ‘android.app.AppOpsManager’, referenced from method android.support.v4.app.AppOpsManagerCompat.noteProxyOpNoThrow

        • Try File -> Invalidate Cache & Restart option from Android Studio. The APK might not be generated properly.

          • yhab shaker

            The error still exists but thank you

          • Okay.

  • Abhishek Pandya

    https://uploads.disquscdn.com/images/cdfad1ec45579af4efa14d9c7b1cfab416d4903644382b4d4465112b2b841712.jpg

    I have this problem can you help me with this? I didn’t get the resource for GlideApp. I just copy the whole code as you said.

    But when I download your code it worked properly.

  • Fabio

    Hi Ravi:
    I have this error: IOException: file:/data/user/0/com.ingeniapps.dicmax/cache/1552502359577.jpg (No such file or directory)

    Help!

  • Holga balakov

    RAVI CHANGED MY LIFE I WENT FRON NULL TO DEVELOPPING REAL WORLD APPS BECAUSE OF YOU. SALUTE SIR

  • Zaid Ali

    sir some images from internal storage cant be selected using this library while some are selected it didnt even print logs to figure our error.

    for example there is folder in my internal storage:

    Internal StorageAmigoScreenLockFavorite

    and other paths also from internal storage from where image cant be selected it show preview and all but after crop and selecting image it just getting nothing even logs cant be printed.. so plz help me i used this for real world app plz sir…..

    • Okay. It might be the issues with Crop library I am using. Could you check library issues tab on Github and see you can find anything related.

      • Zaid Ali

        i couldn’t find any solution on github page of uCrop library sir…

        • I need to search and find the solution. Right now no idea πŸ™

          • Zaid Ali

            okay sir np whenever you find any solution plz let us know…. Thank you for kind support.

          • Sure.

  • Mohammed Imran

    After cropping the image when i click on the tick icon, my app crashes saying in log “File exists”

  • Gionata Stante

    Really Nice Guide but, if I want to store the image in internal folder of app and then show it every time I reopen the app? Using internal storage and not cache. How much code I need to change (referred to this guide) to do this? Thanks!!

  • Denny Kurniawan

    how to save image to Internal storage instead of cache?

  • Yerico Ezeta

    Thank you very much , Ravi.!!!

  • Ss

    Really nice,but i am facing the issue that, when i am selecting the image from gallery and setting on imageview,it’ working but when again i am selecting the same image from gallery, it’s not setting on imageview.

    • I see the problem. Have you called clearCache() function once the image is used? or may be before start the crop activity.

      • Ss

        Yes i have done that.

    • Raja Muhammad Kamal (HQ – IT A

      i may be have same problem here. I can choose pic from gallery and set to profile imageview. but it gone when i press android button back, it will gone.. Do you know what is my problem

  • Raja Muhammad Kamal (HQ – IT A
    • Raja Muhammad Kamal (HQ – IT A

      i use androidx

    • Try like this

      //Glide
      implementation ‘com.github.bumptech.glide:glide:4.9.0’
      implementation ‘com.github.bumptech.glide:annotations:4.9.0’
      implementation(‘com.github.bumptech.glide:okhttp3-integration:4.0.0’) {
      exclude group: ‘glide-parent’
      }
      annotationProcessor ‘com.github.bumptech.glide:compiler:4.9.0’

  • Rupesh Kumar

    Sir its working great… kindly please tell me how to change the dimensions of box….in terms of length breadth so that we can have fixed dimensions image from user …its urgent help please!!

  • Milind Choudhary

    I’m getting this error at the time of project build. RequestOptions cannot be converted to GlideOptions.

    https://uploads.disquscdn.com/images/bc4aa75dbd858fb3177afa1aeafa4cb3f6edd11a1e0e1953bbf5402b86d11c2c.png

  • Yes, it is really amazing post, new of uCrop library in image and we can do in image to pick the image from gallery with 1Γ—1 aspect ratio as same.

  • AimΓ© Sagbo

    Hi ! Great Post ! It help me a lot, but i’m facing an issue with the cropped image : When i choose an image from galery ans crop it, i successfully get it on the imageView, but when i choose the same picture and crop it again, it always display the previous cropped image , can i have some help ?

    • PARMAR ANIRUDDHASINH

      /**
      *

      The below line between Start and End is added to distinct if user select same image to crop than each time same image name replace with random number with .jpg to to
      * overcome issue of same previously cropped image set. if you comment below line between Start and End than check for selecting same image with cropping different portion of image and result will be
      * previously cropped image rather that current crop image. this is issue in Library
      */

      /// Start

      int random = new Random().nextInt();

      String destURI = null;

      if(destinationUri.toString().contains(“jpg”)) {
      String str = random + “.jpg”;
      destURI = destinationUri.toString().replace(“.jpg”, str);
      }
      else if(destinationUri.toString().contains(“png”)) {
      String str = random + “.png”;
      destURI = destinationUri.toString().replace(“.png”, str);
      }

      destinationUri = Uri.parse(destURI);

      /// End

      • This also solves the problem where by the camera image selection works fine but pick from gallery doesn’t work. Worked fine for me. Thank you.

  • |/|@Β₯@|||€

    Hi! I am using your code thats working fine but when I am adding code for selecting the file from file manager that file could be of any type , the code is working in all version but when i run in Android pie it crashed , I could not understand where I am doing wrong, Please suggest the working code for this

  • Spandan Joshi

    Hey Ravi, this was a very good tutorial. Can you please share how to add different ASPECT RATIO options?

    • The different aspect ration are on the comments where he has set to 1 x 1

  • Bhashkar Poddar

    Thank you So Much..It was Wonderful tutorial..!

  • Jaydeep Bhayani

    /data/user/0/com.anetossoftware.nasitparivar/cache/Screenshot_20191023-010431.png (No such file or directory) I want to upload this image to server. How can I do that

  • Alvaro Kadja

    How to save in dataabase after take picture?

  • nellysade lumma

    Thanks for the tutorial it works very well on my app. The thing is where can i get the image set by user. i want to send it to a server app via retrofit.

    • Rahul Pandey

      how to crop gallery image

  • Dharmesh Patel

    Hello Sir,
    Please create demo of compass with camera view.

  • Rahul Pandey

    Not image crop from gallery

  • Rahul Pandey

    Not getting bitmap from uri