Main menu

Pages

By examples, the difference between using SQLite and Room

 

SQLite and Room
SQLite and Room

This article explains the difference between using SQLite and using Room to create database for Android applications.
 

What will you read in this article?

  • A quick overview of the project.
  • A simple introduction to SQLite.
  • SQLite components.
  • A simple introduction to the Room.
  • Room components.
  • Creating databases (both Room and SQLite).
  • The use of databases (both Room and SQLite).

Quick overview of the project

overview
overview


The project is an application for keeping notes. divided into two parts. You can browse them as you like to know the difference between the two via the project link here: Room Vs SQLite .

part One:

  • The MainActivity class has a way to use the Room.
  • The Room folder and contains the necessary requirements from the Classes for the Room.

The second part:

  • The Main2Activity class has a method for using SQLite
  • The SQLite folder contains the necessary requirements from the SQLite classes.

A little introduction to SQLite

SQLite
SQLite

The SQLite is an open source library for the database SQL comes as a package , mounted (built in) in the Android system. The method of building a database using SQLite is relatively long, and with the increase in the tasks of the project, the code also increases very dramatically. In my application, the codes responsible for dealing with the database exceeded a thousand lines. Also, the problems that result from it are many and are difficult to solve quickly, and any modification that occurs to the application is accompanied by many modifications in these codes.

SQLite components

we use the following components:

  • 1-Create a DatabaseContract class in which we write the Schema for the database to be created.
  • 2-Create a class DatabaseHelper is used to create the database as its tables and make the necessary changes as an update to it. 
  • 3-Create a class DatabaseProvider used to write the functions responsible for saving, updating and deleting our elements in the database.
  • We use the Content Provider (with the help of the getContentResolver function in any Context as Activity and Fragment) to be able to use these functions.
  • And the LoaderManager through which we load the data as a Cursor from the database.
  • We also use ContentValues ​​to prepare our items before saving or updating them.

Dealing with these things wastes hours, even days, and even weeks of the programmer's work.

A simple introduction to the Room

Room
Room

The Room library came to shorten a lot of code and time for the programmer. It is a layer that covers SQLite only. It is characterized by the speed of creation and conducting tests on it in a smooth manner and the ease of detecting errors in it.

Room . Components

In the Room we use the following components:

  • The DAO interface, which has SQL formulas and data handling functions. It replaced the DatabaseProvider class.
  • Class AppDatabase for database creation. It replaced the Databasehelper class.
  • The ViewModel class has the LiveData component. It is the link between the database and the application, and it has data handling functions such as saving, updating and deleting. It replaced the DatabaseProvider class, and the Loader Manager and Content Provider were no longer used.

Create databases

SQLite and Room
SQLite and Room

In the Room we need to create:

  • Class Entity.
  • DAO interface.
  • Class ViewModel.
  • Class AppDatabase.

In SQLite we need to create:

  • 1-Class DatabaseContract.
  • 2-Class DatabaseHelper.
  • 3-Class DatabaseProvider.
  • 4-We are running implementation for the LoaderManager interface.
  • 5-We deal with the Content Provider.
  • 6-We operate ContentValues ​​items.

Comparison of the code for Room and SQLite:

In the ROM, we need, as a start, to create the Entity, which is a Model Class that has been modified somewhat to match the Room as follows ::

@Entity(tableName = "note_table_name")
public class NoteEntity {
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "note_id")
    private int mId;
    private String mTitle;
    private String mBody;

    // Room Constructor
    public NoteEntity(@NonNull int id, String title, String body) {
        mId = id;
        mTitle = title;
        mBody = body;
    }

    // Our Constructor
    @Ignore
    public NoteEntity(String title, String body) {
        mTitle = title;
        mBody = body;
    }

    public int getId() {
        return mId;
    }

    public void setId(int id) {
        mId = id;
    }

    public String getTitle() {
        return mTitle;
    }

    public void setTitle(String title) {
        mTitle = title;
    }

    public String getBody() {
        return mBody;
    }

    public void setBody(String body) {
        mBody = body;
    }
}


These classes are equivalent to:

Model class::

public class Note {
    private String mTitle;
    private String mBody;

    public Note(String title, String body) {
        mTitle = title;
        mBody = body;
    }

    public String getTitle() {
        return mTitle;
    }

    public void setTitle(String title) {
        mTitle = title;
    }

    public String getBody() {
        return mBody;
    }

    public void setBody(String body) {
        mBody = body;
    }

DatabaseContract class:

public class DatabaseContract {

    // Private Constructor
    private DatabaseContract(){}

    // Content Provider
    public static final String CONTENT_AUTHORITY = "com.mzdhr.roomvssqlite";

    // Base URI's
    public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);

    // Possible Paths
    public static final String PATH_NOTES = "notes";


