[Android] I quit SQLite and tried using Realm

Introduction

The existence of a "database" that anyone who develops Android will encounter once. If you've already touched SQL, SQLite is a good thing, but I'm not. So I decided to get help from a cat, and when I looked up various plug-ins, I came across "Realm". It has already been used in various apps, and it was said that it has a certain reputation in the iOS area, so I decided to play with it.

realm.gif

Development environment

Introduced immediately

Add the following code to gradle directly under Project.

build.gradle


buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:2.3.3'
    classpath 'io.realm:realm-gradle-plugin:3.5.0' //add to
  }
}

Next, add the following code to the gradle of the app you want to build a database with Realm.

build.gradle


apply plugin: 'realm-android' //add to

dependencies {
  compile fileTree(include: ['*.jar'], dir: 'libs')
  compile 'com.android.support:appcompat-v7:26.0.0-alpha1'
  compile 'com.android.support:recyclerview-v7:26.0.0-alpha1'
  compile 'com.android.support:support-v4:26.0.0-alpha1'
  compile 'io.realm:android-adapters:2.1.0' //add to
}

(Please adjust the version etc. of each Support Library according to each environment.)

Try to implement

  1. Model creation The most important table design for database construction. Realm makes it very easy to design. The sample code is shown below.

    public class Friend extends RealmObject {
    
      @PrimaryKey
      private String address;  //address column
      private String name;  //name column
    
      // getter setter...
      public String getName() { return name; }
    
      public void setName(String name) { this.name = name; }
    
      public String getAddress() { return address; }
    
      public void setAddress(String address) { this.address = address; }
    
    }
    

I have defined a String type "address" parameter (column) and a String type "name" parameter (column) in an object (record) called Friend. Below that, define Getters and Setters for reading and writing values in each column. This time I wanted to set the address column as the primary key, so I added the @PrimaryKey annotation. Other annotations and detailed explanations can be found in the official documentation. Since Model creation has a relatively high degree of freedom in Realm, most things can be done by writing annotations and unique methods. Also, by writing your own Method, you can improve readability without writing frequently performed operations on the Controller side.

  1. View creation We will make a sample screen to add data to the Model (Friend table) created earlier and a screen to display a list. --Add data (register as a friend)

    ```fragment_addfriend.xml
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@color/White"
                tools:context=".AddFriendFragment">
    
      <EditText
        android:id="@+id/address_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="30dp"
        android:ems="12"
        android:hint="address"
        android:inputType="text"
        android:maxLines="1"/>
    
      <EditText
        android:id="@+id/name_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/address_text"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="10dp"
        android:ems="12"
        android:hint="name"
        android:inputType="text"
        android:maxLines="1"/>
    
      <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/name_text"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="30dp"
        android:text="Registration"/>
    
    </RelativeLayout>
    ```
    

Add EditText that accepts address and name input and Button of registration confirmation dialog. --Data list display (friend list)

    ```fragment_friendlist.xml
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@color/White"
                tools:context=".FriendListFragment">
    
      <android.support.v7.widget.RecyclerView
        android:id="@+id/friend_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/button"/>
    
    </FrameLayout>
    ```

Add RecyclerView to list.

    ```row_friend.xml
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="10dp">
    
      <TextView
        android:id="@+id/label1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="name:"/>
    
      <TextView
        android:id="@+id/name_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/label1"
        android:layout_toEndOf="@+id/label1"/>
    
      <TextView
        android:id="@+id/label2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignStart="@+id/label1"
        android:layout_below="@+id/label1"
        android:layout_marginTop="10dp"
        android:text="address:"/>
    
      <TextView
        android:id="@+id/address_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/label2"
        android:layout_toEndOf="@+id/label2"/>
    
    </RelativeLayout>
    ```

