Android App with SQLite runs in ICS but not Jelly Bean - IllegalStateException

701 views Asked by At

I've developed an app for Android which stores car filling information in an SQLite database. Everything seems to work fine on my 4.0.3 phone and my 4.0.3 AVD, but I would like it to also work in Jelly Bean and newer versions. The app really doesn't do anything very complicated. When I fire the app up in the emulated Jelly Bean device I get a FATAL EXCEPTION error:

09-05 01:45:40.311: W/dalvikvm(1062): threadid=1: thread exiting with uncaught exception >(group=0x414c4700) 09-05 01:45:40.331: E/AndroidRuntime(1062): FATAL EXCEPTION: main 09-05 01:45:40.331: E/AndroidRuntime(1062): java.lang.RuntimeException: Unable to start >activity ComponentInfo{ard.util.fueltracker/ard.util.fueltracker.TitleScreenActivity}: >java.lang.IllegalStateException: attempt to re-open an already-closed object: >SQLiteDatabase: /data/data/ard.util.fueltracker/databases/vehicleDatabase 09-05 01:45:40.331: E/AndroidRuntime(1062): at >android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2211)

In my initial searches in Google I thought I had Instance issues somehow, so I followed Approach #1 here (http://www.androiddesignpatterns.com/2012/05/correctly-managing-your-sqlite-database.html), but this doesn't seem to make a difference. Still works in ICS with the changes too.

Here's the code from my TitleScreenActivity:

package ard.util.fueltracker;

import java.util.List;

import ard.util.fueltracker.util.SystemUiHider;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.content.Intent;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.Toast;

/**
 * An example full-screen activity that shows and hides the system UI (i.e.
 * status bar and navigation/system bar) with user interaction.
 * 
 * @see SystemUiHider
 */
@SuppressLint("NewApi")
public class TitleScreenActivity extends Activity implements OnItemSelectedListener {
    /**
     * Whether or not the system UI should be auto-hidden after
     * {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
     */
    private static final boolean AUTO_HIDE = false;

    /**
     * If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after
     * user interaction before hiding the system UI.
     */
    private static final int AUTO_HIDE_DELAY_MILLIS = 3000;

    /**
     * If set, will toggle the system UI visibility upon interaction. Otherwise,
     * will show the system UI visibility upon interaction.
     */
    private static final boolean TOGGLE_ON_CLICK = false;

    /**
     * The flags to pass to {@link SystemUiHider#getInstance}.
     */
    //private static final int HIDER_FLAGS = SystemUiHider.FLAG_HIDE_NAVIGATION;
    private static final int HIDER_FLAGS = 0;

    /**
     * The instance of the {@link SystemUiHider} for this activity.
     */
    private SystemUiHider mSystemUiHider;

    // Spinner element
    Spinner spinner;

    // Buttons
    Button btnAdd;
    Button btnLogo;


