Follow treslines by email clicking Here!

Tuesday, June 11, 2013

How to use ORMLite, save complex objects into SQLite and autogenerate DAO's in your Android App

Hi there! Today i'm gonna show you something really really nice if you are writing android apps using a SQLite model and are still creating entities by hand and embbeding SQLite commands in your code.

ORMLite is a lite version of an ORM tool to manipulate database access. It provides DAO's and SQL commands and you don't need to program it by yourself. It saves a lot of time. I was really surprised how easy it was to implement it. Because i think this a really nice tool, i want to share my expirience with you. I also solved the problem of saving complex objects with it. The example bellow is a little proof of concept i did. I use ORMLite to manage Entities, generate DTO's automatically and to facilitate communication between SQLite (database) and Application. The following link describes the use ORMLite: http://ormlite.com/javadoc/ormlite-core/doc-files/ormlite_4.html#Use-With-Android

Installation of ORMLite:

1. Download libraries ormlite-core.jar and ormlite-android.jar
2. Copy the libraries into folder libs of your android project. (Android will auto-reference it)
3. Create the desired entities and annotate them appropriately as in the following example: (make sure that the default-constructors were defined as ORMLite requires them to their proper operation.)




import java.util.Date;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;

@DatabaseTable(tableName = "contato")
public class ContatoEntity {

        // generatedId set to false, because we will get it from the server!
        @DatabaseField(id = true, generatedId = false) public int id;
        @DatabaseField public String descricao;
        @DatabaseField public String ddi;
        @DatabaseField public String ddd;
        @DatabaseField public Date timestamp;
        public ContatoEntity() {/*OrmLite need a default constructor*/}
}

import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;

@DatabaseTable(tableName = "pessoa")
public class PessoaEntity {

        @DatabaseField(id = true, generatedId = false) public int id;
        @DatabaseField public String nome;
        @DatabaseField(canBeNull = false, foreign = true) public TipoPessoaEntity tpPessoa;
        public PessoaEntity() {/*OrmLite need a default constructor*/}
//Atention: Take a special look at the annotation of tpPessoa bellow! we will discuss it later!
}

import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;

@DatabaseTable(tableName = "pessoa")
public class PessoaEntity {
        @DatabaseField(id = true, generatedId = false) public int id;
        @DatabaseField public String nome;
        @DatabaseField(canBeNull = false, foreign = true,foreignAutoCreate = true,foreignAutoRefresh = true)
        public TipoPessoaEntity tpPessoa;
        public PessoaEntity() {/*OrmLite need a default constructor*/}


Create a proper DatabaseOpenHelper

You helper must extend from OrmLiteSqliteOpenHelper as in the following example:



public class DatabaseHelper extends OrmLiteSqliteOpenHelper {

        private static final String DATABASE_NAME = "db_mobtur.db";
        private static final int DATABASE_VERSION = 1;

        public DatabaseHelper(Context context) {
                super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) {
                try {
                        Log.i(DatabaseHelper.class.getName(), "onCreate");
                        TableUtils.createTable(connectionSource, ContatoEntity.class);
                        TableUtils.createTable(connectionSource, PessoaEntity.class);
                        TableUtils.createTable(connectionSource, TipoPessoaEntity.class);
                } catch (SQLException e) {
                        Log.e(DatabaseHelper.class.getName(), "Can't create database", e);
                        throw new RuntimeException(e);
                }
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
                try {
                        Log.i(DatabaseHelper.class.getName(), "onUpgrade");
                        TableUtils.dropTable(connectionSource, ContatoEntity.class, true);
                        TableUtils.dropTable(connectionSource, PessoaEntity.class, true);
                        TableUtils.dropTable(connectionSource, TipoPessoaEntity.class, true);
                        onCreate(db, connectionSource);
                } catch (SQLException e) {
                        Log.e(DatabaseHelper.class.getName(), "Can't drop databases", e);
                        throw new RuntimeException(e);
                }
        }

        @Override
        public void close() {
                super.close();
        }
}


Create a class DaoFactory 

The DaoFactory is responsible for creating all DAO's we will need as in the following example:



public class DtoFactory extends Application {

        private SharedPreferences preferences;
        private DatabaseHelper databaseHelper = null;

        private Dao<ContatoEntity, Integer> contatoDAO = null;
        private Dao<PessoaEntity, Integer> pessoaDAO = null;
        private Dao<TipoPessoaEntity, Integer> tpContatoDAO = null;

        @Override
        public void onCreate() {
                super.onCreate();
                preferences = PreferenceManager.getDefaultSharedPreferences(this);
                databaseHelper = new DatabaseHelper(this);
        }