Add a TextView that displays the name and address respectively.

  1. Controller creation Since the screen when starting the application is omitted this time, please set to display each fragment by adding two buttons to the Root activity. --Add friend screen

    ```AddFriendFragment.java
    public class AddFriendFragment extends Fragment {
      private EditText addressText;
      private EditText nameText;
      private Button mButton;
    
      @Override
      public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_forth, container, false);
    
        addressText = view.findViewById(R.id.address_text);
        nameText = view.findViewById(R.id.name_text);
        mButton = view.findViewById(R.id.button);
      
        return view;
      }
    
      @Override
      public void onStart() {
        super.onStart();
    
        mButton.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
            DialogFragment friendRegisterDialog = new FriendRegisterDialog();
            Bundle args = new Bundle();
            args.putString("name", nameText.getText().toString());
            args.putString("address", addressText.getText().toString());
            friendRegisterDialog.setArguments(args);
            friendRegisterDialog.show(getActivity().getSupportFragmentManager(), "friendRegister");
          }
        });
      }
    
      public static class FriendRegisterDialog extends DialogFragment {
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
          final Bundle args = getArguments();
          AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
          builder.setTitle("Registration confirmation")
                  .setMessage("Do you want to register as a friend?")
                  .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                      final Realm realm = Realm.getDefaultInstance();  //Instantiate Realm
                      final Friend friend = new Friend();  //Instantiate Friend table
                      friend.setName(args.getString("name"));
                      friend.setAddress(args.getString("address"));
                      realm.executeTransactionAsync(new Realm.Transaction() {
                        @Override
                        public void execute(Realm bgRealm) {
                          bgRealm.copyToRealmOrUpdate(friend);  //If the value set in PrimaryKey already exists, it will be updated, otherwise it will be newly registered.
                        }
                      }, new Realm.Transaction.OnSuccess() {
                        @Override
                        public void onSuccess() {
                          realm.close();  //Always close when the database operation ends()Let me do it!
                          Toast.makeText(getContext(), args.getString("name") + "Registered", Toast.LENGTH_SHORT).show();
                          getActivity().getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); //Close Fragment
                        }
                      }, new Realm.Transaction.OnError() {
                        @Override
                        public void onError(Throwable error) {
                          realm.close();  //Always close when the database operation ends()Let me do it!
                          Toast.makeText(getContext(), "Signup failed", Toast.LENGTH_SHORT).show();
                        }
                      });
                    }
                  })
                  .setNegativeButton("No", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                      dismiss();
                    }
                  });
          return builder.create();
        }
      }
    }
    ```
    

