1

I've been working on this issue for several days and I can't figure out what I'm doing wrong. I'm making an inventory/store app for a project where the user can input what items they have in stock into a list. As per the requirements I need to give it picture taking capability and I need to be able to save the picture in the SQLite database. That part I have achieved. But when I attempt to update an item and save it again to the list, the image returns null. I've attached pictures to visually explain...

Main activity

image1

New item page

image2

Save an item to the Main Activity listview

image3

Click on an item to edit

image4

Unless I take a new picture, when i save the updated item, the icon image returns null

image5

I'm using a CursorLoader. I've treated the image the same way I treated all the other columns in the database... so I can't figure out why it won't stick.

Here's the code pertaining to images in EditorActivity:

Bitmap photos;

// Content URI for the existing item (null if it's a new item)
private Uri mCurrentItemUri;

// Picture taking capability with button
public void takePicture(View view) {
    PackageManager pm = this.getPackageManager();
    if (pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
        }
    } else {
        Toast.makeText(EditorActivity.this, "Sorry! You don't have a camera app.", Toast.LENGTH_SHORT).show();
    }
}

// Once image is taken, save to image view and then save to db as a ByteArray
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        Bundle extras = data.getExtras();
        photos = (Bitmap) extras.get("data");
        mImageView.setImageBitmap(photos);
    }
}

// Get user input from editor and save item into database.
private void saveItem() {
    // Read from input fields
    // Use trim to eliminate leading or trailing white space
    String nameString = mNameEditText.getText().toString().trim();
    String priceString = mPriceEditText.getText().toString().trim();
    // Find quantity TV by id and extract int value as String
    TextView quantityTextView = (TextView) findViewById(R.id.quantity_editor_text_view);
    String quantityString = quantityTextView.getText().toString();
    ImageView iconImage = (ImageView) findViewById(R.id.image_view_editor);

    // Check if this is supposed to be a new item and check if all the fields in the editor are blank
    if (mCurrentItemUri == null && TextUtils.isEmpty(nameString)
            && TextUtils.isEmpty(priceString) && TextUtils.isEmpty(quantityString) && iconImage == null) {
        // Since no fields were modified, we can return early without creating a new item.
        // No need to create ContentValues and no need to do any ContentProvider operations.
        return;
    }

    // Create a ContentValues object where column names are the keys and item attributes from the editor are the values.
    ContentValues values = new ContentValues();

    // Find image by the member variable "photos" and get the bytes
    byte[] bytes = Utils.getImageBytes(photos);
    values.put(ItemEntry.IMAGE_VIEW, bytes);

    values.put(ItemEntry.COLUMN_NAME, nameString);

    int price = 0;
    if (!TextUtils.isEmpty(priceString)) {
        price = Integer.parseInt(priceString);
    }
    values.put(ItemEntry.COLUMN_PRICE, price);

    values.put(ItemEntry.COLUMN_QUANTITY, quantityString);

    // Determine if this is a new or existing item by checking if mCurrentItemUri is null or not
    if (mCurrentItemUri == null) {
        // This is a NEW item, so insert a new item into the provider,
        // returning the content URI for the new item.
        Uri newUri = getContentResolver().insert(ItemEntry.CONTENT_URI, values);
        // Show a toast message depending on whether or not the insertion was successful.
        if (newUri == null) {
            // If the new content URI is null, then there was an error with insertion.
            Toast.makeText(this, getString(R.string.insert_item_failed), Toast.LENGTH_SHORT).show();
        } else {
            // Otherwise, the insertion was successful and we can display a toast.
            Toast.makeText(this, getString(R.string.insert_item_successful), Toast.LENGTH_SHORT).show();
        }
    } else {
        // Otherwise this is an EXISTING item, so update the item with content URI: mCurrentItemUri
        // and pass in the new ContentValues. Pass in null for the selection and selection args
        // because mCurrentItemUri will already identify the correct row in the database that
        // we want to modify.
        int rowsAffected = getContentResolver().update(mCurrentItemUri, values, null, null);
        // Show a toast message depending on whether or not the update was successful.
        if (rowsAffected == 0) {
            // If no rows were affected, then there was an error with the update.
            Toast.makeText(this, getString(R.string.edit_item_failed), Toast.LENGTH_SHORT).show();
        } else {
            // Otherwise, the update was successful and we can display a toast.
            Toast.makeText(this, getString(R.string.edit_item_successful), Toast.LENGTH_SHORT).show();
        }
    }
}

And the Utils file for the image methods:

