A Comprehensive Guide to Storing Images with Supabase and Managing Data with Firebase Realtime database.

Requirements

build.gradle.kts (Module :app)

    // Add the Retrofit library
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")

// Add the Volley library
    implementation("com.android.volley:volley:1.2.0")

// Add the OkHttp library
    implementation("com.squareup.okhttp3:okhttp:4.9.1")
    implementation("com.github.bumptech.glide:glide:4.16.0")
    
    
    //Circle image view
        implementation("de.hdodenhof:circleimageview:3.1.0")

AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />

    <!--    Permissions for Android 12L (API level 32) or lower-->
    <uses-permission
        android:name="android.permission.READ_EXTERNAL_STORAGE"
        android:maxSdkVersion="32" />

    <!-- Permissions for Android 13 (API level 33) or higher -->
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

    <!-- Permissions for Android 14 (API level 34) or higher -->
    <uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />

Codes

activity_create_workshop.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".admin.CreateWorkshopActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:navigationIcon="@drawable/arrow_left"
            app:title="Create Workshop" />
    </com.google.android.material.appbar.AppBarLayout>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="16dp">

            <com.google.android.material.textfield.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="12dp"
                android:hint="Title">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/ed_title"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content" />
            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.textfield.TextInputLayout
                android:id="@+id/til_start_date"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="12dp"
                android:hint="Start Date">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/ed_start_date"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:clickable="true"
                    android:cursorVisible="false"
                    android:focusable="false"
                    android:inputType="none" />
            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.textfield.TextInputLayout
                android:id="@+id/til_end_date"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="12dp"
                android:hint="End Date">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/ed_end_date"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:clickable="true"
                    android:cursorVisible="false"
                    android:focusable="false"
                    android:inputType="none" />
            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.textfield.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="12dp"
                android:hint="Description">

                <com.google.android.material.textfield.TextInputEditText
                    android:id="@+id/ed_description"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:gravity="bottom"
                    android:lines="5" />
            </com.google.android.material.textfield.TextInputLayout>

            <com.google.android.material.card.MaterialCardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="12dp"
                app:cardUseCompatPadding="true">

                <ImageView
                    android:id="@+id/img_image"
                    android:layout_width="match_parent"
                    android:layout_height="150dp" />

                <com.google.android.material.button.MaterialButton
                    android:id="@+id/btn_select_image"
                    style="@style/Widget.Material3.Button.IconButton.Filled"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    app:icon="@drawable/plus_circle" />
            </com.google.android.material.card.MaterialCardView>

            <com.google.android.material.button.MaterialButton
                android:id="@+id/create"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Create" />
        </LinearLayout>
    </ScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

CreateWorkshopActivity.java

package com.example.techsupporthub.admin;

import android.Manifest;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.widget.ImageView;
import android.widget.Toast;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;