The address and name input is received by EditText, and the value is passed to AlertDialog via Bundle. And when the Positive Button of AlertDialog is pressed, if the value of the entered address already exists on the table, the name is updated (Update), and if it does not exist, a new registration (Insert) is performed. .. I'm surprised that these are achieved in just one line of Realm's method copyToRealmOrUpdate (). --Friend list screen

    ```FriendListFragment.java
    public class FriendListFragment extends Fragment {
      private RecyclerView mRecyclerView;
      private FriendAdapter adapter;
      private Realm realm;
    
      @Override
      public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_fifth, container, false);
    
        realm = Realm.getDefaultInstance(); //Instantiate Realm
        final RealmResults<Friend> result = realm.where(Friend.class).findAll(); //Get all records from Friend table
        adapter = new FriendAdapter(result); //Pass the result to the adapter
    
        mRecyclerView = view.findViewById(R.id.friend_list);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
        mRecyclerView.setAdapter(adapter);
        mRecyclerView.setHasFixedSize(true);
        mRecyclerView.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL));
    
        //Implemented swipe operation, which is a feature of RecyclerView
        ItemTouchHelper touchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
          @Override
          public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
            return false;
          }
    
          @Override
          public void onSwiped(final RecyclerView.ViewHolder viewHolder, int direction) {
            //Get properties for each item
            TextView nameText = viewHolder.itemView.findViewById(R.id.name_text);
            TextView address_text = viewHolder.itemView.findViewById(R.id.address_text);
            final String oldName = nameText.getText().toString();
            final String address = address_text.getText().toString();
    
            //Processing is divided according to the direction of the swipe
            if (direction == ItemTouchHelper.LEFT) { //Delete item
              realm.executeTransactionAsync(new Realm.Transaction() {
                @Override
                public void execute(Realm bgRealm) {
                  Friend obj = bgRealm.where(Friend.class).equalTo("address", address).findFirst();
                  obj.deleteFromRealm();
                }
              }, new Realm.Transaction.OnSuccess() {
                @Override
                public void onSuccess() {
                  adapter.notifyDataSetChanged();  //Adapter redraw
                  Toast.makeText(getContext(), oldName + "Was deleted", Toast.LENGTH_SHORT).show();
                }
              }, new Realm.Transaction.OnError() {
                @Override
                public void onError(Throwable error) {
                  adapter.notifyDataSetChanged();  //Adapter redraw
                  Toast.makeText(getContext(), "Failed to delete", Toast.LENGTH_SHORT).show();
                }
              });
            } else { //Change item properties
              final EditText input = new EditText(getContext());
              input.setInputType(TYPE_CLASS_TEXT);
              input.setMaxLines(1);
              input.setText(oldName);
              AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
              builder.setTitle("rename")
                      .setMessage("Please enter the changed name")
                      .setView(input)
                      .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                          final String newName = input.getText().toString();
                          final Friend friend = new Friend();
                          friend.setName(newName);
                          friend.setAddress(address);
                          realm.executeTransactionAsync(new Realm.Transaction() {
                            @Override
                            public void execute(Realm bgRealm) {
                              bgRealm.copyToRealmOrUpdate(friend);
                            }
                          }, new Realm.Transaction.OnSuccess() {
                            @Override
                            public void onSuccess() {
                              adapter.notifyDataSetChanged();  //Adapter redraw
                              Toast.makeText(getContext(), oldName + "Mr." + newName + "Renamed to", Toast.LENGTH_SHORT).show();
                            }
                          }, new Realm.Transaction.OnError() {
                            @Override
                            public void onError(Throwable error) {
                              adapter.notifyDataSetChanged();  //Adapter redraw
                              Toast.makeText(getContext(), "Rename failed", Toast.LENGTH_SHORT).show();
                            }
                          });
                        }
                      })
                      .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int whichButton) {
                        }
                      })
                      .create()
                      .show();
            }
          }
    
          //Menu displayed when swiping
          @Override
          public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
            if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
              Bitmap icon;
              Paint p = new Paint();
              View itemView = viewHolder.itemView;
              float height = (float) itemView.getBottom() - (float) itemView.getTop();
              float width = height / 3;
    
              if (dX > 0) {
                p.setColor(Color.parseColor("#388E3C"));
                RectF background = new RectF((float) itemView.getLeft(), (float) itemView.getTop(), dX, (float) itemView.getBottom());
                c.drawRect(background, p);
                icon = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
                RectF icon_dest = new RectF((float) itemView.getLeft() + width, (float) itemView.getTop() + width, (float) itemView.getLeft() + 2 * width, (float) itemView.getBottom() - width);
                if (dX > itemView.getLeft() + icon.getWidth()) {
                  c.drawBitmap(icon, null, icon_dest, p);
                }
              } else if (dX < 0) {
                p.setColor(Color.parseColor("#D32F2F"));
                RectF background = new RectF((float) itemView.getRight() + dX, (float) itemView.getTop(), (float) itemView.getRight(), (float) itemView.getBottom());
                c.drawRect(background, p);
                icon = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
                RectF icon_dest = new RectF((float) itemView.getRight() - 2 * width, (float) itemView.getTop() + width, (float) itemView.getRight() - width, (float) itemView.getBottom() - width);
                if (dX < -(itemView.getLeft() + icon.getWidth())) {
                  c.drawBitmap(icon, null, icon_dest, p);
                }
              }
            }
            super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
          }
        });
        touchHelper.attachToRecyclerView(mRecyclerView);
    
        return view;
      }
    
      @Override
  public void onDestroy() {
        super.onDestroy();
        mRecyclerView.setAdapter(null);
        realm.close();  //Always close when the database operation ends()Let me do it!
      }
    
    }
    ```

The RecyclerView lists all the records in the Friend table. As you may have noticed here, Realm doesn't need to retrieve the data again when the records retrieved from the table are updated or deleted. In this code, the acquired record is stored in a variable called "result", but if there is a change in the data in it, the data will be synchronized sequentially. It is wonderful. --Custom adapter

    ```FriendAdapter.java
    class FriendAdapter extends RealmRecyclerViewAdapter<Friend, FriendAdapter.FriendViewHolder> {
      private OrderedRealmCollection<Friend> objects;
    
      FriendAdapter(OrderedRealmCollection<Friend> friends) {
        super(friends, true);
        this.objects = friends;
        setHasStableIds(true);
      }
    
      @Override
      public FriendViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_friend, parent, false);
        return new FriendViewHolder(view);
      }
    
      @Override
      public void onBindViewHolder(FriendViewHolder holder, int position) {
        final Friend obj = getItem(position);
        //noinspection ConstantConditions
        holder.name.setText(obj.getName());
        holder.address.setText(obj.getAddress());
      }
    
      @Override
      public int getItemCount() {
        return objects.size();
      }
    
      static class FriendViewHolder extends RecyclerView.ViewHolder {
        TextView name;
        TextView address;
    
        FriendViewHolder(View view) {
          super(view);
          name = view.findViewById(R.id.name_text);
          address = view.findViewById(R.id.address_text);
        }
      }
    }
    ```