        public File getVideosDir() {return videos_dir;}
        public SharedPreferences getPreferences() {return preferences;}

        public Dao<ContatoEntity, Integer> getContatoDao() throws SQLException {
                if (contatoDAO == null) {
                        contatoDAO = databaseHelper.getDao(ContatoEntity.class);
                }
                return contatoDAO;
        }

        public Dao<PessoaEntity, Integer> getPessoaDao() throws SQLException {
                if (pessoaDAO == null) {
                        pessoaDAO = databaseHelper.getDao(PessoaEntity.class);
                }
                return pessoaDAO;
        }

        public Dao<TipoPessoaEntity, Integer> getTpPessoaDao() throws SQLException {
                if (tpContatoDAO == null) {
                        tpContatoDAO = databaseHelper.getDao(TipoPessoaEntity.class);
                }
                return tpContatoDAO;
        }

        @Override
        public void onTerminate() {
                super.onTerminate();
                if (databaseHelper != null) {
                        OpenHelperManager.releaseHelper();
                        databaseHelper = null;
                }
        }
} 

Using the entities in your Activity as desired

Warning: The ORMLite framework does not offer a mechanism for insertion of complex objects (classes that contains within itself another class as property). For that we must always keep in mind, that we have to first save the child class and then the parent class, and then combines them. The example below illustrates this problem with a solution. Note: If the class is composed of several hierarchias of composite properties, the only solution i found is to do a recursive method that dives into it and runs backwards inserting and linking the children to its parents. The followed annotation must be used be used by all child classes: (remember the annotation of pessoaEntitiy above? if not look at it now! ;-)


@DatabaseField(foreign = true,foreignAutoCreate = true,foreignAutoRefresh = true) 

This annotation ensures that the complex type (childs) are really saved in the database and not only updated. See the example bellow:



public class MainActivity extends Activity {

        private String LOG_TAG = MainActivity.class.getSimpleName();
        private DtoFactory dtoFactory;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                dtoFactory = (DtoFactory) getApplication();
        }

        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
                getMenuInflater().inflate(R.menu.main, menu);
                return true;
        }

        public void onclick(View view) throws SQLException {
                try {
                       
                        Dao<PessoaEntity, Integer> pDao = dtoFactory.getPessoaDao();
                        Dao<TipoPessoaEntity, Integer> tpDao = dtoFactory.getTpPessoaDao();

                        switch (view.getId()) {
                       
                        case R.id.btnInsert:
                               System.out.println("insert");
                               TipoPessoaEntity tpessoa = new TipoPessoaEntity();
                               tpessoa.id = new Random().nextInt(100);
                               tpessoa.descricao = "descricao_" + System.currentTimeMillis();
                               tpDao.create(tpessoa);
                              
                               PessoaEntity pe1 = new PessoaEntity();
                               pe1.id = new Random().nextInt(100);
                               pe1.nome = "TestName";
                               pe1.tpPessoa = tpessoa;
                               pDao.create(pe1);
                               break;
                              
                        case R.id.btnUpdate:
                               System.out.println("update");
                               break;
                              

                        case R.id.btnSelect:
                               System.out.println("select: " + pDao.countOf());
                               for (PessoaEntity entity : pDao.queryForAll()) {
                                       System.out.println(String.format("Id: %d, Nome: %s, Tp: %s", entity.id, entity.nome, entity.tpPessoa.descricao));
                                       Log.d(LOG_TAG, String.format("Id: %d, Nome: %s, Tp: %s", entity.id, entity.nome, entity.tpPessoa.descricao));
                               }
                               break;
                              
                        case R.id.btnDelete:
                               System.out.println("delete");
                               break;
                              
                        default:
                               break;
                        }
                } catch (Exception e) {
                        System.out.println();
                }
        }

        @Override
        protected void onDestroy() {
                super.onDestroy();
        }
}



1  Proof of Concept

Copy this xml code into your MainActivity: 



<LinearLayout 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:orientation="vertical"
    android:padding="10dp"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/btnInsert"
        android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="onclick" android:text="Insert"/>

    <Button
        android:id="@+id/btnUpdate"
        android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:onClick="onclick" android:text="Update"/>

    <Button
        android:id="@+id/btnSelect"
        android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:onClick="onclick" android:text="Select"/>

    <Button
        android:id="@+id/btnDelete"
        android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:onClick="onclick" android:text="Delete"/>

</LinearLayout>

Testing on the Emulator 

If everything goes right you should have something like this at this point of the post:


Result