Пример приложения с GreenDAO. Зеленый помощник в работе с БД.

Если вы читаете эти строки, то, скорее всего, вам захотелось чего-то нового в отношениях с вашей базой данных. Вам надоел курсор, SQL запросы вы пишете каждый день и ощущение обыденности остудило те чувства, с которыми вы впервые сохраняли данные в БД.  Что же, GreenDAO внесёт свежий поток в процесс разработки!

Библиотека от немецкой компании Greenrobot позиционируется как инструмент для объектно-реляционного отображения  (ORM) данных в приложениях на Android, являясь прослойкой, реализующей шаблон проектирования DAO(Data Access Object). Если вы не знаете что это за шаблон, то многое в статье будет не понятно и лучше для начала прочитать эту статью.  На момент написания статьи ребята выпустили уже версию 3.2.0, в которой побороли многие детские болячки и прихлопнули горсть багов.  С помощью аннотаций происходит разметка сущности, требующей сохранения, создание DAO классов, таблиц и всю рутинную работу с ними библиотека взваливает на себя.

1.С чего начать?

Дружба начинается с улыбки, а использование библиотеки с её подключения в проект. В нашем случае одной строчкой в Gradle отделаться не получиться, всё чуть-чуть сложнее.  Для начала идём в Gradle проекта и подключаем пути, добавляя строчку classpath ‘org.greenrobot:greendao-gradle-plugin:3.2.1’

как на скриншоте:

 

Далее уже в Gradle файле модуля добавляем зависимости и подключаем плагин:

apply plugin: ‘org.greenrobot.greendao’

compile ‘org.greenrobot:greendao:3.2.0’

Плагин подключаем обязательно выше всех, в первой строчке. Баг это или фича – не нам судить. Ещё необходимо выполнить начальную конфигурацию GreenDAO, для чего добавляем блок

greendao{
    schemaVersion 1
    targetGenDir ‘src/main/java’
}

как указано на скриншоте:

Этот блок выполнит настройку GreenDAO. При тривиальных задачах можно не вникать в тюнинг библиотеки, но schemaVersion вы в любом случае должны будете указать и в случае её изменения инкрементировать это значение. Но если вам интересно, есть такие опции как:

schemaVersion – текущая версия схемы базы данных. Настройка используется OpenHelper’s классами для миграции между версиями схем. Как я и говорил, при изменении схемы делаете инкремент. По умолчанию значение 1.

daoPackage – Имя пакета для генерации DAOs, DaoMaster и DaoSession. По умолчанию – имя пакета, в котором будет находиться исходная сущность.

targetGenDir – место, где будут сгенерированы исходники. По умолчанию – директория проекта.

GenerateTests – можно задать true для автоматической генерации юнит-тестов.

targetGenDirTests – директория с тестами.

Всё, подключение выполнено.


2. Создание бизнес-модели.

Давайте теперь немного отвлечемся и посмотрим на то, что будем сохранять. У нас будет приложение – питомник котиков. Можно будет добавлять новых и отдавать в добрые руки старых котов. У котиков будут свойства — имя и порода. Давайте же опишем класс, не забыв добавить в него геттеры и сеттеры.

package info.javaway.greendaotutorial.model;

public class Cat {

    private String breed;

    private String name;

    public Cat(String breed, String name) {
        this.breed = breed;
        this.name = name;
    }

    public String getBreed() {
        return breed;
    }

    public void setBreed(String breed) {
        this.breed = breed;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 

Для наглядной демонстрации создадим список RecyclerView, который будет хранить в себе котиков. По нажатию кнопки будет создаваться новый котэ и добавляться в RecyclerView, по тапу на элемент списка будет происходить его удаление. Адаптер и холдер выглядят так:

package info.javaway.greendaotutorial;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.List;

import info.javaway.greendaotutorial.model.Cat;


public class CatAdapter extends RecyclerView.Adapter<CatAdapter.CatHolder> {

    private List<Cat> mCats;
    private LayoutInflater mInflater;

    public CatAdapter(List<Cat> cats, LayoutInflater inflater) {
        mCats = cats;
        mInflater = inflater;
    }

    @Override
    public CatHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = mInflater.inflate(R.layout.cat_item, parent, false);
        return new CatHolder(view);
    }

    @Override
    public void onBindViewHolder(CatHolder holder, int position) {
        Cat cat = mCats.get(position);
        holder.bindCat(cat);
    }

    @Override
    public int getItemCount() {
        return mCats.size();
    }

    class CatHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        private TextView mCatName;
        private TextView mCatBreed;


        public CatHolder(View itemView) {
            super(itemView);
            mCatName = (TextView) itemView.findViewById(R.id.cat_name);
            mCatBreed = (TextView) itemView.findViewById(R.id.cat_breed);
            itemView.setOnClickListener(this);

        }

        public void bindCat(Cat cat) {
            mCatName.setText(cat.getName());
            mCatBreed.setText(cat.getBreed());
        }

        @Override
        public void onClick(View view) {
            mCats.remove(getLayoutPosition());
            notifyDataSetChanged();
        }
    }
}

