Search code examples
androiddatabasesqliteandroid-sqliteandroid-database

How can I fix my Android SQLite Database?


I'm a beginner programmer making an android app, so I really need your help. I know that my question is super long, but I will be grateful if you guys answer my quest

I am making an app that selects 1 word randomly from many words in a table.

Thus, I made my Database with SQLite. I tried these things.

  1. I made WordDBHelper to use SQLiteOpenHelper
  2. I made WordDBRecord to put records in a table
  3. I made WordActivty to open DB and to see a word selected from a table through TextView.setText().

These are my codes

  1. Word Activity
public class WordActivity extends AppCompatActivity {

    private WordDBRecord wordDBRecord;
    String category = wordDBRecord.category;
    String word = wordDBRecord.word;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_word);
        TextView t1 = (TextView) findViewById(R.id.textViewCategory);
        TextView t2 = (TextView) findViewById(R.id.textViewWord);

        wordDBRecord = new WordDBRecord(this);
        wordDBRecord.open();
        wordDBRecord.DBSearch("KeyWordDB");
        t1.setText(category);
        t2.setText(word);
        wordDBRecord.close();
        }
    }
  1. WordDBHelper(using SQLite Open Helper)
    import static android.content.ContentValues.TAG;

    public class WordDBHelper extends SQLiteOpenHelper {
         static final String TABLE_NAME = "KeyWordDB";

    public WordDBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        Log.d(TAG, "DataBaseHelper");
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        Log.d(TAG, "Table Create");

        String createQuery = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME +
                "( ID INTEGER PRIMARY KEY AUTOINCREMENT, " +
                "CATEGORY TEXT NOT NULL, " +
                "WORD TEXT NOT NULL);";
        sqLiteDatabase.execSQL(createQuery);
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
        Log.d(TAG, "Table onUpgrade");
        String createQuery = "DROP TABLE IF EXISTS " + TABLE_NAME + ";";
        sqLiteDatabase.execSQL(createQuery);
        }
    }

3.WordDBRecord( Class putting Records)

    import static android.content.ContentValues.TAG;

    public class WordDBRecord {
    public String id;
    public String category;
    public String word;
    private Context context;
    private WordDBHelper dbHelper;
    private SQLiteDatabase database;

    public WordDBRecord(Context c) {
        context = c;
    }

    public WordDBRecord open() throws SQLException {
        dbHelper = new WordDBHelper(context, "DB", null, 1);
        database = dbHelper.getWritableDatabase();

          dbInsert("KeyWordDB", "Movie", "Harry Potter");

        return this;
    }

    public void DBSearch(String tableName) {
        Cursor cursor = null;
        try {
            cursor = database.query(tableName, null, null, null, null, null, null);
            if (cursor != null) {
                while (cursor.moveToNext()) {
                    id = cursor.getString(cursor.getColumnIndex("ID"));
                    category = cursor.getString(cursor.getColumnIndex("CATEGORY"));
                    word = cursor.getString(cursor.getColumnIndex("WORD"));
                }
            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }

    public void close() {
        database.close();
        dbHelper.close();
    }

    public void dbInsert(String tableName, String category, String word) {
        Log.d(TAG, "Insert Data ");

        ContentValues contentValues = new ContentValues();

        contentValues.put("CATEGORY", category);
        contentValues.put("WORD", word);

        long id = database.insert(tableName, null, contentValues);
        Log.d(TAG, "id: " + id);
    }

    }

So I ran my app but I failed to open an app. My app stopped when I clicked my app. I received those Logcats

    2019-12-15 11:37:59.005 22791-22791/org.techtown.worddbpractice E/AndroidRuntime: FATAL EXCEPTION: main
    Process: org.techtown.worddbpractice, PID: 22791
    java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{org.techtown.worddbpractice/org.techtown.worddbpractice.WordActivity}: java.lang.NullPointerException: Attempt to read from field 'java.lang.String org.techtown.worddbpractice.WordDBRecord.category' on a null object reference
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2849)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3045)
        at android.app.ActivityThread.-wrap14(ActivityThread.java)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1642)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6776)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1496)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1386)
     Caused by: java.lang.NullPointerException: Attempt to read from field 'java.lang.String org.techtown.worddbpractice.WordDBRecord.category' on a null object reference
        at org.techtown.worddbpractice.WordActivity.<init>(WordActivity.java:17)
        at java.lang.Class.newInstance(Native Method)
        at android.app.Instrumentation.newActivity(Instrumentation.java:1086)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2839)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3045) 
        at android.app.ActivityThread.-wrap14(ActivityThread.java) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1642) 
        at android.os.Handler.dispatchMessage(Handler.java:102) 
        at android.os.Looper.loop(Looper.java:154) 
        at android.app.ActivityThread.main(ActivityThread.java:6776) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1496) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1386) 

