Android Save and Retrieve Image Path From Database

In this post, I will show you how to save and retrieve image path from database in Android. Images are standard component of many software application. One of the most common use cases of image is to associate an image with an object so that each time the object is shown it is shown with its associated image. This object could be a user profile, an item in a list, etc. Whatever the item is, you want the same image of the user to show up whether you are showing the user in a listview or in a details view.

In this post, I will build a demo project showing a user profile. I will show how to obtain a picture of the user either from an image that already exists in the user’s device or if the user wants to take new image with the Android device camera. Once the image is obtained then we need to save this image path so we can reuse it later. This path can be saved in sharedPreference or can be save in Database and I will be showing here how to save it to a database. Here is what the completed demo application will look like.

This demo project uses the skeleton project that I shared in this post. Please visit that post to download the template if you want to follow along. For brevity I will not show all the lines of code required to achieve the above demo, however you can find the link to the full source code here.

This demo project shows a custom listview of customers showing their thumbnail to the right and their names and email addresses to the left. When you click on the row it will open up a Customer Details screen that shows the full image. And we actually use the same screen to create the customer and show the details. If this is a new customer, we show a blank screen and if it is an existing customer we show the customer details.

Customer Class

Before we can capture an image we have to define where we will save it. For this demo I save the image path as a field to the Customer object. So please go ahead and add a Customer.java file to the Models Package. Add the following fields to the Customer.java file:

public class Customer {
    private int mId;
    private String mName;
    private String mEmailAddress;
    private String mPhoneNumber;
    private String mStreetAddress;
    private String mCity;
    private String mState;
    private String mPostalCode;
    private String mImagePath;
}

Notice the String field ImagePath, this is where we save the image path for the customer. Use your IDE to add getters and setters for the Customer class. Still in the Customer file, we need to add four methods to this class that will help us with displaying the images in the customer’s profile. Add the following files to the Customer class and ignore the red warnings for now.

HasImage method: this checks to see if the customer has an image,

public boolean hasImage() {

        return getImagePath() != null && !getImagePath().isEmpty();
    }

GetThumbnail method: this gets thumbnail version of the customer’s image for display in say listview.

public Drawable getThumbnail(Context context) {

        return getScaledImage(context, 128, 128);
    }

GetImage method: this gets the full size (rather bigger size) of the image

 public Drawable getImage(Context context) {

        return getScaledImage(context, 512, 512);
    }

GetScaledImage method: this is the method that actually gets the image and this is what the getThumbnail and getFullImage methods call into.

private Drawable getScaledImage(Context context, int reqWidth, int reqHeight) {

        // If profile has a Image.
        if (hasImage()) {

            // Decode the input stream into a bitmap.
            Bitmap bitmap = FileUtils.getResizedBitmap(getImagePath(), reqWidth, reqHeight);

            // If was successfully created.
            if (bitmap != null) {

                // Return a drawable representation of the bitmap.
                return new BitmapDrawable(context.getResources(), bitmap);
            }
        }

        // Return the default image drawable.
        return context.getResources().getDrawable(Constants.DEFAULT_IMAGE_RESOURCE);
    }

Notice the call to a method in FileUtils. In your Helper Package, please create a file called FileUtils.java. This file will hold a lot of the important code required to accomplish this use case. Once you add the FileUtils.java file, you can go ahead add this two methods:

/**
     * Get a bitmap from file and try to resize it to be 300x300 pixels.
     *
     * @param filepath Path to the image file.
     * @param reqWidth Requested width.
     * @param reqHeight Requested height.
     * @return Bitmap of image.
     */
    public static Bitmap getResizedBitmap(String filepath, int reqWidth, int reqHeight) {
        Bitmap bitmap;

        // Decode bitmap to get current dimensions.
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filepath, options);

        // Calculate sample size of bitmap.
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap again, setting the new dimensions.
        options.inJustDecodeBounds = false;
        bitmap = BitmapFactory.decodeFile(filepath, options);

        // Return resized bitmap.
        return bitmap;
    }

    /**
     * Calculate an acceptable size based on a requested size.
     *
     * @param options Bitmap options.
     * @param reqWidth Requested width.
     * @param reqHeight Requested height.
     * @return Sample size based on requested dimensions.
     */
    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {

        // Raw height and width of image.
        final int height = options.outHeight;
        final int width = options.outWidth;

        // Initialize sample size.
        int inSampleSize = 1;

        // If image is larger than requested in at least one dimension.
        if (height > reqHeight || width > reqWidth) {

            // Calculate ratios of height and width to requested height and width.
            final int heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);

            // Choose the smallest ratio as sample size value. This will guarantee a final image
            // with both dimensions larger than or equal to the requested height and width.
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }

        // Return sample size.
        return inSampleSize;
}

I have commented the above code to provide explanation of what it is doing. It is simply following the Android recommended best practice for displaying images. With this we are done with the Customer class and next we move on to how to actually save this customer object to SQLite database.