В разметке всего 2 файла, для отображения элемента списка и главной активити:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_marginTop="8dp"
        android:id="@+id/create_cat_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <android.support.design.widget.TextInputLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="Введите имя"
            >
            <EditText
                android:id="@+id/cat_name_et"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
        </android.support.design.widget.TextInputLayout>

        <android.support.design.widget.TextInputLayout

            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="Введите породу">
            <EditText
                android:id="@+id/cat_breed_et"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
        </android.support.design.widget.TextInputLayout>

    </LinearLayout>

    <LinearLayout
        android:id="@+id/separator"
        android:layout_width="match_parent"
        android:layout_height="2dp"
        android:layout_below="@id/create_cat_layout"
        android:orientation="horizontal"
        android:background="@color/black"></LinearLayout>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_margin="16dp"
        android:src="@drawable/ic_add"/>

    <android.support.v7.widget.RecyclerView
        android:layout_below="@id/separator"
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorPrimary"
        android:scrollbars="vertical"
        />
</RelativeLayout>

cat_item.xml

<?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="wrap_content"
              android:layout_marginBottom="2dp"
              android:background="#FFFFFF"
              android:orientation="horizontal"
              android:padding="4dp"
              android:paddingBottom="8dp"
              android:paddingTop="8dp"
    >

    <TextView
        android:id="@+id/cat_name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="4dp"
        android:layout_weight="1"
        android:background="#FFFFFF"
        android:textAlignment="center"
        android:textSize="18sp"/>

    <TextView
        android:id="@+id/cat_breed"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="4dp"
        android:layout_marginRight="8dp"
        android:layout_weight="1"
        android:background="#FFFFFF"
        android:textAlignment="center"
        android:textSize="18sp"/>
</LinearLayout>

Код активити:

package info.javaway.greendaotutorial;

import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

import info.javaway.greendaotutorial.model.Cat;

public class MainActivity extends AppCompatActivity {

    private FloatingActionButton mFab;
    private EditText mCatNameEditText;
    private EditText mCatBreedEditText;
    private RecyclerView mRecyclerView;
    private List<Cat> mCats;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        mFab = (FloatingActionButton) findViewById(R.id.fab);
        mCatNameEditText = (EditText) findViewById(R.id.cat_name_et);
        mCatBreedEditText = (EditText) findViewById(R.id.cat_breed_et);

        mCats = new ArrayList<>();
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        final CatAdapter adapter = new CatAdapter(mCats, getLayoutInflater());
        mRecyclerView.setAdapter(adapter);

        mFab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String name = mCatNameEditText.getText().toString();
                String breed = mCatBreedEditText.getText().toString();
                mCatNameEditText.setText("");
                mCatBreedEditText.setText("");
                mCatNameEditText.clearFocus();
                mCatBreedEditText.clearFocus();
                if(name.trim().equals("") || breed.trim().equals("")){
                    Toast.makeText(MainActivity.this, "Имя или порода не заполнены", Toast.LENGTH_SHORT).show();
                    return;
                }
                Cat cat = new Cat(name, breed);
                mCats.add(cat);
                adapter.notifyDataSetChanged();
            }
        });
    }
}

 

Добавляем необходимые библиотеки в Gradle

compile ‘com.android.support:recyclerview-v7:25.3.0’
compile ‘com.android.support:design:25.3.0’

Ну вот, котики добавляются и по нажатию отдаются в новые семьи, добро восторжествовало. И теперь давайте займёмся сохранением данных, тем ради чего мы тут сегодня и собрались.


 

3.Подготовка к отображению в БД.

Библиотека использует аннотации, с помощью которых мы подготовим наших котиков к их отображению в базе данных. Добавим к классу нашего котика немного аннотаций, подготовим его к упаковке.

@Entity(active = true, nameInDb = "CATS")
public class Cat {

    @Id
    private Long id;

    @NotNull
    private String breed;

    @NotNull
    private String name;

    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getBreed() {
        return this.breed;
    }

