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.
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 & 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.
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.
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
awesome like always thanks…..what about android jetpack any plan for it??
I don’t planned yet.
Hi,
Can you put up a tutorial on EXO Player?
Thank you in advance
Good idea. Will write one.
Great article.
Thank You.
@Ravi Tamada:disqus Can u make a post about new DESIGN APP (material design 2.0) ? How use, make… ? Sorry for publish this question here
Could you explain a bit about your requirement? You mean the design or development?
Design sir
You can use Sketch or Adobe XD to design the layouts.
Here is one article on Sketchapp. Same applies to adobe XD also.
https://www.androidhive.info/2018/01/android-app-ui-designing-using-sketch-app-and-zeplin/
Thanks sir, how about development?
That you have to learn the Material components related info and the write the code manually.
nice tutorial
Thank You:)
Awesome as always 🙂
Thank you 🙂
hello sir, what/where is
activity_image_picker xml ??
It’s just a blank activity. You can get the code from here.
https://github.com/ravi8x/Android-Image-Picker-and-Cropping/blob/master/app/src/main/res/layout/activity_image_picker.xml
thank u sir its really really awesome as always tutorial and u helped me a lot while learning android. Thank you sir…
You are welcome Zaid 🙂
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.
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.
The error still exists but thank you
Okay.
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.
Okay. Pls include this class in your project.
https://github.com/ravi8x/Android-Image-Picker-and-Cropping/blob/master/app/src/main/java/info/androidhive/imagepicker/MyGlideModule.java
problem solved by clean and rebuild the project. Thank You for your help and I MUST SAY your articles are great. I learned a lot from that.
Great. Here are the solutions you can try if you ever face this problem again.
1. Build -> Clean, Rebuild.
2. Build -> Clean, Make Project
3. Final step is to File -> Invalidate Cache & Restart.
hello, I’m having this problem too
I tried all these solutions
and nothing worked
what can I do ?
Hi Ravi:
I have this error: IOException: file:/data/user/0/com.ingeniapps.dicmax/cache/1552502359577.jpg (No such file or directory)
Help!
RAVI CHANGED MY LIFE I WENT FRON NULL TO DEVELOPPING REAL WORLD APPS BECAUSE OF YOU. SALUTE SIR
Thanks Holga 🙂
All the best!
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.
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 🙁
okay sir np whenever you find any solution plz let us know…. Thank you for kind support.
Sure.
After cropping the image when i click on the tick icon, my app crashes saying in log “File exists”
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!!
how to save image to Internal storage instead of cache?
Thank you very much , Ravi.!!!
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.
Yes i have done that.
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
I have error on this 5 files..
this five is uneditable files.
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’
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!!
I’m getting this error at the time of project build. RequestOptions cannot be converted to GlideOptions.

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.
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 ?
/**
*
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
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
Thank you So Much..It was Wonderful tutorial..!
/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
How to save in dataabase after take picture?
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.
how to crop gallery image
Hello Sir,
Please create demo of compass with camera view.
Not image crop from gallery
Me too
Not getting bitmap from uri
Me too….
No crop from gallery… it exits
Hey, First of all thanks for the amazing tutorial.
How can this be modified to pick multiple Images from the gallery. can you please point me in the right direction?
I am getting File not found exception. Can you please help me why am i getting it.
image clear cache not working
it displays old images forever
Print some logs and see if there are errors while deleting them.
content.FileProvider error in manifest file
use this —> android:name=”androidx.core.content.FileProvider”
Hi there! Great tutorial! I have a question though, would you say that your tutorial respects the MVC pattern, if yes, can you explain to me how? I do’t think it does as I cannot see an actual controller…
Hello Mr Ravi , first of all thanks a million for your great tutorials, then I’ve got a small question , I’ll be thankful if you answer it, here it is : i wanted to compress the cropped image with zetbaitsu Compressor then upload it to my server , would you please help me with this ? just compress the cropped image using this library and for uploading i know you have another great tutorial and i will use that , Just Compressing part
thanks a lot Dear Ravi
form gallery use this given code not working…