Androidはワンツーパンチ 三歩進んで二歩下がる

プログラミングやどうでもいい話

速いと噂のAndroid ORMライブラリDBFlowを使ってみた

Raizlabs社というところのDBFlowというORマッパライブラリを使ってみました。
とても新しいライブラリで、2014/9/7がfirst commitとなっています。

github.com

こちらのサイト様で紹介されています。
qiita.com

Raizlabs社のスピードテストでは非常に速いという結果が出ています。
github.com

こちらのサイトでもActive Android, Sprinkles, SugarORM, DBFlowの中では一番速いという結果が出ています。
www.raizlabs.com

そんなDBFlowを基本的なところだけ動かしてみました。



導入

android-aptプラグインを導入します。
トップレベルのbuild.gradleに以下のように記述します。

buildscript {
    repositories {
      // Required for this library, don't use mavenCentral ()
        jcenter()
    }
    dependencies {
          classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
    }
}

projectレベルのbuild.gradleに以下のように記述します。

  apply plugin: 'com.neenbedankt.android-apt'

  dependencies {
    apt 'com.raizlabs.android:DBFlow-Compiler:2.2.1'
    compile "com.raizlabs.android:DBFlow-Core:2.2.1"
    compile "com.raizlabs.android:DBFlow:2.2.1"
  }
※ここで注意事項ですが、ビルド時間の短縮のためオフラインモードを使って下さい。
Android StudioのPreferences->
Build,Executor,Deployment->Build Tools->Gradle->Offline Workをチェックします。


DBFlowのセットアップ

Application継承クラスでDBFlowの初期化をします。

public class ExampleApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        FlowManager.init(this);
    }
}



データベースを定義する

DBFlowではデータベースはテーブルが各自接続するためのプレースホルダーオブジェクトとなります。
データベース用のクラスを作成し、アノテーションでデータベース名とバージョンを指定します。

@Database(name = ColonyDatabase.NAME, version = ColonyDatabase.VERSION)
public class ColonyDatabase {

  public static final String NAME = "Colonies";

  public static final int VERSION = 1;
}



テーブルを定義する

Modelクラスを継承したクラスを作成します。
標準的なテーブル用にModelクラスを拡張したBaseModelクラスが用意されています。
テーブルの定義には@Tableアノテーションをつけ、データベース名を指定します。
必ずprimary keyを定義します。
クラスとColumnのスコープはpackage privateかpublicで定義して下さい。

@Table(databaseName = ColonyDatabase.NAME)
public class Queen extends BaseModel {

  @Column
  @PrimaryKey(autoincrement = true)
  long id;

  @Column
  String name;

}


フィールドを定義する

テーブルのクラスに@Columnアノテーションをつけたフィールドを定義します。
@Column(name = "modify_date")のように指定した場合はmodify_dateが実際のテーブルのフィールド名になります。
@Column(defaultValue = "defaultValueStringhere")のようにデフォルトの値を指定することも出来ます。
@PrimaryKeyアノテーションはプライマリーキーとなります。
他にもFOREIGN KEYの貼り方などリファレンスを見て下さい。

(@NotNull付けるとNOT NULL ON CONFLICT FAILまで付けられる気がするv2.2.1
ので怖いから使いたくない)
(BLOBのフィールドのセットの仕方がわからない)

    @Column
    @Unique
    @PrimaryKey()
    String postId;

    @Column(name = "modify_date")
    Date date;

    @Column
//    @NotNull
    String title;

    @Column
    String description;

 //   @Column
 //   com.raizlabs.android.dbflow.data.Blob testblob;


apt

テーブルの定義が終わったらコンパイルしてみます。/app/build/generated/source/aptフォルダの配下に生成されたコードが入っています。
別にここのコードを見なくても処理を書けますが、見ておくといいかもしれません。
Databaseの定義
f:id:sakura_bird1:20150910205319p:plain


ContentValuesとのバインド
f:id:sakura_bird1:20150910205343p:plain




Query

私もあまり色々試していないのですよくわかっていないのですが、
@Tableとして定義したクラスには
insert()
save()
delete()
update()
async()
などのメソッドが用意されています。

        //データの追加
        posts = new Posts();
        posts.date = new Date();
        posts.postId = "test02";
        posts.title = "title2";
        posts.description = "description2";
        posts.save();


select文
こういうSelect文はAndroidで書くのは大変ですが下記のように簡単に記述できます。
SELECT * FROM Ant where type = 'worker' AND isMale = 0;

// main thread retrieval
List<Ant> devices = new Select().from(Ant.class)
  .where(
      Condition.column(Ant$Table.TYPE).eq("worker"),
      Condition.column(Ant$Table.ISMALE).eq(false)).queryList();


//大きなクエリには TransactionManagerを推奨
// Async Transaction Queue Retrieval (Recommended)
TransactionManager.getInstance().addTransaction(new SelectListTransaction<>(
  new Select()
  .from(DeviceObject.class)
  .where(
      Condition.column(Ant$Table.TYPE).eq("worker"),
      Condition.column(Ant$Table.ISMALE).eq(false))),
  transactionListener);