    public void setBreed(String breed) {
        this.breed = breed;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 

Что изменилось? Начнем сверху. Перед описанием класса находиться анноттация @Entity. Она сообщает GreenDAO что необходимо создать в базе данных таблицу с отображением помеченной сущности. Для этой аннотации можно задать дополнительные параметры:

 

@Entity(

        //Если у нас больше одной схемы, то необходимо сообщить,
        //к какой из них принадлежит объект.

        schema = "myschema",
        
        //Флаг делает сущность "active": активные сущности могут быть          
       // обновлены, удалены, у них можно изменять методы

       active = true,
        
        // Имя таблицы в базе дынных
        // По умолчанию будет использовано имя класса

        nameInDb = "AWESOME_CATS",
        
        // Можно задать индексы сразу нескольким столбцам

        indexes = {
                @Index(value = "name DESC", unique = true)
        },

        
        // Флаг, сообщающий GreenDAO создать таблицу.
        // Задайте false, если хотите много сущностей отобразить в
       // одной таблице или таблица создана снаружи уровня DAO
        createInDb = false,



        // Нужен ли конструктор со всеми свойствами?
        // Конструктор без аргументов нужен всегда.

        generateConstructors = true,

        // Генерировать ли геттеры и сеттеры, если они отсутствуют?
        generateGettersSetters = true
)

public class Cat {
  ...
}

 

Но в нашем случае можно было бы обойтись вообще без дополнительных параметров для аннотации, я их добавил для демонстрации.

Спускаемся ниже, где встречаем новое поле класса – id с аннотацией #Id. Так называемый суррогатный ключ. В бизнес модели он то нам не нужен, его судьба – быть первичным ключом в таблице, во что его и превращает аннотация @Id.

Поля breed и name помечены аннотациями @NotNull, что сообщает GreenDAO что в соответствующих столбцах не смогут храниться null значения. Данной аннотацией нельзя помечать примитивные поля, если такая необходимость возникла, то используйте классы-обёртки.

Есть ещё несколько аннотаций, одна из которых @Transient – исключает поле из процесса сохранения.

Переходим к самому интересному Хотите немного айтишной магии? Выполним сборку проекта(Build -> Make Project или Ctrl+F9). Проект собирается, но вы можете заметить, что мы теперь не одни, рядом с классом Cat теперь появились дополнительные классы, которые реализуют шаблон DAO. Но это ещё не всё, код нашего класса Cat также подвергся изменениям, а точнее дополнениям! Всё что сгенерировано дополнительно, помечается аннотацией @Generated. Разработчики настоятельно не рекомендуют править сгенерированный код, но если уж такая необходимость появляется, то замените после правки аннотацию @Generated на @Keep.

После первого сбора проекта мы можем пользоваться классами greenDAO.

DaoMaster – это точка входа для использования DreenDAO. DaoMaster содержит объект базы данных(SQLiteDatabase) и менеджеры DAO —  классы, описывающие схемы.  Также в нём есть статичные методы для создания и удаления таблиц. Его внутренние классы OpenHendler и DevOpenHendler являющиеся реализациями SQLiteOpenHendler, которые создают схему в базе данных SQLite.

DaoSession – управляет всеми доступными DAO объектами для описанной схемы, которые можно получить с помощью геттеров. DaoSession также предоставляет некоторые обобщенные методы, такие как insert, load, update, refresh и delete для сущностей.

CatDao – объект доступа к данным, сохраняющий и загружающий сущности. Для каждой сущности greenDao создает объект сущностьDao(Cat – CatDao, Man – ManDao, Client – ClientDao и т.п.).  CatDao содержит уже расширенный список методов для работы с базой данных, например – count, loadAll и insertLnTx.

В контексте нашего примера мы можем создавать экземпляры DAO классов непосредственно перед использованием, на нашу архитектуру это никак не повлияет(так как её практически нет >_<). Но давайте всё же сделаем правильно и вынесем в отдельное место инициализацию. Для этого создадим класс App и унаследуемся от Application(не забудьте указать его файлом приложения в манифесте).

public class App extends Application {
    private DaoSession daoSession;

    @Override
    public void onCreate() {
        super.onCreate();

        DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "mycats-db");
        Database db = helper.getWritableDb();
        daoSession = new DaoMaster(db).newSession();
    }

    public DaoSession getDaoSession() {
        return daoSession;
    }
}

манифест

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="info.javaway.greendaotutorial">

