The number of mobile users is expected to reach nearly 7.5 billion (source: Statista) – nearly every person on Earth! This vast user base makes the Android platform a canvas for creating impactful applications.
Photo galleries are essential features for any operating system, including Android. Visual media is king, and everyone needs a way to view their photos. A photo gallery is more than just a digital storage space. They can have additional features like photo editing (cropping, filters, rotation, etc.) and serve as a way to quickly browse, search, and filter images.
In this guide, we’ll explore how you can create a photo gallery on Android. We’ll be using Cloudinary to store and serve images within the cloud, freeing up space on our user’s device and even opening up our app to new features in the future.
In this article:
Creating a Photo Gallery in Android Java Using Cloudinary
Before we begin, you’ll need a Cloudinary account. If you don’t have one already, you can sign up for a free account to get started. Once you’ve signed up, log in to your Cloudinary account and copy your Cloudinary API credentials for later.
For this project, we will be using Android Studio. If you don’t have it installed, you can download the latest version from the Android Studio website.
Initializing Our Android Studio Project
We will select an Empty Views Activity project in Android Studio to create our UI. Ensure that you choose a template that supports Java.
Next, we will name our project Photo Gallery and select its directory. Now, select the default programming language, Java, and the minimum SDK requirement for our project, Android 11. Finally, we will click on Finish to finish setting up our project.
Once Grade finishes setting up your project dependencies, we can begin creating the UI for our app. Let’s start by heading over to our activity_main.xml
file in our project’s res/layout
folder. This file contains the main app screen. We will open this file in Android Studio.
Here, we will first delete our Hello World textbox. Next, right-click on ConstraintLayout and select Change View…. Here, we will change our view to the relative layout.
Next, search for the GridView object in the object palette and drop it in our UI. This will create a dynamic grid view for your app. Now, open up the code of our XML file by clicking on the Code button on the top-right corner highlighted in the image above. Here, we will add vertical and horizontal spacing to our GridView
object and define the number of columns as auto_fit
. Here is what our activity_main.xml
file looks like:
Here is what the source code of our file looks like:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <GridView android:id="@+id/myGrid" android:layout_width="match_parent" android:layout_height="match_parent" android:columnWidth="100dp" android:numColumns="auto_fit" android:verticalSpacing="10dp" android:horizontalSpacing="10dp" /> </androidx.constraintlayout.widget.ConstraintLayout>
Next, we will create a custom dialog to display our image. To do this, right-click on the layout folder of your project and head to New > Layout Resource File. Here, we will set our file name as custom_dialog
and paste the following code inside our XML file:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" android:layout_margin="10dp" > <TextView android:id="@+id/txt_Image_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Image Name" android:gravity="center" android:textSize="20sp" /> <ImageView android:id="@+id/img" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/txt_Image_name" android:scaleType="centerCrop" android:src="@mipmap/ic_launcher_round" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_below="@+id/img" > <Button android:id="@+id/btn_close" android:layout_width="0dp" android:layout_height="wrap_content" android:text="Close" android:textColor="#fff" android:background="#000" android:layout_weight="1" /> </LinearLayout> </RelativeLayout>
Here, we simply create a small component that shows an ImageView with a Close button. The purpose of this dialog box is to open up a picture once it has been clicked on. Here is what our dialog box looks like:
Coding Our Android App
With our UI ready, we need to set up the necessary dependencies for our project. So, head over to your build.gradle
file and add the dependencies below:
implementation("com.squareup.picasso:picasso:2.8") implementation("com.cloudinary:cloudinary-android:2.5.0") implementation("com.android.volley:volley:1.2.1")
Here, we are adding Picasso, a powerful image downloading and caching library for Android, to display our images, and the Cloudinary Android SDK is our dependency. We have also added Volley, which allows us to make API calls to retrieve JSON objects.
This is what our gradle file looks like:
Next, we must give our app some permissions. Android apps require these permissions to access data on your phone and use the internet. To do this, open up your AndroidManifest.xml
file and use the <uses-permission>
tag to add permissions for reading image and video files along with accessing the internet:
<!-- Read Storage --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <!-- Access Internet --> <uses-permission android:name="android.permission.INTERNET" />
With the project now set up, we can begin coding our app. We will begin opening up our MainActivity.java
file in the com.example.cloudinary
folder of our project. Here, we will navigate to the MainActivity
class and start by defining an array that contains our URLs as well as a HashMap called config
:
public class MainActivity extends AppCompatActivity { ArrayList<String> mImageUrls = new ArrayList<>(); Map config = new HashMap();
Next, inside the onCreate()
function, we start by setting the main screen to activity_main
and define our Cloudinary API:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Initializing Cloudinary configuration config.put("cloud_name", "your_cloud_name"); config.put("api_key","your_api_key"); config.put("api_secret","your_api_secret"); MediaManager.init(this, config);
Next, we create our Cloudinary API URL and retrieve our GridView
using the findViewById()
function. We will also create an ImageAdapter
object (which we will define later) and set our gridView
’s adapter as the ImageAdapter
object:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Initializing Cloudinary configuration config.put("cloud_name", "your_cloud_name"); config.put("api_key","your_api_key"); config.put("api_secret","your_api_secret"); MediaManager.init(this, config); String request_url = "https://res.cloudinary.com/<your_cloud_name>/image/list/<your_image_tag>.json"; GridView gridView = findViewById(R.id.myGrid); ImageAdapter imageAdapter = new ImageAdapter(mImageUrls, this); gridView.setAdapter(imageAdapter);
Next, we create a StringRequest
that calls on the Cloudinary API URL and retrieves all the images in a certain tag as a JSON object. Here, we will be defining the tag as "family"
. Next, we traverse over the items inside the JSON object, and for each image, we extract its public ID.
Finally, we use the MediaManager
to create a Cloudinary URL with the same width and height as our ImageView
before finally storing these URLs in our array. We then add this string request to our Volley’s queue:
... // Instantiate the RequestQueue. RequestQueue queue = Volley.newRequestQueue(this); // Request a string response from the provided URL. StringRequest stringRequest = new StringRequest(Request.Method.GET, request_url, new Response.Listener<String>() { @Override public void onResponse(String response) { try { // Convert the response to a JSONObject JSONObject jsonObject = new JSONObject(response); Log.d("JSON", String.valueOf(jsonObject)); // Get the JSONArray of resources JSONArray resourcesArray = jsonObject.getJSONArray("resources"); // Loop through the array and get the public_id of each object for (int i = 0; i < resourcesArray.length(); i++) { JSONObject resourceObject = resourcesArray.getJSONObject(i); String public_id = resourceObject.getString("public_id"); // Add the public_id to your mImageUrls list mImageUrls.add(MediaManager.get().url().transformation(new Transformation().width(350).height(450).crop("pad")).generate(public_id)); } // Notify your adapter that the data has changed imageAdapter.notifyDataSetChanged(); } catch (JSONException e) { e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.e("Volley", "That didn't work!"); } }); // Add the request to the RequestQueue. queue.add(stringRequest); ...
To complete our onCreate()
function, we add an on-click function that will show our Dialog Box, which we will define later. Below is our complete onCreate()
function:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Initializing Cloudinary configuration config.put("cloud_name", "your_"); config.put("api_key","your_api_key"); config.put("api_secret","your_api_secret"); MediaManager.init(this, config); String request_url = "https://res.cloudinary.com/<your_cloud_name>/image/list/<your_image_tag>.json"; GridView gridView = findViewById(R.id.myGrid); ImageAdapter imageAdapter = new ImageAdapter(mImageUrls, this); gridView.setAdapter(imageAdapter); // Instantiate the RequestQueue. RequestQueue queue = Volley.newRequestQueue(this); // Request a string response from the provided URL. StringRequest stringRequest = new StringRequest(Request.Method.GET, request_url, new Response.Listener<String>() { @Override public void onResponse(String response) { try { // Convert the response to a JSONObject JSONObject jsonObject = new JSONObject(response); Log.d("JSON", String.valueOf(jsonObject)); // Get the JSONArray of resources JSONArray resourcesArray = jsonObject.getJSONArray("resources"); // Loop through the array and get the public_id of each object for (int i = 0; i < resourcesArray.length(); i++) { JSONObject resourceObject = resourcesArray.getJSONObject(i); String public_id = resourceObject.getString("public_id"); // Add the public_id to your mImageUrls list mImageUrls.add(MediaManager.get().url().transformation(new Transformation().width(350).height(450).crop("pad")).generate(public_id)); } // Notify your adapter that the data has changed imageAdapter.notifyDataSetChanged(); } catch (JSONException e) { e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.e("Volley", "That didn't work!"); } }); // Add the request to the RequestQueue. queue.add(stringRequest); gridView.setOnItemClickListener((parent, view, position, id) -> { String item_pos = mImageUrls.get(position); ShowDialogBox(item_pos); }); }
Next, we will define our ShowDialogBox()
function. The function starts by creating a dialog box and setting the view as the custom_dialog
. Next, it retrieves the objects from the view and stores them in variables. Finally, it extracts the URL of the image using the position parameter and then uses Picasso to display it. It also adds an on-click function to the Cancel button to close the dialog.
Following is the complete MainActivity.java
code:
package com.example.photogallery; import androidx.appcompat.app.AppCompatActivity; import android.app.Dialog; import android.os.Bundle; import android.util.Log; import android.widget.Button; import android.widget.GridView; import android.widget.ImageView; import android.widget.TextView; import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.toolbox.StringRequest; import com.android.volley.toolbox.Volley; import com.cloudinary.Transformation; import com.cloudinary.android.MediaManager; import com.squareup.picasso.Picasso; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; public class MainActivity extends AppCompatActivity { ArrayList<String> mImageUrls = new ArrayList<>(); Map config = new HashMap(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Initializing Cloudinary configuration config.put("cloud_name", "your_cloud_name"); config.put("api_key","your_api_key"); config.put("api_secret","your_api_secret"); MediaManager.init(this, config); String request_url = "https://res.cloudinary.com/<your_cloud_name>/image/list/<your_image_tag>.json"; GridView gridView = findViewById(R.id.myGrid); ImageAdapter imageAdapter = new ImageAdapter(mImageUrls, this); gridView.setAdapter(imageAdapter); // Instantiate the RequestQueue. RequestQueue queue = Volley.newRequestQueue(this); // Request a string response from the provided URL. StringRequest stringRequest = new StringRequest(Request.Method.GET, request_url, new Response.Listener<String>() { @Override public void onResponse(String response) { try { // Convert the response to a JSONObject JSONObject jsonObject = new JSONObject(response); Log.d("JSON", String.valueOf(jsonObject)); // Get the JSONArray of resources JSONArray resourcesArray = jsonObject.getJSONArray("resources"); // Loop through the array and get the public_id of each object for (int i = 0; i < resourcesArray.length(); i++) { JSONObject resourceObject = resourcesArray.getJSONObject(i); String public_id = resourceObject.getString("public_id"); // Add the public_id to your mImageUrls list mImageUrls.add(MediaManager.get().url().transformation(new Transformation().width(350).height(450).crop("pad")).generate(public_id)); } // Notify your adapter that the data has changed imageAdapter.notifyDataSetChanged(); } catch (JSONException e) { e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.e("Volley", "That didn't work!"); } }); // Add the request to the RequestQueue. queue.add(stringRequest); gridView.setOnItemClickListener((parent, view, position, id) -> { String item_pos = mImageUrls.get(position); ShowDialogBox(item_pos); }); } public void ShowDialogBox(final String item_pos){ final Dialog dialog = new Dialog(this); dialog.setContentView(R.layout.custom_dialog); //Getting custom dialog views TextView Image_name = dialog.findViewById(R.id.txt_Image_name); ImageView Image = dialog.findViewById(R.id.img); Button btn_Close = dialog.findViewById(R.id.btn_close); //extracting name int index = item_pos.lastIndexOf("/"); String name = item_pos.substring(index+1); Image_name.setText(name); Picasso.get().load(item_pos).into(Image); btn_Close.setOnClickListener(v -> dialog.dismiss()); dialog.show(); } }
Now that we’ve completed our MainActivity
class, we need to define our ImageAdapter
class to extend the BaseAdapter
class. The purpose of this class is to handle the loading and displaying of images in a grid view. We will begin by defining a constructor for the class, which will store the array of URLs as well as the context of our code:
public class ImageAdapter extends BaseAdapter { private List<String> mThumbUrls; private Context mContext; public ImageAdapter(List<String> mThumbUrls, Context mContext) { this.mThumbUrls = mThumbUrls; this.mContext = mContext; }
Next, we override some function of the BaseAdapter class as follows:
public class ImageAdapter extends BaseAdapter { private List<String> mThumbUrls; private Context mContext; public ImageAdapter(List<String> mThumbUrls, Context mContext) { this.mThumbUrls = mThumbUrls; this.mContext = mContext; }
Finally, we will override the most important method in the adapter, i.e., getView()
. We override the getView()
method from the BaseAdapter
class so that it creates and populates the view for each image item in the grid view. It first tries to retrieve an existing ImageView
from the convertView
parameter.
This parameter is reused to improve performance by recycling existing views instead of creating new ones for each item. If convertView
is null
, it creates a new ImageView
using the provided context and sets its layout parameters and scale type. It then uses Picasso to load the image from the URL stored at the specified position in the mThumbUrls
list. The loaded image is then set to the ImageView
. Finally, the method returns the ImageView
, which will be displayed in the grid view.
Here is our complete ImageAdapter
class:
package com.example.photogallery; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.GridView; import android.widget.ImageView; import com.squareup.picasso.Picasso; import java.util.List; public class ImageAdapter extends BaseAdapter { private List<String> mThumbUrls; private Context mContext; public ImageAdapter(List<String> mThumbUrls, Context mContext) { this.mThumbUrls = mThumbUrls; this.mContext = mContext; } @Override public int getCount() { return mThumbUrls.size(); } @Override public Object getItem(int position) { return this; } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { ImageView imageView = (ImageView) convertView; if(imageView == null){ imageView = new ImageView(mContext); imageView.setLayoutParams(new GridView.LayoutParams(350,450)); imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); } Picasso.get().load(mThumbUrls.get(position)).into(imageView); return imageView; } }
Testing Our Photo Gallery App
Now that our code is ready, all we need to do is run our app.
Here is what our app looks like:
Now click on an image to show it inside a dialog box:
Wrapping Up
This guide provided a roadmap for creating robust photo galleries on Android devices. While the core functionalities can be built using native libraries, Cloudinary offers a compelling alternative. Its cloud-based storage, image delivery optimization, and transformation capabilities can significantly enhance your photo gallery app. Cloudinary streamlines image management ensures fast loading times, and provides your users a wide range of image manipulation options.
So, what are you waiting for? Take your Android photo gallery app to the next level with Cloudinary. Sign up for a free account and explore its powerful features designed for exceptional photo management.
More from Cloudinary: