Adding custom search suggestions to search dialog from SQLite database in Android

July 11, 2013
By

In this article, we will develop an Android application containing a search dialog, that can search a set of countries stored in an SQLite database. The search dialog is facilitated with custom search suggestions containing texts and images. The search dialog fetches the data from SQLite database using cursor loader via content provider.



Major files used in this application are listed below :

  • MainActivity.java : Search dialog is invoked from the MainActivity class defined in this file.
  • SearchableActivity.java : Actions performed on search dialog is handled by SearchableActivity.
  • CountryActivity.java : The details of the selected country is displayed in this activity. The selection can be done either from suggestion for search dialog or from listview of SearchableActivity.
  • searchable.xml : This is the configuration file for search dialog which defines various properties like content provider for custom suggestions, intent data for selected suggested item, search suggestions to quick search box etc
  • CountryContentProvider.java : The content provider that provides necessary data for the search dialog from table class.
  • CountryDB.java : A table class that fetches data from the SQLite database.
This application is developed in Eclipse (4.2.1) with ADT plugin (22.0.1) and Android SDK (22.0.1).



1. Create new Android application project namely “SearchDialogDemo” with the given below details

Application Name : SearchDialogDemo

Project Name : SearchDialogDemo

Package Name : in.wptrafficanalyzer.searchdialogdemo

Minimum Required SDK : API 8 : Android 2.2 ( Froyo )

Target SDK : API 17 : Android 4.2 ( Jelly Bean )


2. Add Android Support library to this project

By default, Android support library (android-support-v4.jar ) is added to this project by Eclipse IDE to the directory libs. If it is not added, we can do it manually by doing the following steps :

  • Open Project Explorer by Clicking “Window -> Show View -> Project Explorer”
  • Right click this project
  • Then from popup menu, Click “Android Tools -> Add Support Library “

3. Create a folder namely res/drawable and extract the zip file to into it.


4. Update the file res/values/strings.xml


<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">SearchDialogDemo</string>
    <string name="action_settings">Settings</string>
    <string name="hello_world">Hello world!</string>
    <string name="search">Search Country</string>
    <string name="search_hint">Search names</string>
    <string name="flag_description">Country Flag</string>
    <string name="search_settings">Search Country Names</string>

</resources>


5. Create the layout file res/layout/activity_main.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"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/btn_search"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/search"
        android:layout_centerInParent="true" />

</RelativeLayout>

6. Create the layout file res/layout/activity_searchable.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="match_parent"
    android:orientation="vertical" >

    <ListView
        android:id="@+id/lv_countries"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

    </LinearLayout>


7. Create the layout file res/layout/activity_country.xml

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

    <ImageView
        android:id="@+id/iv_flag"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:contentDescription="@string/flag_description"/>

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/iv_flag"
        android:textSize="25sp" />

    <TextView
        android:id="@+id/tv_currency"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/tv_name"
        android:textSize="25sp" />

</RelativeLayout>

8. Create a class Country in the file src/in/wptrafficanalyzer/searchdialogdemo/Country.java

package in.wptrafficanalyzer.searchdialogdemo;

/** Country details are stored in this class and is used to populate the table countries
 * in CountryDb.java
 */
public class Country {
    // Array of strings storing country names
    static String[] countries = new String[] {
        "India",
        "Pakistan",
        "Sri Lanka",
        "China",
        "Bangladesh",
        "Nepal",
        "Afghanistan",
        "North Korea",
        "South Korea",
        "Japan"
    };

    // Array of integers points to images stored in /res/drawable
        static int[] flags = new int[]{
        R.drawable.india,
        R.drawable.pakistan,
        R.drawable.srilanka,
        R.drawable.china,
        R.drawable.bangladesh,
        R.drawable.nepal,
        R.drawable.afghanistan,
        R.drawable.nkorea,
        R.drawable.skorea,
        R.drawable.japan
    };

    // Array of strings to store currencies
    static String[] currency = new String[]{
        "Indian Rupee",
        "Pakistani Rupee",
        "Sri Lankan Rupee",
        "Renminbi",
        "Bangladeshi Taka",
        "Nepalese Rupee",
        "Afghani",
        "North Korean Won",
        "South Korean Won",
        "Japanese Yen"
    };
}

9. Create the class CountryDB in the file src/in/wptrafficanalyzer/searchdialogdemo/CountryDB.java


package in.wptrafficanalyzer.searchdialogdemo;

import java.util.HashMap;

import android.app.SearchManager;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;

public class CountryDB{

    private static final String DBNAME = "country";
    private static final int VERSION = 1;
    private CountryDBOpenHelper mCountryDBOpenHelper;
    private static final String FIELD_ID = "_id";
    private static final String FIELD_NAME = "name";
    private static final String FIELD_FLAG = "flag";
    private static final String FIELD_CURRENCY = "currency";
    private static final String TABLE_NAME = "countries";
    private HashMap<String, String> mAliasMap;

    public CountryDB(Context context){
        mCountryDBOpenHelper = new CountryDBOpenHelper(context, DBNAME, null, VERSION);

        // This HashMap is used to map table fields to Custom Suggestion fields
        mAliasMap = new HashMap<String, String>();

        // Unique id for the each Suggestions ( Mandatory )
        mAliasMap.put("_ID", FIELD_ID + " as " + "_id" );

        // Text for Suggestions ( Mandatory )
        mAliasMap.put(SearchManager.SUGGEST_COLUMN_TEXT_1, FIELD_NAME + " as " + SearchManager.SUGGEST_COLUMN_TEXT_1);

// Icon for Suggestions ( Optional )
        mAliasMap.put( SearchManager.SUGGEST_COLUMN_ICON_1, FIELD_FLAG + " as " + SearchManager.SUGGEST_COLUMN_ICON_1);

        // This value will be appended to the Intent data on selecting an item from Search result or Suggestions ( Optional )
        mAliasMap.put( SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, FIELD_ID + " as " + SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID );
    }

    /** Returns Countries */
    public Cursor getCountries(String[] selectionArgs){

        String selection = FIELD_NAME + " like ? ";

        if(selectionArgs!=null){
            selectionArgs[0] = "%"+selectionArgs[0] + "%";
        }

        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
        queryBuilder.setProjectionMap(mAliasMap);

        queryBuilder.setTables(TABLE_NAME);

        Cursor c = queryBuilder.query(mCountryDBOpenHelper.getReadableDatabase(),
            new String[] { "_ID",
                            SearchManager.SUGGEST_COLUMN_TEXT_1 ,
                            SearchManager.SUGGEST_COLUMN_ICON_1 ,
                            SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID } ,
            selection,
            selectionArgs,
            null,
            null,
            FIELD_NAME + " asc ","10"
        );
        return c;
    }

    /** Return Country corresponding to the id */
    public Cursor getCountry(String id){

        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();

        queryBuilder.setTables(TABLE_NAME);

        Cursor c = queryBuilder.query(mCountryDBOpenHelper.getReadableDatabase(),
            new String[] { "_id", "name", "flag", "currency" } ,
            "_id = ?", new String[] { id } , null, null, null ,"1"
        );

        return c;
    }
    class CountryDBOpenHelper extends SQLiteOpenHelper{

        public CountryDBOpenHelper( Context context,
            String name,
            CursorFactory factory,
            int version ) {
                super(context, DBNAME, factory, VERSION);
             }

        @Override
        public void onCreate(SQLiteDatabase db) {
            String sql = "";

            // Defining table structure
            sql =   " create table " + TABLE_NAME + "" +
                    " ( " +
                    FIELD_ID + " integer primary key autoincrement, " +
                    FIELD_NAME + " varchar(100), " +
                    FIELD_FLAG + " int, " +
                    FIELD_CURRENCY + " varchar(100) " +
                    " ) " ;

            // Creating table
            db.execSQL(sql);

            for(int i=0;i<Country.countries.length;i++){

                // Defining insert statement
                sql = "insert into " + TABLE_NAME + " ( " +
                        FIELD_NAME + " , " +
                        FIELD_FLAG + " , " +
                        FIELD_CURRENCY + " ) " +
                        " values ( " +
                            " '" + Country.countries[i] + "' ," +
                            " " + Country.flags[i] + " ," +
                            " '" + Country.currency[i] + "' ) ";

                // Inserting values into table
                db.execSQL(sql);
            }
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            // TODO Auto-generated method stub
        }
    }
}


10. Creating the content provider in the file “src/in/wptrafficanalyzer/searchdialogdemo/CountryContentProvider.java


package in.wptrafficanalyzer.searchdialogdemo;

import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;

public class CountryContentProvider extends ContentProvider {

    public static final String AUTHORITY = "in.wptrafficanalyzer.searchdialogdemo.CountryContentProvider";
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/countries" );

    CountryDB mCountryDB = null;

    private static final int SUGGESTIONS_COUNTRY = 1;
    private static final int SEARCH_COUNTRY = 2;
    private static final int GET_COUNTRY = 3;

    UriMatcher mUriMatcher = buildUriMatcher();

    private UriMatcher buildUriMatcher(){
        UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

        // Suggestion items of Search Dialog is provided by this uri
        uriMatcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY,SUGGESTIONS_COUNTRY);

        // This URI is invoked, when user presses "Go" in the Keyboard of Search Dialog
        // Listview items of SearchableActivity is provided by this uri
        // See android:searchSuggestIntentData="content://in.wptrafficanalyzer.searchdialogdemo.provider/countries" of searchable.xml
        uriMatcher.addURI(AUTHORITY, "countries", SEARCH_COUNTRY);

        // This URI is invoked, when user selects a suggestion from search dialog or an item from the listview
        // Country details for CountryActivity is provided by this uri
        // See, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID in CountryDB.java
        uriMatcher.addURI(AUTHORITY, "countries/#", GET_COUNTRY);

        return uriMatcher;
    }

    @Override
    public boolean onCreate() {
        mCountryDB = new CountryDB(getContext());
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
        String[] selectionArgs, String sortOrder) {

        Cursor c = null;
        switch(mUriMatcher.match(uri)){
            case SUGGESTIONS_COUNTRY :
                c = mCountryDB.getCountries(selectionArgs);
                break;
            case SEARCH_COUNTRY :
                c = mCountryDB.getCountries(selectionArgs);
                break;
            case GET_COUNTRY :
                String id = uri.getLastPathSegment();
                c = mCountryDB.getCountry(id);
        }

        return c;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        throw new UnsupportedOperationException();
    }

    @Override
    public String getType(Uri uri) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        throw new UnsupportedOperationException();
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
        String[] selectionArgs) {
        throw new UnsupportedOperationException();
    }
}


11. Create a class namely MainActivity in the file src/in/wptrafficanalyzer/searchdialogdemo/MainActivity.java

package in.wptrafficanalyzer.searchdialogdemo;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends FragmentActivity{

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

        Button btn = (Button) findViewById(R.id.btn_search);

        btn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                onSearchRequested();
            }
        });
    }
}

12. Create the class namely “SearchableActivity” in the file src/in/wptrafficanalyzer/searchdialogdemo/SearchableActivity.java


package in.wptrafficanalyzer.searchdialogdemo;

import android.app.SearchManager;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.SearchRecentSuggestions;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SimpleCursorAdapter;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;

public class SearchableActivity extends FragmentActivity implements LoaderCallbacks<Cursor> {

    ListView mLVCountries;
    SimpleCursorAdapter mCursorAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_searchable);

        // Getting reference to Country List
        mLVCountries = (ListView)findViewById(R.id.lv_countries);

        // Setting item click listener
        mLVCountries.setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                Intent countryIntent = new Intent(getApplicationContext(), CountryActivity.class);

                // Creating a uri to fetch country details corresponding to selected listview item
                Uri data = Uri.withAppendedPath(CountryContentProvider.CONTENT_URI, String.valueOf(id));

                // Setting uri to the data on the intent
                countryIntent.setData(data);

                // Open the activity
                startActivity(countryIntent);
            }
        });

        // Defining CursorAdapter for the ListView
        mCursorAdapter = new SimpleCursorAdapter(getBaseContext(),
            android.R.layout.simple_list_item_1,
            null,
            new String[] { SearchManager.SUGGEST_COLUMN_TEXT_1},
            new int[] { android.R.id.text1}, 0);

        // Setting the cursor adapter for the country listview
        mLVCountries.setAdapter(mCursorAdapter);

        // Getting the intent that invoked this activity
        Intent intent = getIntent();

        // If this activity is invoked by selecting an item from Suggestion of Search dialog or
        // from listview of SearchActivity
        if(intent.getAction().equals(Intent.ACTION_VIEW)){
            Intent countryIntent = new Intent(this, CountryActivity.class);
            countryIntent.setData(intent.getData());
            startActivity(countryIntent);
            finish();
        }else if(intent.getAction().equals(Intent.ACTION_SEARCH)){ // If this activity is invoked, when user presses "Go" in the Keyboard of Search Dialog
            String query = intent.getStringExtra(SearchManager.QUERY);
            doSearch(query);
        }
    }

    private void doSearch(String query){
        Bundle data = new Bundle();
        data.putString("query", query);

        // Invoking onCreateLoader() in non-ui thread
        getSupportLoaderManager().initLoader(1, data, this);
    }
    /** This method is invoked by initLoader() */
    @Override
    public Loader<Cursor> onCreateLoader(int arg0, Bundle data) {
        Uri uri = CountryContentProvider.CONTENT_URI;
        return new CursorLoader(getBaseContext(), uri, null, null , new String[]{data.getString("query")}, null);
    }

    /** This method is executed in ui thread, after onCreateLoader() */
    @Override
        public void onLoadFinished(Loader<Cursor> arg0, Cursor c) {
        mCursorAdapter.swapCursor(c);
    }
    @Override
    public void onLoaderReset(Loader<Cursor> arg0) {
        // TODO Auto-generated method stub
    }
}


13. Create the class “CountryActivity” in the file src/in/wptrafficanalyzer/searchdialogdemo/CountryActivity.java

package in.wptrafficanalyzer.searchdialogdemo;

import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.widget.ImageView;
import android.widget.TextView;

public class CountryActivity extends FragmentActivity implements LoaderCallbacks<Cursor>{

    private Uri mUri;
    private ImageView mIvFlag;
    private TextView mTvName;
    private TextView mTvCurrency;

    @Override
    protected void onCreate(Bundle arg0) {
        super.onCreate(arg0);
        setContentView(R.layout.activity_country);

        Intent intent = getIntent();
        mUri = intent.getData();

        mIvFlag = (ImageView) findViewById(R.id.iv_flag);
        mTvName = (TextView) findViewById(R.id.tv_name);
        mTvCurrency = (TextView) findViewById(R.id.tv_currency);

        // Invokes the method onCreateloader() in non-ui thread
        getSupportLoaderManager().initLoader(0, null, this);

    }

    /** Invoked by initLoader() */
    @Override
    public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
        return new CursorLoader(getBaseContext(), mUri, null, null , null, null);
    }

    /** Invoked by onCreateLoader(), will be executed in ui-thread */
    @Override
    public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
        if(cursor.moveToFirst()){
            mIvFlag.setImageResource(cursor.getInt(cursor.getColumnIndex(cursor.getColumnName(2))));
            mTvName.setText("Country: "+cursor.getString(cursor.getColumnIndex(cursor.getColumnName(1))));
            mTvCurrency.setText("Currency: "+cursor.getString(cursor.getColumnIndex(cursor.getColumnName(3))));
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> arg0) {
        // TODO Auto-generated method stub
    }
}

14. Create the configuration file for search dialog in the file res/xml/searchable.xml

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/app_name"
    android:hint="@string/search_hint"
    android:searchSettingsDescription="@string/search_settings"

    android:searchSuggestAuthority="in.wptrafficanalyzer.searchdialogdemo.CountryContentProvider"
    android:searchSuggestIntentAction="android.intent.action.VIEW"
    android:searchSuggestIntentData="content://in.wptrafficanalyzer.searchdialogdemo.CountryContentProvider/countries"
    android:searchSuggestSelection=" ?"
    android:searchSuggestThreshold="1"

    android:includeInGlobalSearch="true" >
</searchable>