バックアップも作ってくれるみたい

Easy back up of the database: backupEnabled() enables back up on the database by calling

FlowManager.getDatabaseForTable(table).backupDB()
Please Note: This creates a temporary third database in case of a failed backup.



サンプル

実際に動かしてみたサンプルはこちらです。

適当なデータを作って画面に表示して、クリックされたデータを削除します。
処理の最初で前回のデータを全件削除するようにしています。

プロジェクト全体はこちらです。
github.com

データベースの定義とテーブルの定義

package sakurafish.com.examdbflow;

import com.raizlabs.android.dbflow.annotation.Database;

@Database(name = DBSample.NAME, version = DBSample.VERSION)
public class DBSample {

    public static final String NAME = "dbsample";

    public static final int VERSION = 1;
}
package sakurafish.com.examdbflow;

import com.raizlabs.android.dbflow.annotation.Column;
import com.raizlabs.android.dbflow.annotation.PrimaryKey;
import com.raizlabs.android.dbflow.annotation.Table;
import com.raizlabs.android.dbflow.annotation.Unique;
import com.raizlabs.android.dbflow.structure.BaseModel;

import java.util.Date;

@Table(databaseName = DBSample.NAME)
public class Posts extends BaseModel {

    @Column
    @Unique
    @PrimaryKey()
    String postId;

    @Column(name = "modify_date")
    Date date;

    @Column
//    @NotNull
    String title;

    @Column
    String description;

//    @Column
//    com.raizlabs.android.dbflow.data.Blob testblob;
}

DBを使用しているコード

package sakurafish.com.examdbflow;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.raizlabs.android.dbflow.sql.language.Delete;
import com.raizlabs.android.dbflow.sql.language.Select;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    final String TAG = MainActivity.class.getSimpleName();
    private List<Posts> mDatas;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        deleteData();
        addData();
        selectData();
        initLayout();
    }

    private void deleteData() {
        //全件削除
        Log.d(TAG, "count 削除前:" + new Delete().from(Posts.class).count());
        new Delete().from(Posts.class).query();
        Log.d(TAG, "count 削除後:" + new Delete().from(Posts.class).count());
    }

    private void addData() {
        Posts posts = new Posts();
        posts.date = new Date();
        posts.postId = "test01";
        posts.title = "title1";
        posts.description = "description1";
        posts.save();

        posts = new Posts();
        posts.date = new Date();
        posts.postId = "test02";
        posts.title = "title2";
        posts.description = "description2";
        posts.save();

        posts = new Posts();
        posts.date = new Date();
        posts.postId = "test03";
        posts.title = "title3";
        posts.description = "description3";
        posts.save();

        posts = new Posts();
        posts.date = new Date();
        posts.postId = "test04";
        posts.title = "title4";
        posts.description = "description4";
        posts.save();

        posts = new Posts();
        posts.date = new Date();
        posts.postId = "test05";
        posts.title = "title5";
        posts.description = "description5";
        posts.save();
    }

    private void selectData() {
        //全件取得
        mDatas = new Select().from(Posts.class).queryList();
    }

    private void initLayout() {
        ListView listView = (ListView) findViewById(R.id.listview);
        final MyAdapter adapter = new MyAdapter(this, mDatas);
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Toast.makeText(getApplicationContext(), "deleted! " + mDatas.get(position).postId, Toast.LENGTH_LONG).show();
                //1件削除
                mDatas.get(position).delete();
                mDatas.remove(position);
                adapter.swapItems(mDatas);
            }
        });
    }

    public class MyAdapter extends BaseAdapter {
        private Context mContext;
        private List<Posts> mDatas;

        public MyAdapter(@NonNull Context context, @Nullable List<Posts> datas) {
            super();
            mContext = context;
            mDatas = datas;
        }

        public void swapItems(@NonNull final List<Posts> datas) {
            mDatas = datas;
            notifyDataSetChanged();
        }

        @Override
        public int getCount() {
            if (mDatas == null) {
                return 0;
            }
            return mDatas.size();
        }

        @Override
        public Object getItem(int position) {
            return mDatas.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        class ViewHolder {
            TextView date;
            TextView postId;
            TextView title;
            TextView description;
        }

        SimpleDateFormat incomingFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
        SimpleDateFormat outgoingFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm",
                java.util.Locale.getDefault());

        @Override
        public View getView(final int position, View convertView, final ViewGroup parent) {
            ViewHolder holder = null;
            if (convertView == null) {
                LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
                convertView = inflater.inflate(R.layout.list_row, null);
                holder = new ViewHolder();

                holder.date = (TextView) convertView.findViewById(R.id.textView_date);
                holder.postId = (TextView) convertView.findViewById(R.id.textView_post_id);
                holder.title = (TextView) convertView.findViewById(R.id.textView_title);
                holder.description = (TextView) convertView.findViewById(R.id.textView_description);

                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }

            Posts data = (Posts) getItem(position);

            String date = outgoingFormat.format(data.date);
            holder.date.setText(date);
            holder.postId.setText(data.postId);
            holder.title.setText(data.title);
            holder.description.setText(data.description);

            return convertView;
        }
    }
}