import com.bumptech.glide.Glide;
import com.example.techsupporthub.R;
import com.example.techsupporthub.model.Workshop;
import com.example.techsupporthub.utils.Config;
import com.example.techsupporthub.utils.Constants;
import com.example.techsupporthub.utils.Utils;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.datepicker.MaterialDatePicker;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.UUID;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class CreateWorkshopActivity extends AppCompatActivity {

    private static final int REQUEST_PERMISSION_CODE = 101;
    private final OkHttpClient client = new OkHttpClient();
    private ImageView imgImage;
    private MaterialButton btnSelectImage;
    private MaterialButton createButton;
    private TextInputEditText edTitle;
    private TextInputEditText edStartDate;
    private TextInputEditText edEndDate;
    private TextInputEditText edDescription;
    private TextInputLayout tilStartDate;
    private TextInputLayout tilEndDate;
    private ActivityResultLauncher<String[]> requestPermissionLauncher;
    private ActivityResultLauncher<Intent> pickImageLauncher;
    private ProgressDialog progressDialog;
    private Uri selectedImageUri;
    private String startDate;
    private String endDate;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_create_workshop);

        MaterialToolbar toolbar = findViewById(R.id.toolbar);
        toolbar.setNavigationOnClickListener(v -> finish());

        imgImage = findViewById(R.id.img_image);
        btnSelectImage = findViewById(R.id.btn_select_image);
        createButton = findViewById(R.id.create);
        edTitle = findViewById(R.id.ed_title);
        edStartDate = findViewById(R.id.ed_start_date);
        edEndDate = findViewById(R.id.ed_end_date);
        edDescription = findViewById(R.id.ed_description);
        tilStartDate = findViewById(R.id.til_start_date);
        tilEndDate = findViewById(R.id.til_end_date);

        // Initialize permission launcher
        requestPermissionLauncher = registerForActivityResult(
                new ActivityResultContracts.RequestMultiplePermissions(),
                permissions -> {
                    boolean allGranted = true;
                    for (boolean isGranted : permissions.values()) {
                        allGranted = allGranted && isGranted;
                    }
                    if (allGranted) {
                        openImagePicker();
                    } else {
                        Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show();
                    }
                }
        );

        // Initialize image picker launcher
        pickImageLauncher = registerForActivityResult(
                new ActivityResultContracts.StartActivityForResult(),
                result -> {
                    if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
                        selectedImageUri = result.getData().getData();
                        try {
                            Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), selectedImageUri);
                            imgImage.setImageBitmap(bitmap);
                        } catch (IOException e) {
                            e.printStackTrace();
                            Toast.makeText(this, "Failed to load image", Toast.LENGTH_SHORT).show();
                        }
                    }
                }
        );

        btnSelectImage.setOnClickListener(v -> requestPermissionsBasedOnVersion());

        edStartDate.setOnClickListener(v -> openStartDatePicker());
        edEndDate.setOnClickListener(v -> openEndDatePicker());

        createButton.setOnClickListener(v -> validateAndSendData());
    }

    private void requestPermissionsBasedOnVersion() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            requestPermissionLauncher.launch(new String[]{
                    Manifest.permission.READ_MEDIA_IMAGES,
                    Manifest.permission.READ_MEDIA_VIDEO,
                    Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
            });
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            requestPermissionLauncher.launch(new String[]{
                    Manifest.permission.READ_MEDIA_IMAGES,
                    Manifest.permission.READ_MEDIA_VIDEO
            });
        } else {
            requestPermissionLauncher.launch(new String[]{
                    Manifest.permission.READ_EXTERNAL_STORAGE
            });
        }
    }

    private void openImagePicker() {
        Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
        pickImageLauncher.launch(intent);
    }

    private void openStartDatePicker() {
        MaterialDatePicker<Long> datePicker = MaterialDatePicker.Builder.datePicker()
                .setTitleText("Select Start Date")
                .setSelection(MaterialDatePicker.todayInUtcMilliseconds())
                .build();

        datePicker.show(getSupportFragmentManager(), "DATE_PICKER_START");
        datePicker.addOnPositiveButtonClickListener(selection -> {
            startDate = formatDate(selection);
            edStartDate.setText(startDate);
        });
    }

    private void openEndDatePicker() {
        MaterialDatePicker<Long> datePicker = MaterialDatePicker.Builder.datePicker()
                .setTitleText("Select End Date")
                .setSelection(MaterialDatePicker.todayInUtcMilliseconds())
                .build();

        datePicker.show(getSupportFragmentManager(), "DATE_PICKER_END");
        datePicker.addOnPositiveButtonClickListener(selection -> {
            endDate = formatDate(selection);
            edEndDate.setText(endDate);
        });
    }

    private String formatDate(long timeInMillis) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
        return dateFormat.format(new Date(timeInMillis));
    }

    private void validateAndSendData() {
        String title = edTitle.getText().toString().trim();
        String description = edDescription.getText().toString().trim();

        if (title.isEmpty()) {
            edTitle.setError("Title is required");
            return;
        }

        if (startDate == null || startDate.isEmpty()) {
            tilStartDate.setError("Start date is required");
            return;
        }

        if (endDate == null || endDate.isEmpty()) {
            tilEndDate.setError("End date is required");
            return;
        }

        if (description.isEmpty()) {
            edDescription.setError("Description is required");
            return;
        }

        if (selectedImageUri == null) {
            Toast.makeText(this, "Please select an image", Toast.LENGTH_SHORT).show();
            return;
        }

        uploadImage(title, description, startDate, endDate);
    }

    private void uploadImage(String title, String description, String startDate, String endDate) {
        progressDialog = new ProgressDialog(this);
        progressDialog.setMessage("Uploading image...");
        progressDialog.setCancelable(false);
        progressDialog.show();

        // Generate a unique file name
        String uniqueFileName = UUID.randomUUID().toString() + getFileExtension(selectedImageUri);

        // Convert Uri to byte array
        try {
            Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), selectedImageUri);
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

            // Determine the MIME type and compress the bitmap accordingly
            String mimeType = getContentResolver().getType(selectedImageUri);
            if ("image/png".equals(mimeType)) {
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
            } else {
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream);
            }

            byte[] byteArray = byteArrayOutputStream.toByteArray();

            MediaType mediaType = MediaType.parse(mimeType);
            RequestBody requestFile = RequestBody.create(mediaType, byteArray);
            MultipartBody.Part body = MultipartBody.Part.createFormData("file", uniqueFileName, requestFile);

            MultipartBody requestBody = new MultipartBody.Builder()
                    .setType(MultipartBody.FORM)
                    .addFormDataPart("file", uniqueFileName, requestFile)
                    .build();

            String uploadUrl = Config.SUPABASE_URL + "/storage/v1/object/" + Config.STORAGE_BUCKET + "/" + uniqueFileName;

            Request request = new Request.Builder()
                    .url(uploadUrl)
                    .post(requestBody)
                    .addHeader("Authorization", Config.AUTHORIZATION_TOKEN)
                    .addHeader("apiKey", Config.API_KEY)
                    .build();

            client.newCall(request).enqueue(new okhttp3.Callback() {
                @Override
                public void onFailure(okhttp3.Call call, IOException e) {
                    runOnUiThread(() -> {
                        progressDialog.dismiss();
                        Toast.makeText(CreateWorkshopActivity.this, "Upload failed: " + e.getMessage(), Toast.LENGTH_SHORT).show();
                    });
                }

                @Override
                public void onResponse(okhttp3.Call call, Response response) throws IOException {
                    if (response.isSuccessful()) {
                        String responseBody = response.body().string();
                        Log.d("SUPABASE_ONE_RESPONSE", responseBody);
                        String imageUrl = Config.SUPABASE_URL + "/storage/v1/object/" + Config.STORAGE_BUCKET + "/" + uniqueFileName;
                        runOnUiThread(() -> {
                            progressDialog.dismiss();
                            Glide.with(CreateWorkshopActivity.this).load(imageUrl).into(imgImage);
                            sendToDB(title, description, startDate, endDate, imageUrl);
                        });
                    } else {
                        runOnUiThread(() -> {
                            progressDialog.dismiss();
                            Toast.makeText(CreateWorkshopActivity.this, "Upload failed", Toast.LENGTH_SHORT).show();
                        });
                        Log.d("SUPABASE_ONE", response.toString());
                    }
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
            runOnUiThread(() -> {
                progressDialog.dismiss();
                Toast.makeText(this, "Failed to load image", Toast.LENGTH_SHORT).show();
            });
        }
    }

    private String getFileExtension(Uri uri) {
        ContentResolver contentResolver = getContentResolver();
        String mimeType = contentResolver.getType(uri);
        if ("image/png".equals(mimeType)) {
            return ".png";
        } else if ("image/jpeg".equals(mimeType) || "image/jpg".equals(mimeType)) {
            return ".jpg";
        } else {
            return ".jpg"; // Default to JPEG if unknown
        }
    }

    private void sendToDB(String title, String description, String startDate, String endDate, String imageUrl) {
        progressDialog = new ProgressDialog(this);
        progressDialog.setMessage("Uploading data...");
        progressDialog.setCancelable(false);
        progressDialog.show();
        // Generate a unique ID for the workshop
        String workshopId = Utils.getDatabaseReference(Constants.WORKSHOP_DB).push().getKey();

        // Create a Workshop object
        Workshop workshop = new Workshop(
                workshopId,
                title,
                description,
                startDate,
                endDate,
                imageUrl,
                System.currentTimeMillis()
        );

        // Push the workshop to Firebase Realtime Database
        if (workshopId != null) {
            Utils.getDatabaseReference(Constants.WORKSHOP_DB).child(workshopId).setValue(workshop)
                    .addOnSuccessListener(aVoid -> {
                        progressDialog.dismiss();
                        Toast.makeText(CreateWorkshopActivity.this, "Data sent to DB", Toast.LENGTH_SHORT).show();
                        finish();
                    })
                    .addOnFailureListener(e -> {
                        progressDialog.dismiss();

                        Toast.makeText(CreateWorkshopActivity.this, "Failed to send data to DB: " + e.getMessage(), Toast.LENGTH_SHORT).show();
                    });
        } else {
            progressDialog.dismiss();
            Toast.makeText(this, "Failed to generate a unique ID", Toast.LENGTH_SHORT).show();
        }
    }
}