I really want to fix these problems, but I don't know how... I will be really really grateful, if someone answers my super long question. Thank you


Solution

  • Explanation

    The issue you are facing is that when you use private WordDBRecord wordDBRecord; this declares the variable wordDBRecord as being of type WordDBRecord, which is not a primative type but an Object.

    Declaring a variable that is not a primative type results in that variable being null (i.e no instance of the object exists (the object has not been created)).

    • In link above in the section entitle Default Values, it includes :- enter image description here

    So when the next line String category = wordDBRecord.category; is encountered there is no object wordDBRecord instead the pointer to the object is set to null to indicate this. So when the asttempt is made to get the category from the object you get the null pointer exception (you cannot get something from nothing).

    If the next line String word = wordDBRecord.word; were to be encountered the same issue would result.

    How to Fix (the null pointer exception)

    The fix is simple, do not try to set that values for category and word by trying to use wordDBRecord until wordDBRecord has been instantiated. The instantiation is done by the line wordDBRecord = new WordDBRecord(this);.

    As such you want to only declare the category and the word variables. e.g.

    public class WordActivity extends AppCompatActivity {
    
        private WordDBRecord wordDBRecord; //<<<<< only declares wordDBrecord so it is null
        String category; //<<<<< CHANGED TO ONLY DECLARE category
        String word; //<<<<< CHANGED TO ONLY DECALRE word
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            TextView t1 = (TextView) findViewById(R.id.textViewCategory);
            TextView t2 = (TextView) findViewById(R.id.textViewWord);
    
            wordDBRecord = new WordDBRecord(this); //<<<<< instantiates wordDBRecord so it is now not null
            wordDBRecord.open();
            wordDBRecord.DBSearch("KeyWordDB");
            t1.setText(category);
            t2.setText(word);
            wordDBRecord.close();
        }
    }
    

    HOWEVER - Complete FIX

    After applying the fix you will not see Movie and Harry Potter in the TextViews.

    This is because when the above runs it declares category and word but they are never set (they are null but the setText method checks for this and bypasses the null pointer exception). You would need to set the values e.g. after the line wordDBRecord.DBSearch("KeyWordDB"); use :-

            category = wordRecordDB.category;
            word = wordRecordDB.word;
            t1.setText(category);
            t2.setText(word);
    

    As you can get the values directly from the wordDBRecord object. Having variable category and word is not even required. As such you could use the more compact :-

    public class WordActivity extends AppCompatActivity {
    
        private WordDBRecord wordDBRecord; //<<<<< only declares wordDBrecord so it is null
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            TextView t1 = (TextView) findViewById(R.id.textViewCategory);
            TextView t2 = (TextView) findViewById(R.id.textViewWord);
    
            wordDBRecord = new WordDBRecord(this); //<<<<< instantiates wordDBRecord so it is now not null
            wordDBRecord.open();
            wordDBRecord.DBSearch("KeyWordDB");
            t1.setText(wordDBRecord.category);
            t2.setText(wordDBRecord.word);
            wordDBRecord.close();
        }
    }
    

    Using the above rsults in :-

    enter image description here

    With the log containing :-

    2019-12-16 10:14:37.773 D/Constraints: DataBaseHelper
    2019-12-16 10:14:37.789 D/Constraints: Table Create
    2019-12-16 10:14:37.794 D/Constraints: Insert Data 
    2019-12-16 10:14:37.798 D/Constraints: id: 1
    

    Additional

    On a similar subject matter the SQliteDatabase query method will never return a Cursor that is null. Setting the Cursor to null before retrieving it and checking for null after retrieving it is not required.

    As such your DBSearch method could be :-

    public void DBSearch(String tableName) {
        Cursor cursor = database.query(tableName,null,null,null,null,null,null);
        while (cursor.moveToNext()) {
            id = cursor.getString(cursor.getColumnIndex("ID"));
            category = cursor.getString(cursor.getColumnIndex("CATEGORY"));
            word = cursor.getString(cursor.getColumnIndex("WORD"));
        }
        cursor.close();
    }