Save Customer to SQLite Database

I created a package called Data, and in this package I added a file called DatabaseHelper.java. And just a reminder, I define all my literals as constants in a file Constants.java under the Helpers package. Here is the first part of the database file that creates the Customer table.

public class DatabaseHelper extends SQLiteOpenHelper{

    // Context in which this database exists.
    private Context mContext;

    // Database version.
    public static final int DATABASE_VERSION = 1;

    // Database name.
    public static final String DATABASE_NAME = "ImageExample";

    // Table names.
    public static final String TABLE_CUSTOMERS = "customers";

    private final static String TAG = DatabaseHelper.class.getSimpleName();


    public DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_CUSTOMER_TABLE);
      }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_CUSTOMERS);

    }

    // Command to create a table of clients.
    private static final String CREATE_CUSTOMER_TABLE = "CREATE TABLE " + TABLE_CUSTOMERS + " ("
            + Constants.COLUMN_CUSTOMER_ID + " INTEGER PRIMARY KEY, "
            + Constants.COLUMN_IMAGE_PATH + " TEXT, "
            + Constants.COLUMN_NAME + " TEXT, "
            + Constants.COLUMN_PHONE + " TEXT, "
            + Constants.COLUMN_EMAIL + " TEXT, "
            + Constants.COLUMN_STREET + " TEXT, "
            + Constants.COLUMN_CITY + " TEXT, "
            + Constants.COLUMN_STATE + " TEXT, "
            + Constants.COLUMN_ZIP_CODE + " TEXT)";

    // Database lock to prevent conflicts.
    public static final Object[] databaseLock = new Object[0];
}

I have also added the following CRUD methods to the database

  1. Add customer to database
  2. Get Customer by Id
  3. Get a list of customers from the database

Below is the addCustomer SQL statement, for brevity see the Github repository for the rest of the SQL statements.

public Long addCustomer(Customer customer) {
        Long ret = null;

        //Lock database for writing
        synchronized (databaseLock) {
            //Get a writable database
            SQLiteDatabase database = getWritableDatabase();

            //Ensure the database exists
            if (database != null) {
                //Prepare the customer information that will be saved to the database
                ContentValues values = new ContentValues();
                values.put(Constants.COLUMN_NAME, customer.getName());
                values.put(Constants.COLUMN_EMAIL, customer.getEmailAddress());
                values.put(Constants.COLUMN_PHONE, customer.getPhoneNumber());
                values.put(Constants.COLUMN_CITY, customer.getCity());
                values.put(Constants.COLUMN_STREET, customer.getStreetAddress());
                values.put(Constants.COLUMN_STATE, customer.getState());
                values.put(Constants.COLUMN_ZIP_CODE, customer.getPostalCode());
                values.put(Constants.COLUMN_IMAGE_PATH, customer.getImagePath());

                //Attempt to insert the client information into the transaction table
                try {
                    ret = database.insert(TABLE_CUSTOMERS, null, values);
                } catch (Exception e) {
                    Log.e(TAG, "Unable to add Customer to database " + e.getMessage());
                }
                //Close database connection
                database.close();
            }
        }
        return ret;
    }

We are don with the database and next we need to create the layout files for adding and displaying customers.

Layout Files

Like I mentioned at the beginning, we are using the same layout file for both creating the customer and displaying the customer detail and here is the layout file for the Customer Details Fragment.

This layout allows us to capture basic information about the customer and you can customize as you fit for your app. Notice that the ImageButton widget, this is so that we can click event on the image. Since this tutorial is focusing on the image, I will ignore the other widgets in the screen mostly the EditTexts and focus on the ImageButton. The CustomerListFragment layout, simply contains a listView that shows the list of customer’s and here is the layout;

This listview needs a custom row to display the customers in the list, so below is the custom row layout that displays the customer’s image on the right and the name and email address on the left. I have named this layout file row_customer_list.xml and you will find in the source code.

With that we are done with the layout. Please look under the menu folder to get the menu files used for this project and all the drawables used are included in the source code.

Java Code

Below are the steps we want to accomplish in code now that we have the layout and the database setup

  • On start, show a blank customer list if there is no customer already added to the list
  • If the user touch the image button during add customer process, launch an Intent chooser
  • Provide the option to choose image from any where, Gallery, Camera, DropBox, Google Drive, etc
  • If user selects an image, retrieve the real path of that image and save to database
  • Anytime you want to show image retrieve the user’s image using the image path associated with their profile.

First we need to get access to the imageButton defined in the layout file. I then CustomerDetailsFragment.cs file, below is how we get a reference to the image button:

private ImageButton mProfileImageButton;
mProfileImageButton = (ImageButton)mRootView.findViewById(R.id.customer_image_button);
        mProfileImageButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                chooseImage();
            }
        });

