We all know the good old SQLite when times are asking for an internal storage. But times are changing and here it comes Realm which is on a great way to replace SQLite.
Note: The Realm version for this demo is 0.82.1, as a more stable version. The latest version at the moment is 0.90.0. For further info check https://realm.io/docs/java/latest/
What is Realm?
Realm is a mobile database and a replacement for SQLite. Although is an OO database it has some differences with other databases. Realm is not using SQLite as it’s engine. Instead it has own C++ core and aims to provide a mobile-first alternative to SQLite. Realm store data in a universal, table-based format by a C++ core. This is what allows Realm to allow data access from multiple languages as well as a range of ad hoc queries.
Below are the advantages of Realm over SQLite:
> faster than SQLite (up to 10x speed up over raw SQLite for normal operations)
> easy to use
> object conversion handled for you
> convenient for creating and storing data on the fly
> very responsive team
Also there are some disadvantages which might be taken into consideration:
> no importing
> still under active development
> not a lot of content online
> can’t access objects across threads
Realm at the moment is missing the following features:
> null support
> auto incrementing id’s
> Map
> easy migrations
> notifications on specific data changed
> compound primary keys
Building E-Book App
Note: If you receive any problems during running the project after changes have been done to the realm, you might need to uninstall the app and run it again.
1. Create a new project in Android Studio from File β New Project. When it prompts you to select the default activity, select Blank Activity and proceed.
2. Open build.gradle and add Realm, Glide, CardView and RecyclerView dependencies. RecyclerView and CardView are used to show the realm data and Glide is for showing images.
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.3.0' compile 'com.android.support:support-v4:23.3.0' compile 'com.android.support:design:23.3.0' compile 'com.github.bumptech.glide:glide:3.7.0' // Realm compile 'io.realm:realm-android:0.82.1' // RecyclerView compile 'com.android.support:recyclerview-v7:23.3.0' // CardView compile 'com.android.support:cardview-v7:23.3.0' }
3. Open dimens.xml located under res β values and add the following dimensions.
<resources> <!-- Default screen margins, per the Android Design guidelines. --> <dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen> <dimen name="margin_normal">8dp</dimen> <dimen name="margin_large">16dp</dimen> <dimen name="text_size_large">16sp</dimen> <dimen name="margin_small">0dp</dimen> <dimen name="text_size_normal">13sp</dimen> <dimen name="imge_book_detail">80dp</dimen> </resources>
4. Add the below styles to your styles.xml located under res β values. Here we define styles for the CardView.
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> <style name="AppTheme.Card.Margins" parent="CardView"> <item name="android:layout_marginTop">@dimen/margin_normal</item> <item name="android:layout_marginBottom">@dimen/margin_normal</item> <item name="android:layout_marginLeft">@dimen/margin_large</item> <item name="android:layout_marginRight">@dimen/margin_large</item> <item name="android:clickable">true</item> <item name="android:focusable">true</item> <item name="android:foreground">?android:attr/selectableItemBackground</item> <item name="cardCornerRadius">@dimen/margin_small</item> <item name="cardPreventCornerOverlap">false</item> <item name="cardBackgroundColor">@android:color/white</item> </style> </resources>
5. Create four packages named app, activity, adapters, model and realm and place your MainActivity.java under activity package. These packages helps in keeping your project organized.
6. Realms are the equivalent of a database: they contain different kinds of objects, and map to one file on disk. The most basic setup for realm is by calling:
// Obtain realm instance Realm realm = Realm.getInstance(this);
Calling Realm.getInstance(context) makes it easy to get started with Realm. For more fine-grained control, it is possible to create a RealmConfiguration object that controls all aspects of how a Realm is created.
Create a class named MyApplication.java under app package. Here we setup the realm configuration.
package app.androidhive.info.realm.app; import android.app.Application; import io.realm.Realm; import io.realm.RealmConfiguration; public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this) .name(Realm.DEFAULT_REALM_NAME) .schemaVersion(0) .deleteRealmIfMigrationNeeded() .build(); Realm.setDefaultConfiguration(realmConfiguration); } }
The RealmConfiguration can be saved as a default configuration. Setting a default configuration in your custom Application class, will ensure that it is available in the rest of your code.
It is also possible to have multiple RealmConfigurations. In this way you can control the version, schema and location of each Realm independently.
/* RealmConfiguration myConfig = new RealmConfiguration.Builder(context) .name("myrealm.realm"). .schemaVersion(2) .setModules(new MyCustomSchema()) .build(); RealmConfiguration otherConfig = new RealmConfiguration.Builder(context) .name("otherrealm.realm") .schemaVersion(5) .setModules(new MyOtherSchema()) .build(); Realm myRealm = Realm.getInstance(myConfig); Realm otherRealm = Realm.getInstance(otherConfig); */
7. Open AndroidManifest.xml and add the MyApplication to <application> tag. Also add the INTERNET permission as we need to make HTTP calls for the images.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.androidhive.info.realm"> <uses-permission android:name="android.permission.INTERNET" /> <application android:name=".app.MyApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".activity.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
8. In the model package add a class named Book.java. Realm data models are created by extending the the RealmObject base class. A Realm data model also supports public, protected and private fields as well as custom methods.
package app.androidhive.info.realm.model; import io.realm.RealmObject; import io.realm.annotations.Ignore; import io.realm.annotations.PrimaryKey; public class Book extends RealmObject { @PrimaryKey private int id; private String title; private String description; private String author; private String imageUrl; // Standard getters & setters generated by your IDE⦠public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getImageUrl() { return imageUrl; } public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; } }
The @PrimaryKey annotation indicates that this field is set as a Primary key and must not be null.
Also you can use the @Ignore annotation for the fields that should not be persisted to the disk:
@Ignore private String isbn;
9. Open the layout files of your main activity and add the recyclerView. For my main activity I have three layout files activity_main.xml, content_main.xml and item_books.xml
The activity_main.xml contains the general AppBar, Toolbar and floating action button and includes the content_main.xml layout.
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:fab="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.design.widget.AppBarLayout android:id="@+id/appbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:contentScrim="?attr/colorPrimary" app:layout_scrollFlags="enterAlways" /> </android.support.design.widget.AppBarLayout> <include layout="@layout/content_main" /> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:src="@mipmap/ic_add_white_24dp" app:backgroundTint="@color/colorPrimary" app:elevation="4dp" app:layout_anchor="@+id/recycler" app:layout_anchorGravity="bottom|right|end" /> </android.support.design.widget.CoordinatorLayout>
The content_main.xml contains the recyclerView to load the data.
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/recycler" android:layout_width="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" android:layout_height="match_parent" />
The item_books.xml contains the CardView with the ImageView and TextView’s for the recycler to display the data.
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/card_books" style="@style/AppTheme.Card.Margins" android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <ImageView android:id="@+id/image_background" android:layout_width="130dp" android:layout_height="wrap_content" android:layout_gravity="center_vertical|start" /> <LinearLayout android:id="@+id/layout_partner" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingBottom="@dimen/margin_normal"> <TextView android:id="@+id/text_books_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/margin_small" android:paddingBottom="@dimen/margin_normal" android:paddingLeft="@dimen/margin_large" android:paddingRight="@dimen/margin_large" android:paddingTop="@dimen/margin_large" android:textSize="@dimen/text_size_large" android:textColor="#555555" android:textStyle="bold" /> <TextView android:id="@+id/text_books_author" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="@dimen/margin_large" android:paddingRight="@dimen/margin_large" android:textSize="@dimen/text_size_normal" android:textStyle="italic" /> <TextView android:id="@+id/text_books_description" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/margin_small" android:maxLines="2" android:paddingBottom="@dimen/margin_normal" android:paddingLeft="@dimen/margin_large" android:paddingRight="@dimen/margin_large" android:paddingTop="@dimen/margin_small" android:textSize="@dimen/text_size_normal" /> </LinearLayout> </LinearLayout> </android.support.v7.widget.CardView>
10. Under the realm package, create a class named RealmController.java. This is a singleton class where we put our realm methods for usage through the app.
package app.androidhive.info.realm.realm; import android.app.Activity; import android.app.Application; import android.support.v4.app.Fragment; import app.androidhive.info.realm.model.Book; import io.realm.Realm; import io.realm.RealmResults; public class RealmController { private static RealmController instance; private final Realm realm; public RealmController(Application application) { realm = Realm.getDefaultInstance(); } public static RealmController with(Fragment fragment) { if (instance == null) { instance = new RealmController(fragment.getActivity().getApplication()); } return instance; } public static RealmController with(Activity activity) { if (instance == null) { instance = new RealmController(activity.getApplication()); } return instance; } public static RealmController with(Application application) { if (instance == null) { instance = new RealmController(application); } return instance; } public static RealmController getInstance() { return instance; } public Realm getRealm() { return realm; } //Refresh the realm istance public void refresh() { realm.refresh(); } //clear all objects from Book.class public void clearAll() { realm.beginTransaction(); realm.clear(Book.class); realm.commitTransaction(); } //find all objects in the Book.class public RealmResults<Book> getBooks() { return realm.where(Book.class).findAll(); } //query a single item with the given id public Book getBook(String id) { return realm.where(Book.class).equalTo("id", id).findFirst(); } //check if Book.class is empty public boolean hasBooks() { return !realm.allObjects(Book.class).isEmpty(); } //query example public RealmResults<Book> queryedBooks() { return realm.where(Book.class) .contains("author", "Author 0") .or() .contains("title", "Realm") .findAll(); } }
The usage of this class is quite simple. Let’s say if we want to get all the objects saved in the Book.class, all we need to do in the wanted activity is call the following getBooks() method from the RealmController.java class:
RealmController.with(this).getBooks()
11. Under the adapters package create the following classes: RealmRecyclerViewAdapater.java, RealmModelAdapter.java, RealmBooksAdapter.java and BooksAdapter.java.
Inside RealmRecyclerViewAdapater.java place the following code:
package app.androidhive.info.realm.adapters; import android.support.v7.widget.RecyclerView; import io.realm.RealmBaseAdapter; import io.realm.RealmObject; public abstract class RealmRecyclerViewAdapter<T extends RealmObject> extends RecyclerView.Adapter { private RealmBaseAdapter<T> realmBaseAdapter; public T getItem(int position) { return realmBaseAdapter.getItem(position); } public RealmBaseAdapter<T> getRealmAdapter() { return realmBaseAdapter; } public void setRealmAdapter(RealmBaseAdapter<T> realmAdapter) { realmBaseAdapter = realmAdapter; } }
This is a wrapper class that allows a RealmBaseAdapter instance to serve as the data source for a RecyclerView.Adapter
Inside RealmModelAdapter.java place the following code:
package app.androidhive.info.realm.adapters; import android.content.Context; import android.view.View; import android.view.ViewGroup; import io.realm.RealmBaseAdapter; import io.realm.RealmObject; import io.realm.RealmResults; public class RealmModelAdapter<T extends RealmObject> extends RealmBaseAdapter<T> { public RealmModelAdapter(Context context, RealmResults<T> realmResults, boolean automaticUpdate) { super(context, realmResults, automaticUpdate); } @Override public View getView(int position, View convertView, ViewGroup parent) { return null; } }
In RealmBooksAdaper.java place the following code:
package app.androidhive.info.realm.adapters; import android.content.Context; import app.androidhive.info.realm.model.Book; import io.realm.RealmResults; public class RealmBooksAdapter extends RealmModelAdapter<Book> { public RealmBooksAdapter(Context context, RealmResults<Book> realmResults, boolean automaticUpdate) { super(context, realmResults, automaticUpdate); } }
This classes are needed to make the recycler view adapter work with the realm data.
Finally create BooksAdapter.java.
package app.androidhive.info.realm.adapters; import android.content.Context; import android.content.DialogInterface; import android.support.v7.app.AlertDialog; import android.support.v7.widget.CardView; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import com.bumptech.glide.Glide; import app.androidhive.info.realm.R; import app.androidhive.info.realm.app.Prefs; import app.androidhive.info.realm.model.Book; import app.androidhive.info.realm.realm.RealmController; import io.realm.Realm; import io.realm.RealmResults; public class BooksAdapter extends RealmRecyclerViewAdapter<Book> { final Context context; private Realm realm; private LayoutInflater inflater; public BooksAdapter(Context context) { this.context = context; } // create new views (invoked by the layout manager) @Override public CardViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { // inflate a new card view View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_books, parent, false); return new CardViewHolder(view); } // replace the contents of a view (invoked by the layout manager) @Override public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position) { realm = RealmController.getInstance().getRealm(); // get the article final Book book = getItem(position); // cast the generic view holder to our specific one final CardViewHolder holder = (CardViewHolder) viewHolder; // set the title and the snippet holder.textTitle.setText(book.getTitle()); holder.textAuthor.setText(book.getAuthor()); holder.textDescription.setText(book.getDescription()); // load the background image if (book.getImageUrl() != null) { Glide.with(context) .load(book.getImageUrl().replace("https", "http")) .asBitmap() .fitCenter() .into(holder.imageBackground); } //remove single match from realm holder.card.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { RealmResults<Book> results = realm.where(Book.class).findAll(); // Get the book title to show it in toast message Book b = results.get(position); String title = b.getTitle(); // All changes to data must happen in a transaction realm.beginTransaction(); // remove single match results.remove(position); realm.commitTransaction(); if (results.size() == 0) { Prefs.with(context).setPreLoad(false); } notifyDataSetChanged(); Toast.makeText(context, title + " is removed from Realm", Toast.LENGTH_SHORT).show(); return false; } }); //update single match from realm holder.card.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View content = inflater.inflate(R.layout.edit_item, null); final EditText editTitle = (EditText) content.findViewById(R.id.title); final EditText editAuthor = (EditText) content.findViewById(R.id.author); final EditText editThumbnail = (EditText) content.findViewById(R.id.thumbnail); editTitle.setText(book.getTitle()); editAuthor.setText(book.getAuthor()); editThumbnail.setText(book.getImageUrl()); AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setView(content) .setTitle("Edit Book") .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { RealmResults<Book> results = realm.where(Book.class).findAll(); realm.beginTransaction(); results.get(position).setAuthor(editAuthor.getText().toString()); results.get(position).setTitle(editTitle.getText().toString()); results.get(position).setImageUrl(editThumbnail.getText().toString()); realm.commitTransaction(); notifyDataSetChanged(); } }) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); AlertDialog dialog = builder.create(); dialog.show(); } }); } // return the size of your data set (invoked by the layout manager) public int getItemCount() { if (getRealmAdapter() != null) { return getRealmAdapter().getCount(); } return 0; } public static class CardViewHolder extends RecyclerView.ViewHolder { public CardView card; public TextView textTitle; public TextView textAuthor; public TextView textDescription; public ImageView imageBackground; public CardViewHolder(View itemView) { // standard view holder pattern with Butterknife view injection super(itemView); card = (CardView) itemView.findViewById(R.id.card_books); textTitle = (TextView) itemView.findViewById(R.id.text_books_title); textAuthor = (TextView) itemView.findViewById(R.id.text_books_author); textDescription = (TextView) itemView.findViewById(R.id.text_books_description); imageBackground = (ImageView) itemView.findViewById(R.id.image_background); } } }
12. Now that everything is set we can add code to the MainActivity.java
> Get realm instance from the RealmController.java
> Setup the recycler. setupRecycler() method is used for this purpose.
> Write some data in realm to be displayed in the recyclerView. setRealmData() method is used for this.
> Refresh the realm instance
> Set the realm adapter, get the realm data and pass it to the adapter
package app.androidhive.info.realm.activity; import android.content.DialogInterface; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.view.LayoutInflater; import android.view.View; import android.widget.EditText; import android.widget.Toast; import app.androidhive.info.realm.app.Prefs; import app.androidhive.info.realm.R; import app.androidhive.info.realm.adapters.BooksAdapter; import app.androidhive.info.realm.adapters.RealmBooksAdapter; import app.androidhive.info.realm.model.Book; import app.androidhive.info.realm.realm.RealmController; import io.realm.Realm; import io.realm.RealmResults; public class MainActivity extends AppCompatActivity { private BooksAdapter adapter; private Realm realm; private LayoutInflater inflater; private FloatingActionButton fab; private RecyclerView recycler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); fab = (FloatingActionButton) findViewById(R.id.fab); recycler = (RecyclerView) findViewById(R.id.recycler); //get realm instance this.realm = RealmController.with(this).getRealm(); //set toolbar Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); setupRecycler(); if (!Prefs.with(this).getPreLoad()) { setRealmData(); } // refresh the realm instance RealmController.with(this).refresh(); // get all persisted objects // create the helper adapter and notify data set changes // changes will be reflected automatically setRealmAdapter(RealmController.with(this).getBooks()); Toast.makeText(this, "Press card item for edit, long press to remove item", Toast.LENGTH_LONG).show(); //add new item fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { inflater = MainActivity.this.getLayoutInflater(); View content = inflater.inflate(R.layout.edit_item, null); final EditText editTitle = (EditText) content.findViewById(R.id.title); final EditText editAuthor = (EditText) content.findViewById(R.id.author); final EditText editThumbnail = (EditText) content.findViewById(R.id.thumbnail); AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setView(content) .setTitle("Add book") .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Book book = new Book(); //book.setId(RealmController.getInstance().getBooks().size() + 1); book.setId(RealmController.getInstance().getBooks().size() + System.currentTimeMillis()); book.setTitle(editTitle.getText().toString()); book.setAuthor(editAuthor.getText().toString()); book.setImageUrl(editThumbnail.getText().toString()); if (editTitle.getText() == null || editTitle.getText().toString().equals("") || editTitle.getText().toString().equals(" ")) { Toast.makeText(MainActivity.this, "Entry not saved, missing title", Toast.LENGTH_SHORT).show(); } else { // Persist your data easily realm.beginTransaction(); realm.copyToRealm(book); realm.commitTransaction(); adapter.notifyDataSetChanged(); // scroll the recycler view to bottom recycler.scrollToPosition(RealmController.getInstance().getBooks().size() - 1); } } }) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); AlertDialog dialog = builder.create(); dialog.show(); } }); } public void setRealmAdapter(RealmResults<Book> books) { RealmBooksAdapter realmAdapter = new RealmBooksAdapter(this.getApplicationContext(), books, true); // Set the data and tell the RecyclerView to draw adapter.setRealmAdapter(realmAdapter); adapter.notifyDataSetChanged(); } private void setupRecycler() { // use this setting to improve performance if you know that changes // in content do not change the layout size of the RecyclerView recycler.setHasFixedSize(true); // use a linear layout manager since the cards are vertically scrollable final LinearLayoutManager layoutManager = new LinearLayoutManager(this); layoutManager.setOrientation(LinearLayoutManager.VERTICAL); recycler.setLayoutManager(layoutManager); // create an empty adapter and add it to the recycler view adapter = new BooksAdapter(this); recycler.setAdapter(adapter); } private void setRealmData() { ArrayList<Book> books = new ArrayList<>(); Book book = new Book(); book.setId(1 + System.currentTimeMillis()); book.setAuthor("Reto Meier"); book.setTitle("Android 4 Application Development"); book.setImageUrl("https://api.androidhive.info/images/realm/1.png"); books.add(book); book = new Book(); book.setId(2 + System.currentTimeMillis()); book.setAuthor("Itzik Ben-Gan"); book.setTitle("Microsoft SQL Server 2012 T-SQL Fundamentals"); book.setImageUrl("https://api.androidhive.info/images/realm/2.png"); books.add(book); book = new Book(); book.setId(3 + System.currentTimeMillis()); book.setAuthor("Magnus Lie Hetland"); book.setTitle("Beginning Python: From Novice To Professional Paperback"); book.setImageUrl("https://api.androidhive.info/images/realm/3.png"); books.add(book); book = new Book(); book.setId(4 + System.currentTimeMillis()); book.setAuthor("Chad Fowler"); book.setTitle("The Passionate Programmer: Creating a Remarkable Career in Software Development"); book.setImageUrl("https://api.androidhive.info/images/realm/4.png"); books.add(book); book = new Book(); book.setId(5 + System.currentTimeMillis()); book.setAuthor("Yashavant Kanetkar"); book.setTitle("Written Test Questions In C Programming"); book.setImageUrl("https://api.androidhive.info/images/realm/5.png"); books.add(book); for (Book b : books) { // Persist your data easily realm.beginTransaction(); realm.copyToRealm(b); realm.commitTransaction(); } Prefs.with(this).setPreLoad(true); } }
What is happening in this activity is when it is started, if there is no data in the realm, the setRealmData() method creates couple of objects and save it to the realm. After that realm instance is refreshed, the data is called with getBooks() and the adapter is notifyed about the changes:
// refresh the realm instance RealmController.with(this).refresh(); // get all persisted objects // create the helper adapter and notify data set changes // changes will be reflected automatically setRealmAdapter(RealmController.with(this).getBooks());
Run the app, and you can now see the data displayed in the recycler view.
Performing Write, Update & Delete Operations
13. We have set the app to read the realm data, now let’s do some simple write, update and delete to the data. Under res β layout, create a layout named edit_item.xml. This layout will be use in a dialog interface for writing and updating.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <EditText android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/activity_horizontal_margin" android:hint="Title" android:inputType="text" /> <EditText android:id="@+id/author" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/activity_horizontal_margin" android:hint="Author" android:inputType="text" /> <EditText android:id="@+id/thumbnail" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/activity_horizontal_margin" android:hint="Image" android:inputType="text" /> </LinearLayout>
14. Open again the MainActivity.java and the following code:
//add new item fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { inflater = MainActivity.this.getLayoutInflater(); View content = inflater.inflate(R.layout.edit_item, null); final EditText editTitle = (EditText) content.findViewById(R.id.title); final EditText editAuthor = (EditText) content.findViewById(R.id.author); final EditText editThumbnail = (EditText) content.findViewById(R.id.thumbnail); AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setView(content) .setTitle("Add book") .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Book book = new Book(); //book.setId(RealmController.getInstance().getBooks().size() + 1); book.setId(RealmController.getInstance().getBooks().size() + System.currentTimeMillis()); book.setTitle(editTitle.getText().toString()); book.setAuthor(editAuthor.getText().toString()); book.setImageUrl(editThumbnail.getText().toString()); if (editTitle.getText() == null || editTitle.getText().toString().equals("") || editTitle.getText().toString().equals(" ")) { Toast.makeText(MainActivity.this, "Entry not saved, missing title", Toast.LENGTH_SHORT).show(); } else { // Persist your data easily realm.beginTransaction(); realm.copyToRealm(book); realm.commitTransaction(); adapter.notifyDataSetChanged(); // scroll the recycler view to bottom recycler.scrollToPosition(RealmController.getInstance().getBooks().size() - 1); } } }) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); AlertDialog dialog = builder.create(); dialog.show(); } });
Here we set and dialog on the floating action button to add new item. Run the following code and try to add some new data. (Note: you might need to uninstall and run the app again for the changes to take place) .
15. In order to make some update and delete actions open BooksAdapter.java and add the following code:
//remove single match from realm holder.card.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { RealmResults<Book> results = realm.where(Book.class).findAll(); // Get the book title to show it in toast message Book b = results.get(position); String title = b.getTitle(); // All changes to data must happen in a transaction realm.beginTransaction(); // remove single match results.remove(position); realm.commitTransaction(); if (results.size() == 0) { Prefs.with(context).setPreLoad(false); } notifyDataSetChanged(); Toast.makeText(context, title + " is removed from Realm", Toast.LENGTH_SHORT).show(); return false; } }); //update single match from realm holder.card.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View content = inflater.inflate(R.layout.edit_item, null); final EditText editTitle = (EditText) content.findViewById(R.id.title); final EditText editAuthor = (EditText) content.findViewById(R.id.author); final EditText editThumbnail = (EditText) content.findViewById(R.id.thumbnail); editTitle.setText(book.getTitle()); editAuthor.setText(book.getAuthor()); editThumbnail.setText(book.getImageUrl()); AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setView(content) .setTitle("Edit Book") .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { RealmResults<Book> results = realm.where(Book.class).findAll(); realm.beginTransaction(); results.get(position).setAuthor(editAuthor.getText().toString()); results.get(position).setTitle(editTitle.getText().toString()); results.get(position).setImageUrl(editThumbnail.getText().toString()); realm.commitTransaction(); notifyDataSetChanged(); } }) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); AlertDialog dialog = builder.create(); dialog.show(); } });
Here we set delete of a match through long click on the card item and update dialog on click. The removing of an item is very easy. All we need is the bellow code with the position passed to the realm.
// All changes to data must happen in a transaction realm.beginTransaction(); // remove single match results.remove(position); realm.commitTransaction();
The same happens with the update.
realm.beginTransaction(); results.get(position).setAuthor(editAuthor.getText().toString()); results.get(position).setTitle(editTitle.getText().toString()); results.get(position).setDescription(editDescription.getText().toString()); results.get(position).setImageUrl("https://realm.io/assets/RealmSquare.png"); realm.commitTransaction();
Now run the code again (uninstall or clear data if necessary ) and check the changes. Try updating or deleting some items.
Migration from SQLite
If you have currently have an app that uses SQLite and want to migrate to Realm, there is definitely some work involved. It is not a drop-in change. SQLite prefer a very normalized-form that doesn’t necessarily work the best with Realm. It’s better to rethink your schema and model it as objects.
Once you’ve modified the schema to work well with Realm however, it is much easier to migrate any existing data from SQLite. Just setup a migration (from version 0 to 1 of your Realm database), and in this migration, load your SQLite data into Realm objects and then just save them.
Or, if your data also resides on a remote server, you could just build the Realm database from scratch.
Before adopting Realm though, note that it must be considered as bleeding edge software, with an API that can have breaking changes in future versions.