    @SuppressLint("NewApi")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);   //new
        getActionBar().hide();                                   //new
        getWindow().setFlags(
             WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);

        setContentView(R.layout.activity_title_screen);

        // Spinner element
        spinner = (Spinner) findViewById(R.id.titleSelectionSpinner);

        // buttons
        btnAdd = (Button) findViewById(R.id.button_go);
        btnLogo = (Button) findViewById(R.id.button_ard_logo);

        // Spinner click listener
        spinner.setOnItemSelectedListener(this);

        // Check for Add New Vehicle entry to create menu option in drop down list
        checkAddNewVehicle();

        // Loading spinner data from database
        loadSpinnerData();    

        //"Go" button actions
        btnAdd.setOnClickListener(new View.OnClickListener() {
            public void onClick(View arg0) {
                //Starting a new Intent
                Intent screenAddVehicle = new Intent(getApplicationContext(), AddVehicleActivity.class);
                Intent screenRecordViewing = new Intent(getApplicationContext(), RecordViewing.class);
                String SpinnerChoice = spinner.getSelectedItem().toString();

                //Sending data to another Activity
                //store value of vehicle label for Record Viewing screen 
                screenRecordViewing.putExtra("vehicleLabel", SpinnerChoice);

                //interpret Spinner choice as Menu items
                //"Add New Vehicle" always at Position 0
                if (SpinnerChoice == spinner.getItemAtPosition(0).toString()) {
                startActivity(screenAddVehicle);
                } else {
                    //display value of selection if not "Add New Vehicle"
                    //Toast.makeText(spinner.getContext(), "Selection:" + SpinnerChoice, Toast.LENGTH_LONG).show();

                    //go to Record Viewing screen
                    startActivity(screenRecordViewing);
                }

            }
        });

        //"Logo" button - display program copyright
        btnLogo.setOnClickListener(new View.OnClickListener() {
            public void onClick(View arg0) {
                Toast.makeText(getApplicationContext(), "FuelTracker is Copyright 2013 by Authentic Ruby Designs", Toast.LENGTH_LONG).show();
            }


        });

        final View controlsView = findViewById(R.id.fullscreen_content_controls);
        final View contentView = findViewById(R.id.fullscreen_content);

        // Set up an instance of SystemUiHider to control the system UI for
        // this activity.
        mSystemUiHider = SystemUiHider.getInstance(this, contentView,
                HIDER_FLAGS);
        mSystemUiHider.setup();
        mSystemUiHider
                .setOnVisibilityChangeListener(new SystemUiHider.OnVisibilityChangeListener() {
                    // Cached values.
                    int mControlsHeight;
                    int mShortAnimTime;

                    @Override
                    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
                    public void onVisibilityChange(boolean visible) {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
                            // If the ViewPropertyAnimator API is available
                            // (Honeycomb MR2 and later), use it to animate the
                            // in-layout UI controls at the bottom of the
                            // screen.
                            if (mControlsHeight == 0) {
                                mControlsHeight = controlsView.getHeight();
                            }
                            if (mShortAnimTime == 0) {
                                mShortAnimTime = getResources().getInteger(
                                        android.R.integer.config_shortAnimTime);
                            }
                            controlsView
                                    .animate()
                                    .translationY(visible ? 0 : mControlsHeight)
                                    .setDuration(mShortAnimTime);
                        } else {
                            // If the ViewPropertyAnimator APIs aren't
                            // available, simply show or hide the in-layout UI
                            // controls.
                            controlsView.setVisibility(visible ? View.VISIBLE
                                    : View.GONE);
                        }

                        if (visible && AUTO_HIDE) {
                            // Schedule a hide().
                            delayedHide(AUTO_HIDE_DELAY_MILLIS);
                        }
                    }
                });

        // Set up the user interaction to manually show or hide the system UI.
        contentView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (TOGGLE_ON_CLICK) {
                    mSystemUiHider.toggle();
                } else {
                    mSystemUiHider.show();
                }
            }
        });

        // Upon interacting with UI controls, delay any scheduled hide()
        // operations to prevent the jarring behavior of controls going away
        // while interacting with the UI.
        findViewById(R.id.button_go).setOnTouchListener(
                mDelayHideTouchListener);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        // Trigger the initial hide() shortly after the activity has been
        // created, to briefly hint to the user that UI controls
        // are available.
        //delayedHide(100);
    }

    /**
     * Touch listener to use for in-layout UI controls to delay hiding the
     * system UI. This is to prevent the jarring behavior of controls going away
     * while interacting with activity UI.
     */
    View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            if (AUTO_HIDE) {
                delayedHide(AUTO_HIDE_DELAY_MILLIS);
            }
            return false;
        }
    };

    Handler mHideHandler = new Handler();
    Runnable mHideRunnable = new Runnable() {
        @Override
        public void run() {
            mSystemUiHider.hide();
        }
    };

    /**
     * Schedules a call to hide() in [delay] milliseconds, canceling any
     * previously scheduled calls.
     */
    private void delayedHide(int delayMillis) {
        mHideHandler.removeCallbacks(mHideRunnable);
        mHideHandler.postDelayed(mHideRunnable, delayMillis);
    }
    @Override
    public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2,
            long arg3) {
        // TODO Auto-generated method stub

    }
    @Override
    public void onNothingSelected(AdapterView<?> arg0) {
        // TODO Auto-generated method stub

    }

    /**
     * Function to check that Add New Vehicle is the first label in the DB table
     */
    private void checkAddNewVehicle() {
        // database handler
        //DatabaseHandler db = new DatabaseHandler(getApplicationContext());
        DatabaseHandler db = DatabaseHandler.getInstance(getApplicationContext());

        // table data
        List<String> labels = db.getAllLabels();

        //Check for "Add New Vehicle" entry at first row of table
        if (labels.isEmpty() ) {
            db.insertLabel("Add New Vehicle");
        }
    }

    /**
     * Function to load the spinner data from SQLite database
     * */
    private void loadSpinnerData() {
        // database handler
        //DatabaseHandler db = new DatabaseHandler(getApplicationContext());
        DatabaseHandler db = DatabaseHandler.getInstance(getApplicationContext());

        // Spinner Drop down elements
        List<String> labels = db.getAllLabels();


        // Creating adapter for spinner
        ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_spinner_item, labels);

        // Drop down layout style - list view with radio button
        dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

        // attaching data adapter to spinner
        spinner.setAdapter(dataAdapter);
    }



}

And here's my DatabaseHandler class:

package ard.util.fueltracker;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.widget.TableLayout;

public class DatabaseHandler extends SQLiteOpenHelper {
    //create static instance
    private static DatabaseHandler mInstance = null;

