How to Build a Photo Viewing Application in Android?
Last Updated :
27 Mar, 2025
Gallery app is one of the most used apps which comes pre-installed on many Android devices and there are several different apps that are present in Google Play to view the media files present in your device. In this article, we will be simply creating a Gallery app in which we can view all the photos which we have stored on our device. Along with that, we can view individual photos in our app as well.
What we are going to build in this article?
We will be building a simple application in which we will be simply displaying the list of photos in the grid format and on clicking on the photo we can view that photo and can zoom in the photo to view it properly.
Important: This project cannot be run from Android 13+ since the permission READ_EXTERNAL_STORAGE is deprecated.
Step by Step Implementation
Step 1: Create a New Project
To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio.
Note that select Java as the programming language.
Step 2: Add the dependency for image loading
Navigate to the Gradle Scripts > build.gradle.kts (Module :app) and add the below dependency to it. We are using Glide for loading images from paths in our ImageView.
dependencies {
...
implementation ("com.github.bumptech.glide:glide:4.16.0")
}
Now sync your project.
Step 3: Adding permissions to read external storage
Navigate to the app > manifests > AndroidManifest.xml file and add the below permissions to it.
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
As we are loading all the images from our storage at a time so we have to add 2 attributes to our application tag in the AndroidManifest.xml file. Navigate to the AndroidManifest.xml file and add below two lines in your application tag of Manifest file.
<application
...
android:hardwareAccelerated="false"
android:largeHeap="true"
...
</application>
Step 4: Working with the layout files
Navigate to the app > res > layout > activity_main.xml and add the below code to that file. Now, create a new layout for each item of the recycler view and add the below code to it.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<!--recycler view for displaying the list of images-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
item_rv.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="3dp"
android:elevation="8dp"
app:cardCornerRadius="8dp">
<!--Image view for displaying the image
in our card layout in recycler view-->
<ImageView
android:id="@+id/idIVImage"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:scaleType="centerCrop" />
</androidx.cardview.widget.CardView>
Step 5: Creating a new activity for displaying a single image
Navigate to the app > java > {package-name}, Right-click on it, New > Empty Views Activity and name your activity as ImageDetailActivity and create a new activity. We will be using this activity to display our single image from the list of different images.
ImageDetailActivity.java
package org.geeksforgeeks.demo;
import android.os.Bundle;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.bumptech.glide.Glide;
import java.io.File;
public class ImageDetailActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_image_detail);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
String imagePath = getIntent().getStringExtra("imgPath");
if (imagePath != null) {
ImageView imageView = findViewById(R.id.image);
Glide.with(this).load(new File(imagePath)).into(imageView);
}
}
}
ImageDetailActivity.kt
package org.geeksforgeeks.demo
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.bumptech.glide.Glide
import java.io.File
class ImageDetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_image_detail)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
val imagePath = intent.getStringExtra("imgPath")
Glide.with(this).load(File(imagePath!!)).into(findViewById(R.id.image))
}
}
activity_image_detail.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ImageDetailActivity">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.5" />
</androidx.constraintlayout.widget.ConstraintLayout>
Step 6: Creating an adapter class for the RecyclerView
Navigate to the app > java > {package-name}, Right-click on it, New Java/Kotlin class and name your class as Adapter and add the below code to it.
Adapter File:
Adapter.java
package org.geeksforgeeks.demo;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import java.io.File;
import java.util.ArrayList;
public class Adapter extends RecyclerView.Adapter<Adapter.RecyclerViewHolder> {
private final Context context;
private final ArrayList<String> imagePathArrayList;
public Adapter(Context context, ArrayList<String> imagePathArrayList) {
this.context = context;
this.imagePathArrayList = imagePathArrayList;
}
public static class RecyclerViewHolder extends RecyclerView.ViewHolder {
private final ImageView imageIV;
public RecyclerViewHolder(@NonNull View itemView) {
super(itemView);
imageIV = itemView.findViewById(R.id.idIVImage);
}
}
@NonNull
@Override
public RecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_rv, parent, false);
return new RecyclerViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerViewHolder holder, int position) {
File imgFile = new File(imagePathArrayList.get(position));
if (imgFile.exists()) {
Glide.with(context).load(imgFile).into(holder.imageIV);
holder.itemView.setOnClickListener(v -> {
Intent intent = new Intent(context, ImageDetailActivity.class);
intent.putExtra("imgPath", imagePathArrayList.get(position));
context.startActivity(intent);
});
}
}
@Override
public int getItemCount() {
return imagePathArrayList.size();
}
}
Adapter.kt
package org.geeksforgeeks.demo
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import java.io.File
class Adapter (
private val context: Context,
private val imagePathArrayList: ArrayList<String>
) : RecyclerView.Adapter<Adapter.RecyclerViewHolder>() {
inner class RecyclerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
internal val imageIV: ImageView = itemView.findViewById<ImageView>(R.id.idIVImage)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerViewHolder {
val view: View = LayoutInflater.from(parent.context)
.inflate(R.layout.item_rv, parent, false)
return RecyclerViewHolder(view)
}
override fun getItemCount(): Int = imagePathArrayList.size
override fun onBindViewHolder(holder: RecyclerViewHolder, position: Int) {
val imgFile = File(imagePathArrayList[position])
if (imgFile.exists()) {
Glide.with(context).load(imgFile).into(holder.imageIV)
holder.itemView.setOnClickListener {
val intent = Intent(context, ImageDetailActivity::class.java)
intent.putExtra("imgPath", imagePathArrayList[position])
context.startActivity(intent)
}
}
}
}
Step 7: Working with the MainActivity file
Go to the MainActivity file and refer to the following code. Below is the code for the MainActivity file. Comments are added inside the code to understand the code in more detail.
MainActivity File:
MainActivity.java
package org.geeksforgeeks.demo;
import android.Manifest;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
private static final int PERMISSION_REQUEST_CODE = 200;
private ArrayList<String> imageUris;
private RecyclerView recyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
imageUris = new ArrayList<>();
recyclerView = findViewById(R.id.recyclerView);
requestPermissions();
prepareRecyclerView();
}
private boolean checkPermission() {
int result = ContextCompat.checkSelfPermission(getApplicationContext()
, Manifest.permission.READ_EXTERNAL_STORAGE);
return result == PackageManager.PERMISSION_GRANTED;
}
private void requestPermissions() {
if (checkPermission()) {
Toast.makeText(this, "Permissions granted.", Toast.LENGTH_SHORT).show();
fetchImagePaths();
} else {
requestPermission();
}
}
private void requestPermission() {
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
PERMISSION_REQUEST_CODE
);
}
private void prepareRecyclerView() {
Adapter adapter = new Adapter(this, imageUris);
GridLayoutManager manager = new GridLayoutManager(this, 4);
recyclerView.setLayoutManager(manager);
recyclerView.setAdapter(adapter);
}
private void fetchImagePaths() {
boolean isSDPresent = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
if (isSDPresent) {
String[] columns = {MediaStore.Images.Media.DATA, MediaStore.Images.Media._ID};
String orderBy = MediaStore.Images.Media._ID;
Cursor cursor = getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
columns,
null,
null,
orderBy
);
if (cursor != null) {
int count = cursor.getCount();
for (int i = 0; i < count; i++) {
cursor.moveToPosition(i);
int dataColumnIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
imageUris.add(cursor.getString(dataColumnIndex));
}
cursor.close();
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0) {
boolean storageAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
if (storageAccepted) {
Toast.makeText(this, "Permissions Granted.", Toast.LENGTH_SHORT).show();
fetchImagePaths();
}
else {
Toast.makeText(this, "Permissions denied. Permissions are required to use the app."
, Toast.LENGTH_SHORT).show();
}
}
}
}
}
MainActivity.kt
package org.geeksforgeeks.demo
import android.Manifest.permission
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
class MainActivity : AppCompatActivity() {
private lateinit var imageUris: ArrayList<String>
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: Adapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
imageUris = ArrayList()
recyclerView = findViewById(R.id.recyclerView)
requestPermissions()
prepareRecyclerView()
}
private fun checkPermission(): Boolean {
val result = ContextCompat.checkSelfPermission(applicationContext, permission.READ_EXTERNAL_STORAGE)
return result == PackageManager.PERMISSION_GRANTED
}
private fun requestPermissions() {
if (checkPermission()) {
Toast.makeText(this, "Permissions granted..", Toast.LENGTH_SHORT).show()
imagePath
} else {
requestPermission()
}
}
private fun requestPermission() {
ActivityCompat.requestPermissions(
this,
arrayOf(permission.READ_EXTERNAL_STORAGE),
PERMISSION_REQUEST_CODE
)
}
private fun prepareRecyclerView() {
adapter = Adapter(this, imageUris)
val manager = GridLayoutManager(this, 4)
recyclerView.layoutManager = manager
recyclerView.adapter = adapter
}
private val imagePath: Unit
get() {
val isSDPresent = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
if (isSDPresent) {
val columns = arrayOf(MediaStore.Images.Media.DATA, MediaStore.Images.Media._ID)
val orderBy = MediaStore.Images.Media._ID
val cursor = contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
columns,
null,
null,
orderBy
)
val count = cursor!!.count
for (i in 0 until count) {
cursor.moveToPosition(i)
val dataColumnIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA)
imageUris.add(cursor.getString(dataColumnIndex))
}
cursor.close()
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
PERMISSION_REQUEST_CODE ->
if (grantResults.isNotEmpty()) {
val storageAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED
if (storageAccepted) {
Toast.makeText(this, "Permissions Granted..", Toast.LENGTH_SHORT).show()
imagePath
} else {
Toast.makeText(this, "Permissions denied, Permissions are required to use the app..", Toast.LENGTH_SHORT).show()
}
}
}
}
companion object {
private const val PERMISSION_REQUEST_CODE = 200
}
}
Note: Make sure to grant read storage permissions.
Refer to the following github repo to get the entire code: Photo_Viewer_Android
Output:
Similar Reads
How to Build a Photo Viewing Application in Android using Jetpack Compose? Gallery App is one of the most used Applications which comes pre-installed on many Android devices and there are several different applications that are present in Google Play to view the media files present in a device. In this article, we will be building a simple Gallery Application for Android u
10 min read
How to Create a Paint Application in Android? We all have once used the MS-Paint in our childhood, and when the system was shifted from desks to our palms, we started doodling on Instagram Stories, Hike, WhatsApp, and many more such apps. But have you ever thought about how these functionalities were brought to life? So, In this article, we wil
8 min read
How to Build a Bitcoin Tracker Android App? In this article, we will be building a Bitcoin Tracker Project using Java/Kotlin and XML in Android. The application will display the current rates of Bitcoin in different countries using Bitcoin API. There are many free APIs available and for this project, we will be using API by Coinlayer. The API
7 min read
How to Build a ChatGPT Like Image Generator Application in Android? Chat GPT is nowadays one of the famous AI tools which are like a chatbot. This chatbot answers all the queries which are sent to it. In this article, we will be building a simple ChatGPT-like android application in which we will be able to ask any question and from that question, we will be able to
5 min read
How to Share a Captured Image to Another Application in Android? Pre-requisite: How to open a Camera through Intent and capture an image In this article, we will try to send the captured image (from this article) to other apps using Android Studio. Approach: The image captured gets stored on the external storage. Hence we need to request permission to access the
6 min read