Saving an image in a SQLite database in your Android application

When you start caching data to a local database from your Android application, sooner or later you’ll need to save images in that database too. For example, if you’re storing reports about observations the users make in the field that will be uploaded to the main system later, it can be handy to add a picture to better describe the problem.

The first idea that comes to mind to store those images is saving them directly in the database as BLOBs (Binary Large OBjects). It can work, but it’s not the most efficient way to do it since SQLite is meant to store values and not large binary values. Also, the local database could get big pretty fast, especially if you need to save full size, high quality pictures. You can check out a benchmark on the official SQLite site at https://www.sqlite.org/intern-v-extern-blob.html if you want to have a better idea of the performance.

What you really want to do in that case is keep only the path to the image in your database and save the image in the internal storage of your application. When you need to use the image, you can simply use the path to fetch the image from the file system. I recommend you save the pictures to the internal storage since so it’s accessible only from your application and is available at all times; actually, the SQLite database itself is also stored in the internal storage.

Here is an example of how to add an image to an existing report, saving it in the internal storage and keeping the path in the database. To learn more about how to create a database and store data to it, you can read my article about saving to a SQLite database.

public class ApplicationDatabaseHelper extends SQLiteOpenHelper {
/**
  * Updates the current picture for the report.
  *
  * @param reportId the identifier of the report for which to save the picture
  * @param picture the picture to save to the internal storage and save path in the database.
  */
public void updateReportPicture(long reportId, Bitmap picture) {
   // Saves the new picture to the internal storage with the unique identifier of the report as 
   // the name. That way, there will never be two report pictures with the same name.
   String picturePath = "";
   File internalStorage = mContext.getDir("ReportPictures", Context.MODE_PRIVATE);
   File reportFilePath = new File(internalStorage, reportId + ".png");
   String picturePath = reportFilePath.toString();

   FileOutputStream fos = null;
   try {
      fos = new FileOutputStream(reportFilePath);
      picture.compress(Bitmap.CompressFormat.PNG, 100 /*quality*/, fos);
      fos.close();
      }
   catch (Exception ex) {
      Log.i("DATABASE", "Problem updating picture", ex);
      picturePath = "";
      }

   // Updates the database entry for the report to point to the picture
   SQLiteDatabase db = getWritableDatabase();

   ContentValues newPictureValue = new ContentValues();
   newPictureValue.put(ReportContract.ReportEntry.COLUMN_PICTURE_TITLE, 
                       picturePath);

   db.update(ReportContract.TABLE_NAME,
             newPictureValue,
             ReportContract.ReportEntry._ID + "=?",
             new String[]{String.valueOf(reportId)});
   }
}

If you save a picture to the database, you also need a way to get that picture to display it in your application. Here is how to get the image that was just saved to display it again :

public class ApplicationDatabaseHelper extends SQLiteOpenHelper {
/**
  * Gets the picture for the specified report in the database.
  *
  * @param reportId the identifier of the report for which to get the picture.
  *
  * @return the picture for the report, or null if no picture was found.
  */
public Bitmap getReportPicture(long reportId) {
   String picturePath = getReportPicturePath(reportId);
   if (picturePath == null || picturePath.length() == 0)
      return (null);

   Bitmap reportPicture = BitmapFactory.decodeFile(picturePath);

   return (reportPicture);
   }

/**
  * Gets the path of the picture for the specified report in the database.
  *
  * @param reportId the identifier of the report for which to get the picture.
  *   
  * @return the picture for the report, or null if no picture was found.
  */
private String getReportPicturePath(long reportId) {
   // Gets the database in the current database helper in read-only mode
   SQLiteDatabase db = getReadableDatabase();

   // After the query, the cursor points to the first database row
   // returned by the request
   Cursor reportCursor = db.query(ReportContract.TABLE_NAME,
                                  null,
                                  ReportContract.ReportEntry._ID + "=?",
                                  new String[]{String.valueOf(reportId)},
                                  null,
                                  null,
                                  null);
   reportCursor.moveToNext();
    
   // Get the path of the picture from the database row pointed by
   // the cursor using the getColumnIndex method of the cursor.
   String picturePath = reportCursor.getString(reportCursor.
                             getColumnIndex(ReportContract.ReportEntry.COLUMN_PICTURE_TITLE));

   return (picturePath);
   }
}

Finally, you need to make sure to delete the picture from the internal storage if you delete the associated record from the database. If you keep the pictures, the internal storage used by your application will grow larger with time and your users won’t have any way to delete those pictures since the internal storage is hidden from them.

public class ApplicationDatabaseHelper extends SQLiteOpenHelper {
/**
  * Deletes the specified report from the database, removing also the associated picture from the
  * internal storage if any.
  *
  * @param reportId the report to remove.
  */
public void deleteReport(long reportId) {
   // Remove picture for report from internal storage
   String picturePath = getReportPicturePath(reportId); // See above
   if (picturePath != null && picturePath.length() != 0) {
      File reportFilePath = new File(picturePath);
      reportFilePath.delete();
   }

   // Remove the report from the database
   SQLiteDatabase db = getWritableDatabase();

   db.delete(ReportContract.TABLE_NAME,
             ReportContract.ReportEntry._ID + "=?",
             new String[]{String.valueOf(reportId)});
   }
}

How to show a Toast for a specific duration in Android

In the Android SDK, an android.widget.Toast is a small message that pops up at the bottom of the screen to display an information. The toast will disappears by itself after a specified duration. Here is an example of what a toast looks like and how to display one :

CustomToastDuration

Context context = getApplicationContext();
Toast.makeText(context, "Hello world, I am a toast.", Toast.LENGTH_SHORT).show();

The duration for which a toast is displayed on screen is unfortunately defined by a flag: you can either show it for a SHORT duration, which is 2 seconds or a LONG duration which is 3,5 seconds. But what if you have a long error message that needs to be shown for longer than that? Or if you need to show a countdown that updates every second?

There are no way to directly change the duration for which the toast is shown using the show() method without reimplementing the whole Toast class in your application, but there is a workaround. You can use a android.os.CountDownTimer to count down the time for which to display a toast. The CountDownTimer class schedules a countdown for a time in milliseconds with notifications at specified intervals until the countdown is finished.

In this example, the countdown is used to display a toast message for a specific duration when a button is pressed:

private Toast mToastToShow;
public void showToast(View view) {
   // Set the toast and duration
   int toastDurationInMilliSeconds = 10000;
   mToastToShow = Toast.makeText(this, "Hello world, I am a toast.", Toast.LENGTH_LONG);

   // Set the countdown to display the toast
   CountDownTimer toastCountDown;
   toastCountDown = new CountDownTimer(toastDurationInMilliSeconds, 1000 /*Tick duration*/) {
      public void onTick(long millisUntilFinished) {
         mToastToShow.show();
      }
      public void onFinish() {
         mToastToShow.cancel();
         }
   };

   // Show the toast and starts the countdown
   mToastToShow.show();
   toastCountDown.start();
}

Here is how it works: the countdown has a notification time shorter than the duration for which the toast is displayed according to the flag, so the toast can be shown again if the countdown is not finished. If the toast is shown again while it is still on screen, it will stay there for the whole duration without blinking. When the countdown is finished, the toast is cancelled to hide it even if its display duration is not over.

This works even if the toast must be shown for a duration shorter than the default duration: the first toast displayed will simply be cancelled when the countdown is finished.