Monday, April 9, 2012

Django Tastypie with android client part 2

This post picks up from part 1 where we had defined the api using tastypie.The purpose of this second part is to create an android client to consume the api. here are the files that make up the android application. RecipesActivity.java
package com.thecodachi.recipe;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;

import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.HTTP;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.j256.ormlite.android.apptools.OpenHelperManager;
import com.j256.ormlite.dao.Dao;

import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class RecipesActivity extends Activity {
 
 public static String api_url = "http://10.0.2.2:8000/api/";
 
 private List recipesList = null;
 private ProgressDialog progDialog;
 private ProgressThread progThread;
 private ListView recipes;

 private DatabaseHelper databaseHelper = null;

 private ArrayAdapter adapter;
 
 @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        recipes = (ListView)findViewById(R.id.lvRecipes);
        
        showDialog(0);
  
        try{
   //get our Dao and query for all customers in db
   Dao recipes = getHelper().getRecipeDataDao();
   recipesList = recipes.queryForAll();
  
  }catch(SQLException e){
   Log.e("LOG", "error getting products");
  }
  
  
        adapter = new ArrayAdapter(this,
                      R.layout.recipe_row,
                      R.id.recipeLabel,
                      recipesList);
        recipes.setAdapter(adapter);
    }

 @Override
 protected Dialog onCreateDialog(int id) {
  switch(id){
  case 0:
   progDialog = new ProgressDialog(this);
   progDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
   progDialog.setMessage("Loading data...");
   progThread = new ProgressThread();
            progThread.start();
   return progDialog;
  default:
   return null;
  }
 }
 
 private class ProgressThread extends Thread{
  
  private static final String LOG_TAG = "PROGRESS_THREAD";

  @Override
  public void run() {
   HttpClient httpClient = new DefaultHttpClient();
   HttpGet httpGet = new HttpGet(api_url+"recipes/?format=json&limit=0");
   HttpResponse response = null;
   try{
    
    response = httpClient.execute(httpGet);
    
   }catch(Exception e){
    System.out.println(e.toString());
   }
   if(response!=null){
    HttpEntity entity = response.getEntity();
    String apiResponse = null;
    try {
     apiResponse = _getResponseBody(entity);
    } catch (ParseException e) {
     e.printStackTrace();
    } catch (IOException e) {
     e.printStackTrace();
    }
    if(apiResponse!=null){
     try{
      Dao recipesDao = getHelper().getRecipeDataDao();
      
      JsonElement json = new JsonParser().parse(apiResponse);
      JsonObject rootObject = json.getAsJsonObject();
      JsonArray array= rootObject.getAsJsonArray("objects");
      Iterator iterator = array.iterator();
      
      while(iterator.hasNext()){
       JsonElement json2 = (JsonElement)iterator.next();
       Gson gson = new Gson();
       Recipe recipe = gson.fromJson(json2, Recipe.class);
       recipesDao.createIfNotExists(recipe);
       Log.d(LOG_TAG, "--------------------------------created recipe");
      }
      
     }catch(Exception e){
      Log.e(LOG_TAG, "error creating recipe from json");
     }


    }
    
   }  
   dismissDialog(0);
   adapter.notifyDataSetChanged();
  }
 }
 public static String _getResponseBody(final HttpEntity entity) throws IOException, ParseException {

  if (entity == null) { throw new IllegalArgumentException("HTTP entity may not be null"); }

  InputStream instream = entity.getContent();

  if (instream == null) { return ""; }

  if (entity.getContentLength() > Integer.MAX_VALUE) { throw new IllegalArgumentException(

  "HTTP entity too large to be buffered in memory"); }

  String charset = getContentCharSet(entity);

  if (charset == null) {

  charset = HTTP.DEFAULT_CONTENT_CHARSET;

  }

  Reader reader = new InputStreamReader(instream, charset);

  StringBuilder buffer = new StringBuilder();

  try {

  char[] tmp = new char[1024];

  int l;

  while ((l = reader.read(tmp)) != -1) {

  buffer.append(tmp, 0, l);

  }

  } finally {

  reader.close();

  }

  return buffer.toString();

  }
 public static String getContentCharSet(final HttpEntity entity) throws ParseException {

  if (entity == null) { throw new IllegalArgumentException("HTTP entity may not be null"); }

  String charset = null;

  if (entity.getContentType() != null) {

  HeaderElement values[] = entity.getContentType().getElements();

  if (values.length > 0) {

  NameValuePair param = values[0].getParameterByName("charset");

  if (param != null) {

  charset = param.getValue();

  }

  }

  }

  return charset;

  }
 private DatabaseHelper getHelper(){
  if(databaseHelper  == null){
   databaseHelper = OpenHelperManager.getHelper(this, DatabaseHelper.class);
  }
  return databaseHelper;
 }
}
Recipe.java class which defines a recipe object which can be persisted in the sqlite database using ormlite.
package com.thecodachi.recipe;

import com.j256.ormlite.field.DatabaseField;

public class Recipe {
 
 @DatabaseField(id=true)
 private int id;
 
 @DatabaseField
 private String name;
 
 @DatabaseField
 private String content;
 
 Recipe(){
  //used by ormlite
 }
 
 
 public int getId() {
  return id;
 }


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


 public String getName() {
  return name;
 }

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

 public String getContent() {
  return content;
 }

 public void setContent(String content) {
  this.content = content;
 }


 @Override
 public String toString() {
  // TODO Auto-generated method stub
  return name;
 }
 
}
DatabaseHelper.java responsible for persisting and retreiving objects from sqlite database using ormlite.
package com.thecodachi.recipe;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;

public class DatabaseHelper extends OrmLiteSqliteOpenHelper {

 //name of the database file for the application
 private static final String DATABASE_NAME = "Recipe.db";
 private static final int DATABASE_VERSION = 1;
 /*
  * the DAO objects we use to access the recipes* 
  */
 
 private Dao recipesDao = null;
 
 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, Recipe.class);
 
   
  }catch(java.sql.SQLException e){
   Log.e(DatabaseHelper.class.getName(),"can't create db");
   throw new RuntimeException();
  }
 }

 //called when your application is upgraded
 
 @Override
 public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion,
   int newVersion) {
  try{
   Log.i(DatabaseHelper.class.getName(), "onUpgrade");
   TableUtils.dropTable(connectionSource, Recipe.class, true);
   
   //after dropping old databases, we create the new ones
   onCreate(db, connectionSource);
   
  }catch(java.sql.SQLException e){
   Log.e(DatabaseHelper.class.getName(), "can't drop databases");
   throw new RuntimeException(e);
  }
 }
 

 /* methods that returns the Database access object(Dao) for our product class
  * will create it or just give the cached value
  */
 
 public Dao getRecipeDataDao() throws java.sql.SQLException{
  if(recipesDao == null){
   recipesDao = getDao(Recipe.class);
  }
  return recipesDao;
 }


 /*
  * close all database connections and clear any cached DAOs
  */
 @Override
 public void close(){
  super.close();
  recipesDao = null;
 }
}
You can take a look at the rest of the resources belonging to this app from the repo which i will share. The implementation of the REST architecture i took in this example is not the best and it is highly recommended not to use this approach when building an android application.The reason for this is that the app does network requests within an activity.This should be done within a service For a more detailed and recommended architecture for creating RESTful android application look here. Otherwise this is just a demo.

No comments:

Post a Comment