Loklak Wok Android provides suggestions for tweet searches. The suggestions are stored in local database to provide a persistent view, resulting in a better user experience. The local database used here is Realm database instead of sqlite3 which is supported by Android SDK. The proper way to use an sqlite3 database is to first create a contract where the schema of the database is defined, then a database helper class which extends from SQLiteOpenHelper class where the schema is created i.e. tables are created and finally write ContentProvider so that you don’t have to write long SQL queries every time a database operation needs to be performed. This is just a lot of hard work to do, as this includes a lot of steps, debugging is also difficult. A solution to this can be using an ORM that provides a simple API to use sqlite3, but the currently available ORMs lack in terms of performance, they are too slow. A reliable solution to this problem is realm database, which is faster than raw sqlite3 and has really simple API for database operations. This blog explains the use of realm database for storing tweet search suggestions.
Adding Realm database to Android project
In project level build.gradle
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.3.3' classpath "io.realm:realm-gradle-plugin:3.3.1" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } }
And at the top of app/build.gradle “apply plugin: ‘realm-android'” is added.
Using Realm Database
Let’s start with a simple example. We have a Student class that has only two attributes name and age. To create the model for the database, the Student class is simply extended to RealmObject.
public class Student extends RealmObject { private String name; private int age; // A constructor needs to be explicitly defined, be it an empty constructor public Student(String name, int age) { this.name = name; this.age = age; } // getters and setters }
To push data to the database, Java objects are created, a transaction is initialized, then copyToRealm method is used to push the data and finally the transaction is committed. But before all this, the database is initialized and a Realm instance is obtained.
Realm.init(context); // Database initialized Realm realm = Realm.getDefaultInstance(); // realm instance obtained Student student = new Student("Rahul Dravid", 22); // Simple java object created realm.beginTransaction() // initialization of transaction realm.copyToRealm(student); // pushed to database realm.commitTransaction(); // transaction committed
copyToRealm takes only a single parameter, the parameter can be an object or an Iterable. Off course, the passed parameter should extend RealmObject. A List of Student can be passed as a parameter to copyToRealm to push multiple data into the database.
The above way of inserting data is synchronous. Realm also supports asynchronous transactions, you guessed it right, you don’t have to depend on AsyncTaskLoader. The same operation can be performed asynchronously as
realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { Student student = new Student("Rahul Dravid", 22); realm.copyToRealm(student); } });
Now, querying the database is as easy as inserting.
RealmResults<Student> studentList = realm.where(Student.class).findAll();
No, transaction is required as we are not manipulating the database, as data is just read from the database. RealmResults extend Java List, so List methods which don’t manipulate the List can be used on studentList e.g. get(int index) to obtain object at the index.
The result can also be a filtered one, for example filtering students who are 22 years old.
RealmResults<Student> studentList = realm.where(Student.class).equalTo(“age”, 22).findAll();
Now, removing data from database. deleteAllFromRealm method can be executed on the obtained RealmResults or to completely remove data of a model class delete(Model.class) method on the realm instance is invoked. The operations should be enclosed between beginTransaction and commitTransaction if synchronous behaviour is required else for asynchronous behaviour the operation is done in execute method of an anonymous object of Realm.Transaction.
studentList.deleteAllFromRealm(); // removes the filtered result realm.delete(Student.class); // removes all data of model class
Storing Tweet Search Suggestions in Loklak Wok Android for Persistent view
Loklak Wok Android uses Retrofit2 for sending network requests, for which POJO classes are already created so that it becomes easy for parsing the obtained JSON from network request. Due to this using Realm database becomes more easier, as the defined POJOs can be simply extended to RealmObject to create the model class of the data e.g. Query class extends RealmObject, one of the attribute is the suggestion query i.e. mQuery.
The database is initialized in LoklakWokApplication, the application class, this way the database is initialized only once which persists throughout the app lifecycle.
@Override public void onCreate() { super.onCreate(); Realm.init(this); RealmConfiguration realmConfiguration = new RealmConfiguration.Builder() .name(Realm.DEFAULT_REALM_NAME) .deleteRealmIfMigrationNeeded() .build(); Realm.setDefaultConfiguration(realmConfiguration); }
deleteRealmIfMigrationNeeded removes the old realm database and creates a new one if any of the Model class get changed i.e. an attribute is removed, added or simply the name of attribute is changed. This is done as we are not storing user generated data, we are just using database to provide persistent view. So, the previously kept data is not important.
The database is closed in onTerminate callback of the application
@Override public void onTerminate() { Realm.getDefaultInstance().close(); super.onTerminate(); }
Now that database is initialized. We fetch the previously stored data and display it in RecyclerView. If the network request is successful the queries from database are replaced by the queries fetched in network request, else the queries from database are displayed providing a persistent view. The way it is implemented in onCreateView of SuggestFragment
mRealm = Realm.getDefaultInstance(); ... // old queries obtained from database RealmResults<Query> queryRealmResults = mRealm.where(Query.class).findAll(); List<Query> queries = mRealm.copyFromRealm(queryRealmResults); // RecyclerView adapter created with old queries mSuggestAdapter = new SuggestAdapter(queries, this); tweetSearchSuggestions.setLayoutManager(new LinearLayoutManager(getActivity())); tweetSearchSuggestions.setAdapter(mSuggestAdapter);
The old queries are replaced in onSuccessfulRequest
private void onSuccessfulRequest(SuggestData suggestData) { // suggestData contains suggestion queries if (suggestData != null) { // old queries replaced with new ones mSuggestAdapter.setQueries(suggestData.getQueries()); } setAfterRefreshingState(); }
Now suggestion queries needs to be inserted into the database. Only the latest suggestions are inserted i.e. queries present when onStop lifecycle method of fragment is called, and as previous queries are not needed anymore, they are deleted. The operation is performed in a synchronous way.
@Override public void onStop() { ... mRealm.beginTransaction(); // old queries deleted mRealm.delete(Query.class); // new queries inserted mRealm.copyToRealm(mSuggestAdapter.getQueries()); mRealm.commitTransaction(); // fragment lifecycle called i.e. a new fragment/activity opens super.onStop(); }
Conclusion: Sqlite3 and Realm comparison
Operations | Sqlite3 | Realm |
Table creation | CREATE TABLE … | extends RealmObject |
Inserting data | INSERT INTO … | copyToRealm |
Searching data | SELECT … | realm.where(Model.class) |
Deleting data | DELETE FROM … | realmResults.deleteAllFromRealm() or realm.delete(Model.class) |
Resources:
- Realm documentation: https://realm.io/docs/java/latest/
- Realm tutorials:
- Using realm better way: https://medium.com/@Zhuinden/how-to-use-realm-for-android-like-a-champ-and-how-to-tell-if-youre-doing-it-wrong-ac4f66b7f149