    // Database Version
    private static final int DATABASE_VERSION = 1;

    // Database Name
    private static final String DATABASE_NAME = "vehicleDatabase";

    // Labels table name
    private static final String TABLE_LABELS = "labels";

    // Fuel Data table name
    private static final String TABLE_FUELDATA = "fuel_data";

    // Labels Table Columns names
    private static final String KEY_ID = "id";
    private static final String KEY_NAME = "name";

    // Fuel Data Tables Columns names
    private static final String KEY_ENTRY = "entry_id";
    private static final String FIELD_VEHICLEID = "vehicle_id";
    private static final String FIELD_DATE = "date";
    private static final String FIELD_FTYPE = "fuel_type";
    private static final String FIELD_BRAND = "brand";
    private static final String FIELD_PRICE = "price";
    private static final String FIELD_KMS = "kms";
    private static final String FIELD_LITRES = "litres";
    private static final String FIELD_LPER = "l_per";
    private static final String FIELD_MPG = "mpg";
    // Fuel Data Columns for display - query shorthand
    private static final String[] fuelDataDisplayCols = { FIELD_DATE, FIELD_FTYPE, FIELD_BRAND, FIELD_PRICE,
        FIELD_KMS, FIELD_LITRES, FIELD_LPER, FIELD_MPG };

    public static DatabaseHandler getInstance(Context ctx) {
        //Use the application context to avoid leaking Activity's context
        if (mInstance == null) {
            mInstance = new DatabaseHandler(ctx.getApplicationContext());
        }
        return mInstance;
    }