    public static final class NoteEntry implements BaseColumns{

        // Note Table Uri
        public static final Uri CONTENT_URI_NOTE = Uri.withAppendedPath(BASE_CONTENT_URI, PATH_NOTES);

        // Note Table
        public static final String NOTE_TABLE_NAME = "note_table_name";
        public static final String _ID = BaseColumns._ID;
        public static final String COLUMN_NOTE_TITLE = "note_title";
        public static final String COLUMN_NOTE_BODY = "note_body";

    }
}

Class of Dao in the Room :

@Dao
public interface NoteDao {
    @Query("SELECT * FROM note_table_name")
    LiveData<List<NoteEntity>> getAllNotes();

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertNote(NoteEntity noteEntity);

    @Update(onConflict = OnConflictStrategy.REPLACE)
    void updateNote(NoteEntity noteEntity);

    @Delete
    void deleteNote(NoteEntity noteEntity);
}

This class is equivalent to the following:
DatabaseProvider class:

public class DatabaseProvider extends ContentProvider {
    private static final String TAG = DatabaseProvider.class.getSimpleName();

    private static final int NOTES = 1100;
    private static final int NOTE_ID = 1101;

    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    private DatabaseHelper mDatabaseHelper;

    static {
        sUriMatcher.addURI(DatabaseContract.CONTENT_AUTHORITY, DatabaseContract.PATH_NOTES, NOTES);
        sUriMatcher.addURI(DatabaseContract.CONTENT_AUTHORITY, DatabaseContract.PATH_NOTES + "/#", NOTE_ID);
    }