It is a little faster (lighter) using ViewHolder. Create your own custom adapter that inherits RealmRecyclerViewAdapter <table name, adapter name.ViewHolder name>.

Tips I haven't posted the code for MainActivity this time, but you need to initialize Realm with Activity when the app starts. I think it's okay if you add the following code.

Realm.init(this);
RealmConfiguration config = new RealmConfiguration.Builder().build();
Realm.setDefaultConfiguration(config);

It seems that there are various parameters for config as well, so in the official docs ...

Also, after distributing the app, there will be requests such as "I want to add a new column!", "I want to change the column attributes!", And "I want to add a table in the first place". Realm can handle this with the migration function. However, if it is still under development and before distribution, it is troublesome to describe the migration one by one. Therefore, if you add the following code at the time of initialization, the database will be deleted once cleanly, so migration is unnecessary.

Realm.deleteRealm(config);

This is useful when the database specifications have not been finalized in the early stages of development!

At the end

When I actually used Realm, I was impressed with its ease of use. It is also wonderful that the response is overwhelmingly fast compared to other related plugins.

I think it is perfect for those who have never touched SQL and have little experience. If you are having trouble with the database developed by Androi, it is worth a try!

There are methods and functions that have not been introduced yet, so in the official document ... Realm Java 3.5.0

Recommended Posts

[Android] I quit SQLite and tried using Realm
[Android] I tried using Coordinator Layout.
I tried using Realm with Swift UI
I tried using Gson
I tried using TestNG
I tried using Galasa
I tried using "nifty cloud mobile backend" and "Firebase" Authentication on Kotlin + Android
I tried using a database connection in Android development
I tried using YOLO v4 on Ubuntu and ROS
I tried using azure cloud-init
I tried using Apache Wicket
I tried using Java REPL
I tried unit testing Rails app using RSpec and FactoryBot
[Rails] I tried to implement "Like function" using rails and js
[Android] [Library] I tried using an animation library called "Before After animation".
I tried using the CameraX library with Android Java Fragment
I tried using anakia + Jing now
I tried using Spring + Mybatis + DbUnit
I tried using JOOQ with Gradle
I tried using Java8 Stream API
I tried using JWT in Java
I tried using Pari gp container
I tried using WebAssembly Stadio (2018/4/17 version)
I tried using Java memo LocalDate
I tried using GoogleHttpClient of Java
I tried to integrate Docker and Maven / Netbean nicely using Jib
I wanted to animate a row when using realm and RecyclerView on Android, but I gave up
I tried to make a simple face recognition Android application using OpenCV
I tried using Elasticsearch API in Java
I tried using Java's diagnostic tool Arthas
I introduced WSL2 + Ubuntu to Window10 and tried using GDC, DMD, LDC
I tried using UICollectionViewListCell added from Xcode12.
I created and set my own Dialect with Thymeleaf and tried using it
I tried using Scalar DL with Docker
I tried to make my own transfer guide using OpenTripPlanner and GTFS
I tried using OnlineConverter with SpringBoot + JODConverter
It's new, but I tried using Groonga
I tried using OpenCV with Java + Tomcat
[JDBC ③] I tried to input from the main method using placeholders and arguments.
I tried using Docker for the first time
I tried using Junit on Mac VScode Maven
[For beginners] I tried using DBUnit in Eclipse
I tried barcode scanning using Rails + React + QuaggaJS
[For beginners] I tried using JUnit 5 in Eclipse
I tried to link grafana and postgres [docker-compose]
I also tried WebAssembly with Nim and C
I made blackjack with Ruby (I tried using minitest)
[API] I tried using the zip code search API
I tried to link JavaFX and Spring Framework.
I tried to implement a server using Netty
I tried using the profiler of IntelliJ IDEA
I tried using Wercker to create and publish a Docker image that launches GlassFish 5.
I tried Spring.
I tried tomcat
I tried youtubeDataApi.
I tried refactoring ①
I tried FizzBuzz.
I tried JHipster 5.1
I tried Mastodon's Toot and Streaming API in Java
I tried using the Server Push function of Servlet 4.0
I tried to read and output CSV with Outsystems