    private DatabaseHandler(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    // Creating Tables
    @Override
    public void onCreate(SQLiteDatabase db) {
        // Category table create query
        String CREATE_CATEGORIES_TABLE = "CREATE TABLE " + TABLE_LABELS + "("
                + KEY_ID + " INTEGER PRIMARY KEY," + KEY_NAME + " TEXT)";         
        db.execSQL(CREATE_CATEGORIES_TABLE);

        // Fuel Data table create query
        String CREATE_FUELDATA_TABLE = "CREATE TABLE " + TABLE_FUELDATA + "("
                + KEY_ENTRY + " INTEGER PRIMARY KEY," + FIELD_VEHICLEID + " INTEGER,"
                + FIELD_DATE + " TEXT," + FIELD_FTYPE + " TEXT," + FIELD_BRAND + 
                " TEXT," + FIELD_PRICE + " REAL," + FIELD_KMS + " REAL," 
                + FIELD_LITRES + " REAL," + FIELD_LPER + " REAL," + FIELD_MPG +
                " REAL)";
        db.execSQL(CREATE_FUELDATA_TABLE);
        db.close();

    }

    // Upgrading database
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // Drop older table if existed
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_LABELS);
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_FUELDATA);

        // Create tables again
        onCreate(db);
    }

    /**
     * Inserting new label into Labels table
     * */
    public void insertLabel(String label){
        SQLiteDatabase db = this.getWritableDatabase();

        ContentValues values = new ContentValues();
        values.put(KEY_NAME, label);

        // Inserting Row
        db.insert(TABLE_LABELS, null, values);
        db.close(); // Closing database connection
    }

    /**
     * Inserting entry data into Fuel Data table
     */
    //public void insertFuelData(int vehicleID, String date, String ftype, String brand, 
    //      float price, float kms, float litres) {
    public void insertFuelData(ArrayList<String> fuelEntryData) {
        SQLiteDatabase dbWrite = this.getWritableDatabase();
        ArrayList<String> fuelEntry = fuelEntryData;
        ContentValues values = new ContentValues();

        //turn array values into individual field values with correct datatype
        int vehicleID = getVehicleID(fuelEntry.get(0)); 
        String date = fuelEntry.get(1);
        String ftype = fuelEntry.get(2);
        String brand = fuelEntry.get(3);
        double price = Double.parseDouble((fuelEntry.get(4)));
        double kms = Double.parseDouble((fuelEntry.get(5)));
        double litres = Double.parseDouble((fuelEntry.get(6)));     

        //values for calculations 
        double lper;
        double mpg;
        //Format calculations to round to one significant digit
        DecimalFormat df = new DecimalFormat("###.#");

        //calculate Liters/100Kilometres
        lper = (100 / (kms / litres));
        //calculate U.S. Miles Per Gallon
        mpg = (kms / litres) * 2.35;

        //Prepare data and Insert row into table
        values.put(FIELD_VEHICLEID, vehicleID);
        values.put(FIELD_DATE, date);
        values.put(FIELD_FTYPE, ftype);
        values.put(FIELD_BRAND, brand);
        values.put(FIELD_PRICE, price);
        values.put(FIELD_KMS, kms);
        values.put(FIELD_LITRES, litres);
        values.put(FIELD_LPER, df.format(lper));
        values.put(FIELD_MPG, df.format(mpg));

        dbWrite.insert(TABLE_FUELDATA, null, values);
        dbWrite.close();        

    }

    /**
     * Getting all labels
     * returns list of labels
     * */
    public List<String> getAllLabels(){
        List<String> labels = new ArrayList<String>();

        // Select All Query
        String selectQuery = "SELECT  * FROM " + TABLE_LABELS;

        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.rawQuery(selectQuery, null);

        // looping through all rows and adding to list
        if (cursor.moveToFirst()) {
            do {
                labels.add(cursor.getString(1));
            } while (cursor.moveToNext());
        }

        // closing connection
        cursor.close();
        db.close();

        // returning lables
        return labels;
    }

    /**
     * Get entries based on vehicle id
     */
    public List<List<String>> getFuelData(int vehicleID) {

        //List of Lists - each row/entry of data is a separate List
        List<List<String>> fuelDataTable = new ArrayList<List<String>>();
        List<String> fuelDataRow = new ArrayList<String>();

        //Select Query
        String selectQuery = "SELECT * FROM " + TABLE_FUELDATA +
                " WHERE vehicle_id = ? ";

        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.rawQuery(selectQuery, new String[] { String.valueOf(vehicleID) });

        // looping through all rows and 9 columns (not incl key) and adding to list
        if (cursor.moveToFirst()) {
            do {
                for(int i = 1; i < 10; i++) {
                    //doesn't work with non-string values
                    //fuelDataRow.add(cursor.getString(i));
                }
                //add each row in its entirety to the List; multi-dimenionsal list
                fuelDataTable.add(fuelDataRow);
                //empty fuelDataRow
                fuelDataRow.clear();
            } while (cursor.moveToNext());
        }

        // closing connection
        cursor.close();
        db.close();

        //return data
        return fuelDataTable;
    }

    public List<String> getFuelDataRows(int vehicleID) {
        List<String> fuelDataTable = new ArrayList<String>();
        SQLiteDatabase db = this.getReadableDatabase();

        //Get data from database table
        Cursor c = db.query(TABLE_FUELDATA, fuelDataDisplayCols, " vehicle_id=? ", new String[] { String.valueOf(vehicleID) }, null, null, FIELD_DATE);

        //Insert Header row into Array
        fuelDataTable.add("Date");
        fuelDataTable.add("Type");
        fuelDataTable.add("Brand");
        fuelDataTable.add("Price");
        fuelDataTable.add("KMs");
        fuelDataTable.add("Litres");
        fuelDataTable.add("L/100");
        fuelDataTable.add("MPG");

        //Go to beginning of Cursor data and loop through
        c.moveToFirst();
        while (!c.isAfterLast()) {
            //add each cell in the row to List array    
            fuelDataTable.add(c.getString(c.getColumnIndex(FIELD_DATE)));
            fuelDataTable.add(c.getString(c.getColumnIndex(FIELD_FTYPE)));
            fuelDataTable.add(c.getString(c.getColumnIndex(FIELD_BRAND)));
            fuelDataTable.add(String.valueOf(c.getDouble(c.getColumnIndex(FIELD_PRICE))));
            fuelDataTable.add(String.valueOf(c.getDouble(c.getColumnIndex(FIELD_KMS))));
            fuelDataTable.add(String.valueOf(c.getDouble(c.getColumnIndex(FIELD_LITRES))));
            fuelDataTable.add(String.valueOf(c.getDouble(c.getColumnIndex(FIELD_LPER))));
            fuelDataTable.add(String.valueOf(c.getDouble(c.getColumnIndex(FIELD_MPG))));
            c.moveToNext();
        }
        // Make sure to close the cursor
        c.close();
        db.close();

        return fuelDataTable;
    }


    public int getVehicleID(String label) {
        // set variables
        SQLiteDatabase dbReader = this.getReadableDatabase();
        String vLabel = label;


        //Query String
        String selectQuery = "SELECT " + KEY_ID + " FROM " + TABLE_LABELS +
                " WHERE name = ? ";
        Cursor c = dbReader.rawQuery(selectQuery, new String[] { vLabel }); 

        //avoid out of bounds exception
        c.moveToFirst();        

        //extract value as integer
        int vID = c.getInt(c.getColumnIndex(KEY_ID));
        c.close();
        return vID;
    }
}

Some of it probably looks ugly - this is my first app project and Eclipse auto-populated some of the methods upon creation - but it is working OK in Ice Cream Sandwich.

Thanks for any clues.

1

There are 1 answers

2
Varun On BEST ANSWER

db.close() is not needed in public void onCreate(). This will probably fix your problem.