Notice that we attached an onClick event handler to the image button and when this event is triggered by clicking on the image, we call a method to handle choosing image for this customer’s profile. Since we want to give the user the option to either choose an existing image or take a new picture we actually need two Intents one for picking the image and one for taking a new image with the camera. We then merge the two Intents together into an IntentChoose intent like so:

private void chooseImage(){

        //We need the customer's name to to save the image file
        if (mNameEditText.getText() != null && !mNameEditText.getText().toString().isEmpty()) {
            // Determine Uri of camera image to save.
            final File rootDir = new File(Constants.PICTURE_DIRECTORY);

            // Create the destination path if it does not exist.
            //noinspection ResultOfMethodCallIgnored
            rootDir.mkdirs();

            // Create the temporary file and get it's URI.

            //Get the customer name
            String customerName = mNameEditText.getText().toString();

            //Remove all white space in the customer name
            customerName.replaceAll("s+", "");

            //Use the customer name to create the file name of the image that will be captured
            File file = new File(rootDir, FileUtils.generateImageName(customerName));
            mCapturedImageURI = Uri.fromFile(file);

            // Initialize a list to hold any camera application intents.
            final List cameraIntents = new ArrayList();

            // Get the default camera capture intent.
            final Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

            // Get the package manager.
            final PackageManager packageManager = getActivity().getPackageManager();

            // Ensure the package manager exists.
            if (packageManager != null) {

                // Get all available image capture app activities.
                final List listCam = packageManager.queryIntentActivities(captureIntent, 0);

                // Create camera intents for all image capture app activities.
                for(ResolveInfo res : listCam) {

                    // Ensure the activity info exists.
                    if (res.activityInfo != null) {

                        // Get the activity's package name.
                        final String packageName = res.activityInfo.packageName;

                        // Create a new camera intent based on android's default capture intent.
                        final Intent intent = new Intent(captureIntent);

                        // Set the intent data for the current image capture app.
                        intent.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));
                        intent.setPackage(packageName);
                        intent.putExtra(MediaStore.EXTRA_OUTPUT, mCapturedImageURI);

                        // Add the intent to available camera intents.
                        cameraIntents.add(intent);
                    }
                }
            }

            // Create an intent to get pictures from the filesystem.
            final Intent galleryIntent = new Intent();
            galleryIntent.setType("image/*");
            galleryIntent.setAction(Intent.ACTION_GET_CONTENT);

            // Chooser of filesystem options.
            final Intent chooserIntent = Intent.createChooser(galleryIntent, "Select Source");

            // Add the camera options.
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
                    cameraIntents.toArray(new Parcelable[cameraIntents.size()]));

            // Start activity to choose or take a picture.
            startActivityForResult(chooserIntent, Constants.ACTION_REQUEST_IMAGE);
        } else {
            mNameEditText.setError("Please enter customer name");
        }
    }

Again, I have commented the code to provide visibility into what it is doing. The next important step is to get the data back from the intent when it come back. This could be very tricky and is a source of quite a few frustration expressed at StackOverflow. The challenge stems from how every device handle the location of the image. Luckily a group of people created a method at this Github repo that helps get the image path successfully on the most devices.

When the Intent completes, the onActivityResult is called and here is my code to handle the result.

 @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == Activity.RESULT_OK){
               // Get the resultant image's URI.
                final Uri selectedImageUri = (data == null) ? mCapturedImageURI : data.getData();

                // Ensure the image exists.
                if (selectedImageUri != null) {

                    // Add image to gallery if this is an image captured with the camera
                    //Otherwise no need to re-add to the gallery if the image already exists
                    if (requestCode == Constants.ACTION_REQUEST_IMAGE) {
                        final Intent mediaScanIntent =
                                new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
                        mediaScanIntent.setData(selectedImageUri);
                        getActivity().sendBroadcast(mediaScanIntent);
                    }

                   mCurrentImagePath = FileUtils.getPath(getActivity(), selectedImageUri);

                    // Update client's picture
                    if (mCurrentImagePath != null && !mCurrentImagePath.isEmpty()) {
                        mProfileImageButton.setImageDrawable(new BitmapDrawable(getResources(),
                                FileUtils.getResizedBitmap(mCurrentImagePath, 512, 512)));
                    }
                }
        }

    }

Notice the call to getPath, that is very important because we need the actual String file path to save to the database otherwise we will not be able to get the image when we need it next time. And I got the method for that Paul Burkes aFileChooser.

Summary

That is it, you should now be able to select an image from anywhere in the device and save to the database associating it to one user.
Points to Note

Remember to add the relevant permission to your manifest. I added READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions.
Remember to add use-feature android.hardware.camera
Remember to call onActivityResult on the Activity and return super so that the Fragment will have a chance to handle the onActivityResult.

About the Author valokafor

I am a Software Engineer with expertise in Android Development. I am available for Android development projects.

follow me on:

Leave a Comment: