avatarTom

Summary

This article provides a comprehensive guide on implementing a camera and gallery image chooser in an Android application using Kotlin and Jetpack Compose.

Abstract

The tutorial outlines the process of creating a feature within an Android app that allows users to select an image from either their device's camera or gallery. It begins by listing the prerequisites, such as having Android Studio installed and possessing basic Android development knowledge. The guide then delves into setting up the necessary permissions and dependencies in the AndroidManifest.xml file, including handling runtime permissions for external storage access and camera functionality. It also covers the creation of a file_paths.xml for file provider configuration. A key part of the tutorial is the implementation of an ImageUtils class in Kotlin, which encapsulates methods for capturing images, picking them from the gallery, and obtaining their paths. Finally, the article demonstrates how to integrate this utility class into a Jetpack Compose UI, providing a user interface for choosing or taking an image and displaying the selected image using Glide. The article concludes by encouraging customization of the UI and integration of the selected image into the application, while also promoting an AI service as a cost-effective alternative to ChatGPT Plus.

Opinions

  • The author assumes the reader has a basic understanding of Android development and familiarity with Android Studio.
  • The use of Jetpack Compose is recommended for building the user interface, indicating a preference for modern Android app development tools.
  • The article suggests that handling image selection from both the camera and gallery is a common and essential feature in Android apps.
  • The inclusion of a file provider is emphasized for sharing files across different applications securely.
  • The ImageUtils class is presented as a clean and reusable approach to abstracting image handling logic.
  • The tutorial encourages developers to explore and use AI services, specifically mentioning ZAI.chat as a more affordable option compared to ChatGPT Plus.

Building a Camera and Gallery Chooser in Android Using Kotlin and Jetpack Compose

Are you looking to implement a feature in your Android app that allows users to choose an image either from the camera or the gallery? In this tutorial, we’ll guide you through creating a Camera and Gallery Chooser using Kotlin and Jetpack Compose.

Prerequisites

Before we begin, ensure you have the following:

  • Android Studio installed on your machine.
  • Basic knowledge of Android development.

Setting Up Permissions and Dependencies

First, let’s set up the necessary permissions and dependencies in your AndroidManifest.xml file.

AndroidManifest.xml

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<uses-feature android:name="android.hardware.camera" android:required="false" />

<queries>
    <intent>
        <action android:name="android.media.action.IMAGE_CAPTURE" />
    </intent>
    <intent>
        <action android:name="android.intent.action.PICK" />
        <data android:mimeType="image/*" />
    </intent>
    <intent>
        <action android:name="android.intent.action.VIEW" />
    </intent>
</queries>

<application 
    android:requestLegacyExternalStorage="true"
    ...>
    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="${applicationId}.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>
</application>

file_paths.xml

Create a new XML file named file_paths.xml in the res/xml directory.

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

Implementing ImageUtils

Now, let’s create a utility class, ImageUtils, to handle image-related operations.

ImageUtils.kt

class ImageUtils(private val context: Context) {
    var currentPhotoPath: String? = null

    private fun createImageFile(): File {
        val timestamp = System.currentTimeMillis()
        val storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)

        return File.createTempFile(
            "JPEG_${timestamp}_",
            ".jpg",
            storageDir
        ).apply {
            currentPhotoPath = absolutePath
        }
    }

    private fun getImageCaptureIntent() = Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
        takePictureIntent.resolveActivity(context.packageManager)?.also {
            val photoFile: File? = try {
                createImageFile()
            } catch (ex: Exception) {
                null
            }

            photoFile?.also {
                val photoURI = FileProvider.getUriForFile(
                    context,
                    "${context.packageName}.fileprovider",
                    it
                )

                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
            }
        }
    }

    private fun getGalleryIntent() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R) >= 2) {
        Intent(MediaStore.ACTION_PICK_IMAGES)
    } else {
        Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
    }

    fun getPathFromGalleryUri(uri: Uri): String? {
        val projection = arrayOf(MediaStore.Images.Media.DATA)

        val cursor = context.contentResolver.query(uri, projection, null, null, null)

        cursor?.moveToFirst()

        val columnIndex = cursor?.getColumnIndex(projection[0])

        val path = columnIndex?.let { cursor.getString(it) }

        cursor?.close()

        return path
    }

    fun getIntent(): Intent {
        val captureIntent = getImageCaptureIntent()
        val galleryIntent = getGalleryIntent()

        val chooserIntent = Intent(Intent.ACTION_CHOOSER).apply {
            putExtra(Intent.EXTRA_INTENT, galleryIntent)
            putExtra(Intent.EXTRA_TITLE, "Select from:")
            putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(captureIntent))
        }

        return chooserIntent
    }
}

Utilizing the Chooser in Jetpack Compose

Now, let’s integrate our ImageUtils into a Jetpack Compose UI.

Your Composable

@Composable
fun CameraGalleryChooser() {
    val context = LocalContext.current

    val imageUtils = ImageUtils(context)

    var currentPhoto by remember {
        mutableStateOf<String?>(null)
    }

    val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
        if (it.resultCode == Activity.RESULT_OK) {
            val data = it.data?.data
            currentPhoto = if (data == null) {
                // Camera intent
                imageUtils.currentPhotoPath
            } else {
                // Gallery Pick Intent
                imageUtils.getPathFromGalleryUri(data)
            }
        }
    }
    
    Column {
      Button(
          onClick = { launcher.launch(imageUtils.getIntent()) }
      ) {
          Text(text = "Choose or take image")
      }

      if (currentPhoto != null) {
        // You can also use a normal Image by decoding the currentPhoto to a Bitmap using the BitmapFactory
        GlideImage(
            model = currentPhoto,
            contentDescription = null,
            modifier = Modifier
                .size(100.dp)
                .padding(16.dp)
        )
      }
    }   
}

@Composable
@Preview(showBackground = true)
fun CameraGalleryChooserPreview() {
    CameraGalleryChooser()
}

Feel free to customize the UI and integrate the selected image into your application according to your specific requirements. Happy coding!

Android
Kotlin
Image
Photos
Jetpack Compose
Recommended from ReadMedium