15. Update the file AndroidManifest.xml


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="in.wptrafficanalyzer.searchdialogdemo"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >

    <!-- Activity with SearchDialog enabled -->
    <activity
        android:name="in.wptrafficanalyzer.searchdialogdemo.MainActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    <!-- Enabling Search Dialog -->
    <meta-data android:name="android.app.default_searchable"
        android:value=".SearchableActivity" />
    </activity>

    <!-- A Searchable activity, that handles the searches -->
    <activity android:name=".SearchableActivity" >
        <intent-filter>
            <action android:name="android.intent.action.SEARCH" />
        </intent-filter>
        <meta-data android:name="android.app.searchable"
            android:resource="@xml/searchable"/>
    </activity>

    <!-- Activity that shows the country details -->
    <activity android:name=".CountryActivity" />

    <!-- Content Provider to query sqlite database -->
    <provider
        android:name=".CountryContentProvider"
        android:authorities="in.wptrafficanalyzer.searchdialogdemo.CountryContentProvider"
        android:exported="true" />
    </application>

</manifest>


16. Screenshots of the application

MainActivity of the application

Figure 1 : MainActivity of the application

Showing Custom Suggestions from SQLite database

Figure 2 : Showing Custom Suggestions from SQLite database

Showing country details in CountryActivity

Figure 3 : Showing country details in CountryActivity

Searching in Quick  Search Box

Figure 4 : Searching in Quick Search Box


17. Download Source Code


How to hire me?

I am George Mathew, working as software architect and Android app developer at wptrafficanalyzer.in

You can hire me on hourly basis or on project basis for Android applications development.

For hiring me, please mail your requirements to info@wptrafficanalyzer.in.

My other blogs
store4js.blogspot.com


Android Knowledge Quiz

Ready to test your knowledge in Android? Take this quiz :



Tags: , , , , ,

12 Responses to Adding custom search suggestions to search dialog from SQLite database in Android

  1. ALB on December 4, 2013 at 1:53 am

    Thank you so much for this very helpful and easily adaptable example…unlike many tutorials, this one works perfectly!

  2. Andrés Holguín on January 28, 2014 at 10:14 pm

    Thanks for this example, how can I use search widget (Since API 11) using this example?

  3. Ashwin Prabu on December 10, 2014 at 11:34 am

    Hi,
    I want to display the camera preview in two layouts.How can i achieve this stuff.please send your valuable reply.

  4. praroop on February 2, 2015 at 11:00 am

    I have csv file in sd card in android and many entry in csv file
    suppose i hvae login and data show in autofill text box bt i have a problem supose i m login with praroop and i want show record only praroop in autofil textbox

  5. Pablo on April 2, 2015 at 8:10 am

    Great, thank you George!

  6. Dheeraj Bansal on May 16, 2015 at 5:48 pm

    I want to make a list of string in search view. Like in flipcart app we search in search view and get list.Can you help me.
    Thank you

  7. Neha on June 1, 2015 at 11:20 am

    hello,
    My @xml/searchable in meta-data xml is throwing error of no resource found. how can i solve this?

  8. Akki on June 15, 2015 at 4:02 pm

    Hello Sir,
    I follow your tutorial. It works fine but it will not gives suggestion in quick search box.

  9. Vaibhav on August 5, 2015 at 7:01 pm

    Hello. I am using Android Studio. I can’t find the res/xml file in my project. Can you please help me out.
    For that matter i cant even find the res folder.
    Thanks for the help.

  10. Paresh on April 29, 2016 at 6:17 pm

    Thanks George. It would be great if you can create the same app with an ORM. That might cut down on a lot of boilerplate code. I have recently come across JDXA and seems like a nice ORM.

  11. Tarun on May 1, 2016 at 1:00 pm

    Hi, Can we add data in content provider dynamically?

  12. michelle on May 12, 2016 at 1:25 pm

    Thanks George. It would be great if you can create the same app with an ORM. That might cut down on a lot of boilerplate code. I have recently come across JDXA and seems like a nice ORM.

Leave a Reply to Pablo Cancel reply

Your email address will not be published. Required fields are marked *

Be friend at g+

Subscribe for Lastest Updates

FBFPowered by ®Google Feedburner