    <application
        android:name=".App"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

Всё готово, теперь мы можем использовать классы DAO для сохранения и загрузки наших котиков. Финальная версия класса MainActivity:

package info.javaway.greendaotutorial;

import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import org.greenrobot.greendao.query.Query;

import java.util.ArrayList;
import java.util.List;

import info.javaway.greendaotutorial.model.Cat;
import info.javaway.greendaotutorial.model.CatDao;
import info.javaway.greendaotutorial.model.DaoSession;

public class MainActivity extends AppCompatActivity {

    private FloatingActionButton mFab;
    private EditText mCatNameEditText;
    private EditText mCatBreedEditText;
    private RecyclerView mRecyclerView;

    private CatAdapter adapter;
    private List<Cat> mCats;

    public CatDao getCatDao() {
        return mCatDao;
    }

    private CatDao mCatDao;
    private Query<Cat> catsQuery;


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

        setupViews();

        DaoSession daoSession = ((App)getApplication()).getDaoSession();
        mCatDao = daoSession.getCatDao();

        catsQuery = mCatDao.queryBuilder().orderAsc(CatDao.Properties.Name).build();
        updateCats();
    }

    public void updateCats() {
        mCats = catsQuery.list();
        adapter.setCats(mCats);
    }

    private void setupViews() {
        mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        mFab = (FloatingActionButton) findViewById(R.id.fab);
        mCatNameEditText = (EditText) findViewById(R.id.cat_name_et);
        mCatBreedEditText = (EditText) findViewById(R.id.cat_breed_et);

        mCats = new ArrayList<>();
        adapter = new CatAdapter(mCats, getLayoutInflater(), this);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.setAdapter(adapter);


        mFab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                addCat();

            }
        });
    }

    private void addCat() {
        String name = mCatNameEditText.getText().toString();
        String breed = mCatBreedEditText.getText().toString();
        mCatNameEditText.setText("");
        mCatBreedEditText.setText("");
        mCatNameEditText.clearFocus();
        mCatBreedEditText.clearFocus();

        if (name.trim().equals("") || breed.trim().equals("")) {
            Toast.makeText(MainActivity.this, "Имя или порода не заполнены", Toast.LENGTH_SHORT).show();
            return;
        }
        Cat cat = new Cat();
        cat.setName(name);
        cat.setBreed(breed);
        mCatDao.insert(cat);
        updateCats();
    }


}

 

 

И адаптера:

package info.javaway.greendaotutorial;

import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.List;

import info.javaway.greendaotutorial.model.Cat;


public class CatAdapter extends RecyclerView.Adapter<CatAdapter.CatHolder> {

    private List<Cat> mCats;
    private LayoutInflater mInflater;
    private AppCompatActivity mAppCompatActivity;

    public CatAdapter(List<Cat> cats, LayoutInflater inflater, AppCompatActivity activity) {
        mCats = cats;
        mInflater = inflater;
        mAppCompatActivity = activity;
    }

    @Override
    public CatHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = mInflater.inflate(R.layout.cat_item, parent, false);
        return new CatHolder(view);
    }

    @Override
    public void onBindViewHolder(CatHolder holder, int position) {
        Cat cat = mCats.get(position);
        holder.bindCat(cat);
    }

    @Override
    public int getItemCount() {
        return mCats.size();
    }

    public void setCats(List<Cat> cats) {
        mCats = cats;
        notifyDataSetChanged();
    }

    class CatHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        private TextView mCatName;
        private TextView mCatBreed;
        private Cat cat;


        public CatHolder(View itemView) {
            super(itemView);
            mCatName = (TextView) itemView.findViewById(R.id.cat_name);
            mCatBreed = (TextView) itemView.findViewById(R.id.cat_breed);
            itemView.setOnClickListener(this);

        }

        public void bindCat(Cat cat) {
            this.cat = cat;
            mCatName.setText(cat.getName());
            mCatBreed.setText(cat.getBreed());
        }

        @Override
        public void onClick(View view) {
            Cat cat = mCats.get(getLayoutPosition());
            Long catId = cat.getId();
            ((MainActivity)mAppCompatActivity).getCatDao().deleteByKey(catId);
            ((MainActivity)mAppCompatActivity).updateCats();
        }
    }
}

Ну вот, теперь наши коты не разбегаются при выходе из приложения, а аккуратно упаковываются в базу данных.

По-моему библиотека заслуживает внимания, она мало весит и показывает хорошие результаты в скорости.  Если нужно, могу дополнить пример ещё несколькими таблицами с разными связями, код из примера можете взять на Гитхабе. Всем добра!

P.s. Если есть неудержимое желание поддержать Javaway и котиков, можете сделать это тут =)

Понравилась статья? Поделиться с друзьями:

Комментарии:

Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: