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.
<?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>
<?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.
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);
}
}
}
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))
}
}
<?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:
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();
}
}
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:
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();
}
}
}
}
}
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