Config .java

package com.example.techsupporthub.utils;

public class Config {
    // Supabase URL
    public static final String SUPABASE_URL = "<https://wizudywwslwlpfxaajvf.supabase.co>";
    // Authorization Token
    public static final String AUTHORIZATION_TOKEN = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6IndpenVkeXd3c2x3bHBmeGFhanZmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzgzNTgzMjEsImV4cCI6MjA1MzkzNDMyMX0.10DBdAyiAk1Z-Q18GrcN1wixMekylL16OFpMFPdPnlI";
    // API Key
    public static final String API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6IndpenVkeXd3c2x3bHBmeGFhanZmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzgzNTgzMjEsImV4cCI6MjA1MzkzNDMyMX0.10DBdAyiAk1Z-Q18GrcN1wixMekylL16OFpMFPdPnlI";
    // Storage Bucket
    public static final String STORAGE_BUCKET = "rest_test";
}

Utils.java

package com.example.techsupporthub.utils;

import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;

public class Utils {

    /**
     * Returns a DatabaseReference for the specified child/table.
     *
     * @param child The child or table name.
     * @return A DatabaseReference for the specified child/table.
     */
    public static DatabaseReference getDatabaseReference(String child) {
        return FirebaseDatabase.getInstance().getReference(child);
    }
}