    @Override
    public boolean onCreate() {
        mDatabaseHelper = new DatabaseHelper(getContext());
        return true;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        SQLiteDatabase database = mDatabaseHelper.getReadableDatabase();
        Cursor cursor;
        int match = sUriMatcher.match(uri);

        switch (match) {
            case NOTES:
                cursor = database.query(DatabaseContract.NoteEntry.NOTE_TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
                break;

            case NOTE_ID:
                selection = DatabaseContract.NoteEntry._ID + "=?";
                selectionArgs = new String[]{String.valueOf(ContentUris.parseId(uri))};
                cursor = database.query(DatabaseContract.NoteEntry.NOTE_TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
                break;

            default:
                throw new IllegalArgumentException(TAG + " Cannot query unknown Uri ---> " + uri);
        }

        cursor.setNotificationUri(getContext().getContentResolver(), uri);
        return cursor;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        final int match = sUriMatcher.match(uri);

        switch (match) {
            case NOTES:
                return insertNote(uri, contentValues);

            default:
                throw new IllegalArgumentException(TAG + " Insertion is not supported for " + uri);
        }
    }

    private Uri insertNote(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        SQLiteDatabase database = mDatabaseHelper.getWritableDatabase();
        long id = database.insert(DatabaseContract.NoteEntry.NOTE_TABLE_NAME, null, contentValues);
        if (id == -1) {
            Log.e(TAG, "insert: Failed to insert a row for " + uri);
        }

        getContext().getContentResolver().notifyChange(uri, null);
        return ContentUris.withAppendedId(uri, id);
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        final int match = sUriMatcher.match(uri);

        switch (match) {
            case NOTE_ID:
                selection = DatabaseContract.NoteEntry._ID + "=?";
                selectionArgs = new String[]{String.valueOf(ContentUris.parseId(uri))};
                return deleteNote(uri, selection, selectionArgs);

            default:
                throw new IllegalArgumentException(TAG + "delete: Deletion is not supported for " + uri);
        }
    }

    private int deleteNote(@NonNull Uri uri, @NonNull String selection, @NonNull String[] selectionArgs) {
        int rowsDeleted;
        SQLiteDatabase database = mDatabaseHelper.getWritableDatabase();
        rowsDeleted = database.delete(DatabaseContract.NoteEntry.NOTE_TABLE_NAME, selection, selectionArgs);

        if (rowsDeleted != 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        return rowsDeleted;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        final int match = sUriMatcher.match(uri);

        switch (match) {
            case NOTE_ID:
                selection = DatabaseContract.NoteEntry._ID + "=?";
                selectionArgs = new String[]{String.valueOf(ContentUris.parseId(uri))};
                return updateNote(uri, values, selection, selectionArgs);

            default:
                throw new IllegalArgumentException("Update is not supported for " + uri);
        }
    }

    private int updateNote(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        if (values.size() == 0) {
            return 0;
        }

        SQLiteDatabase database = mDatabaseHelper.getWritableDatabase();
        int rowsUpdated = database.update(DatabaseContract.NoteEntry.NOTE_TABLE_NAME, values, selection, selectionArgs);

        // Notify the UI
        if (rowsUpdated != 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }

        return rowsUpdated;
    }
}
 
The AppDatabase class in the Room :
@Database(entities = {NoteEntity.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase{

    private static final String TAG = AppDatabase.class.getSimpleName();
    private static final Object LOCK = new Object();
    private static final String DATABASE_NAME = "flashcardsdb";
    private static AppDatabase sInstance;

    public static AppDatabase getInstance(Context context) {
        if (sInstance == null) {
            synchronized (LOCK) {
                Log.d(TAG, "getInstance: Creating a new database instance");
                sInstance = Room.databaseBuilder(
                        context.getApplicationContext(),
                        AppDatabase.class,
                        AppDatabase.DATABASE_NAME
                ).build();
            }
        }
        Log.d(TAG, "getInstance: Getting the database instance, no need to recreated it.");
        return sInstance;
    }


    public abstract NoteDao noteDao();

}
 
This class is equivalent to the following:

DatabaseHelper class:

public class DatabaseHelper extends SQLiteOpenHelper{

    private static final String DATABASE_NAME = "SQLiteNoteDatabase.db";
    private static final int DATABASE_VERSION = 1;

    // SQL COMMANDS
    private String SQL_CREATE_NOTE_TABLE;

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

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        SQL_CREATE_NOTE_TABLE = "CREATE TABLE " + DatabaseContract.NoteEntry.NOTE_TABLE_NAME + " ("
                + DatabaseContract.NoteEntry._ID +  " INTEGER PRIMARY KEY AUTOINCREMENT, "
                + DatabaseContract.NoteEntry.COLUMN_NOTE_TITLE +  " TEXT NOT NULL, "
                + DatabaseContract.NoteEntry.COLUMN_NOTE_BODY +  " TEXT NOT NULL DEFAULT '' )";

        sqLiteDatabase.execSQL(SQL_CREATE_NOTE_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

    }
}

This class of SQLite components is equivalent to the uses of   LoaderManager and Content Provider.

ViewModel class in the room :

public class MainActivityViewModel extends AndroidViewModel {
    private NoteDao mNoteDao;
    private LiveData<List<NoteEntity>> mAllNotes;

    public MainActivityViewModel(@NonNull Application application) {
        super(application);
        AppDatabase appDatabase = AppDatabase.getInstance(application);
        mNoteDao = appDatabase.noteDao();
        mAllNotes = mNoteDao.getAllNotes();
    }

    public LiveData<List<NoteEntity>> getAllNotes() {
        return mAllNotes;
    }

    public void insert(final NoteEntity noteEntity) {
        AsyncTask.execute(new Runnable() {
            @Override
            public void run() {
                mNoteDao.insertNote(noteEntity);
            }
        });
    }

    public void update(final NoteEntity noteEntity) {
        AsyncTask.execute(new Runnable() {
            @Override
            public void run() {
                mNoteDao.updateNote(noteEntity);
            }
        });
    }

    public void delete(final NoteEntity noteEntity) {
        AsyncTask.execute(new Runnable() {
            @Override
            public void run() {
                mNoteDao.deleteNote(noteEntity);
            }
        });
    }
}

This class of SQLite components is equivalent to the uses of   LoaderManager and Content Provider.

Use the two rules

After preparing all these classes and functions (for the Room and SQLite bases) which are considered as tools that help us in building our applications, now we will know which of these tools are easier to use, deal with and build.

Room and SQLite
Room and SQlite

Using the Room

Room
Room

Create a field for the ViewModel class :

private MainActivityViewModel mViewModel;

 

We do init her:

mViewModel = ViewModelProviders.of(this).get(MainActivityViewModel.class);

 

We prepare two items:

NoteEntity noteEntity1 = new NoteEntity("This is first note", "Body for this note");
NoteEntity noteEntity2 = new NoteEntity("This is first note", "Body for this note");

 

Save method:

mViewModel.insert(noteEntity1);
mViewModel.insert(noteEntity2);

 

How to update the first element:

mViewModel.update(new NoteEntity(1,"User", "Body"));

 

How to delete the second element:

mViewModel.delete(noteEntity2);

 

View all items:

mViewModel.getAllNotes().observe(this, new Observer<List<NoteEntity>>() {
    @Override
    public void onChanged(@Nullable List<NoteEntity> noteEntities) {
        for (int i = 0; i < noteEntities.size(); i++) {
            Log.d(TAG, "Title: " + noteEntities.get(i).getTitle() + " Body: " + noteEntities.get(i).getBody());
        }
    }
});

 

Using SQLite:

SQLite
SQLite

first, don't forget to put the Provider inside the application in the AndroidManifest.xml like this:

<!-- SQLite Database Provider -->
<provider
android:name=".SQLite.DatabaseProvider"
android:authorities="com.mzdhr.roomvssqlite"
android:exported="false"/>

 

We prepare two items:

Note note1 = new Note("Note Title", "Note Body");
Note note2 = new Note("Note Title", "Note Body");

 

Save method:

insertNote(note1);
insertNote(note2);

    private void insertNote(Note note) {
        ContentValues contentValues = new ContentValues();
        contentValues.put(DatabaseContract.NoteEntry.COLUMN_NOTE_TITLE, note.getTitle());
        contentValues.put(DatabaseContract.NoteEntry.COLUMN_NOTE_BODY, note.getBody());

        Uri insertUri = getContentResolver().insert(DatabaseContract.NoteEntry.CONTENT_URI_NOTE, contentValues);

        if (insertUri == null) {
            Log.d(TAG, "insertNote: Insert failed!");
        } else {
            Log.d(TAG, "insertNote: Insert successful");
            int insertedNoteId = Integer.valueOf(insertUri.getLastPathSegment());
            Log.d(TAG, "insertNote: Insert ID: " + insertedNoteId);
        }

    }

 

How to update the first element:

Note updateNote = new Note("User", "This is user one");
updateNote(updateNote, 1);

    private void updateNote(Note note, int noteId) {
        ContentValues contentValues = new ContentValues();
        contentValues.put(DatabaseContract.NoteEntry.COLUMN_NOTE_TITLE, note.getTitle());
        contentValues.put(DatabaseContract.NoteEntry.COLUMN_NOTE_BODY, note.getBody());

        Uri updateUri = ContentUris.withAppendedId(DatabaseContract.NoteEntry.CONTENT_URI_NOTE, noteId);

        int rowsUpdated = getContentResolver().update(updateUri, contentValues, null, null);
        if (rowsUpdated == 0) {
            Log.d(TAG, "updateNote: update failed");
        } else {
            Log.d(TAG, "updateNote: update successful");
        }
    }

 

How to delete the second element:

deleteNote(2);

    private void deleteNote(int noteId) {
        Uri deleteUri = ContentUris.withAppendedId(DatabaseContract.NoteEntry.CONTENT_URI_NOTE, noteId);
        int rowsDeleted = getContentResolver().delete(deleteUri, null, null);
        if (rowsDeleted == 0 ) {
            Log.d(TAG, "deleteNote: Delete failed");
        } else {
            Log.d(TAG, "deleteNote: delete successful");
        }
    }

 

View all items:

An implementaion for the LoaderManager interface for the Activity to be used in:

public class Main2Activity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor>{
...
}

 

Preparing a field for Loader:

private static final int ALL_NOTE_LOADER = 100;

 

Run this Loader:

getLoaderManager().initLoader(ALL_NOTE_LOADER, null, this).forceLoad();

 

Override Functions:

    private void updateNote(Note note, int noteId) {
        ContentValues contentValues = new ContentValues();
        contentValues.put(DatabaseContract.NoteEntry.COLUMN_NOTE_TITLE, note.getTitle());
        contentValues.put(DatabaseContract.NoteEntry.COLUMN_NOTE_BODY, note.getBody());

        Uri updateUri = ContentUris.withAppendedId(DatabaseContract.NoteEntry.CONTENT_URI_NOTE, noteId);

        int rowsUpdated = getContentResolver().update(updateUri, contentValues, null, null);
        if (rowsUpdated == 0) {
            Log.d(TAG, "updateNote: update failed");
        } else {
            Log.d(TAG, "updateNote: update successful");
        }
    }

    /**
     * Loader Section
     */
    @Override
    public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
        String[] projection = {
                DatabaseContract.NoteEntry.COLUMN_NOTE_TITLE,
                DatabaseContract.NoteEntry.COLUMN_NOTE_BODY,
        };

        return new CursorLoader(this,
                DatabaseContract.NoteEntry.CONTENT_URI_NOTE,
                projection,
                null,
                null,
                null
        );
    }

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

        if (cursor == null || cursor.getCount() < 1) {
            Log.d(TAG, "onLoadFinished: Cursor is null or there is less than 1 row in the cursor");
            return;
        }

        for (int i = 0; i < cursor.getCount(); i++) {
            // Getting Indexes
            cursor.moveToNext();
            int noteTitleIndex = cursor.getColumnIndex(DatabaseContract.NoteEntry.COLUMN_NOTE_TITLE);
            int noteBodyIndex = cursor.getColumnIndex(DatabaseContract.NoteEntry.COLUMN_NOTE_BODY);

            // Getting Values
            String noteTitle = cursor.getString(noteTitleIndex);
            String noteBody = cursor.getString(noteBodyIndex);

            Log.d(TAG, "Title: " + noteTitle + " Body: " + noteBody);
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {

    } 

end of article

you can read also :

Google Play App Store | How to publish your own Android app step by step

reactions

Comments