public class Utils {

// Used in saveItem
// Adding the if/else statement here allows the picture to be null
// But now the picture disappears when you update an item without taking a new pic.
public static byte[] getImageBytes(Bitmap bitmap) {
    if (bitmap != null) {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
        return stream.toByteArray();
    } else {
        return null;
    }
}

// Used in OnLoadFinished (Editor) and cursor adapter
public static Bitmap getImage(byte[] image) {
    return BitmapFactory.decodeByteArray(image, 0, image.length);
}
}

I'm fairly certain the issue lies in the saveItem method but let me know if you'd like to see the whole file.

Phantômaxx
  • 37,901
  • 21
  • 84
  • 115
Emily
  • 89
  • 1
  • 11
  • 1
    Nice app by the way. I think it would be better not to store images in your database. What I would do is to create a hidden folder on the device or memory card to store images, then save a link to the images and load them into the application when it is required. So the column in your database for images would hold a path to the image and not the image itself – jaletechs Nov 17 '17 at 07:55
  • Thank you. I agree but it's a project for class and we're required to use the database. – Emily Nov 17 '17 at 08:03
  • Shouldn't you be calling/invoking `saveItem` from `onActivityResult` ? – MikeT Nov 17 '17 at 08:15
  • I don't think so @Mike. SaveItem is called when the menu button (the check mark) at the top is clicked. Since "photos" is a member variable the method still receives that information and saves it to the content values. And it'll save to the db just fine, but it won't remain unless I keep taking a new picture with every update. The name/price/quantity will all remain (also in the content values), so I'm not sure what's different about a blob. – Emily Nov 17 '17 at 08:26
  • Perhaps change the comment `// Once image is taken, save to image view and then save to db as a ByteArray`. I'd suggest adding a breakpoint or logging at `values.put(ItemEntry.IMAGE_VIEW, bytes);` and seeing if/when null is returned from `getImageBytes`. Could it be size of the pictures? There's apparently a 1mb limit for blobs [e.g. this article](https://stackoverflow.com/questions/5406429/cursor-size-limit-in-android-sqlitedatabase) – MikeT Nov 17 '17 at 08:35
  • You could also perhaps temporarily save some text rather than a blob to see if the update works for non blobs (for want of a better word). – MikeT Nov 17 '17 at 08:38

2 Answers2

0

The best approach for this work is that you should save image in phone directory which is accessible/not accessible from gallery as per your requirement and save the file path in the image column and you can access that image whenever it is required to load. I am also following the same approach in one of my offline applications which works smoothly without any problems but make sure whenever you perform delete option for any row then at that time you should also remove that file for better storage management.

Pang
  • 9,564
  • 146
  • 81
  • 122
Rishabh Saxena
  • 1,765
  • 15
  • 26
0

Posting in case it helps anyone. The problem was not where I thought it was. In the onLoadFinished method in the EditorActivity I needed to add this line:

photos = Utils.getImage(image);

just after getting the blob from the cursor. That allows the picture to stick around after updating the item, without taking a new picture.

public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {

    // Bail early if the cursor is null or there is less than 1 row in the cursor
    if (cursor == null || cursor.getCount() < 1) {
        return;
    }

    // Proceed with moving to the first row of the cursor and reading data from it
    // (This should be the only row in the cursor)
    if (cursor.moveToFirst()) {
        // Find the columns of item attributes that we're interested in
        int imageColumnIndex = cursor.getColumnIndex(ItemEntry.IMAGE_VIEW);
        int nameColumnIndex = cursor.getColumnIndex(ItemEntry.COLUMN_NAME);
        int priceColumnIndex = cursor.getColumnIndex(ItemEntry.COLUMN_PRICE);
        int quantityColumnIndex = cursor.getColumnIndex(ItemEntry.COLUMN_QUANTITY);

        // Extract out the value from the Cursor for the given column index
        byte[] image = cursor.getBlob(imageColumnIndex);
        // populate the photos variable with bytes and the picture will save to the list view after editing item
        photos = Utils.getImage(image);
        String name = cursor.getString(nameColumnIndex);
        String price = cursor.getString(priceColumnIndex);
        int quantity = cursor.getInt(quantityColumnIndex);

        // Update the views on the screen with the values from the database
        // if I delete the if/else statement, error saying Attempt to get length of null array from method Utils.getImage
        // can remove once I get image to stick
        if (image != null) {
            mImageView.setImageBitmap(Utils.getImage(image));
        } else {
            mImageView.setImageBitmap(null);
        }
        mNameEditText.setText(name);
        mPriceEditText.setText(price);
        mQuantityTextView.setText(Integer.toString(quantity));
    }
}
Emily
  • 89
  • 1
  • 11