Monday, September 24, 2012

Automating build process of an android project using ant

If you have developed your Android project using eclipse you might have noticed that there isn't any ant build.xml file in the project directory. That's because the android plugin itself handles all the necessary tasks. 
In case you want to automate your build process by using a custom build file what should you do? First lets discuss a scenario. Consider you have developed an application and you want to release it for testing. Now you want to create a .CMD file and provide some switches for the testers to do pretty much everything by themselves. For example, updating the code from a version control, building, cleaning and installing on the phone or emulator. By doing this you pretty much have automated everything.

Lets have a look at the 1 possible path which updates the code to the latest version and installs it on the phone attached to the PC via USB connector. We name our command file dist.cmd and it has a switch "-install".
   
    dist.cmd -install

    The steps taken automatically are:

    1. uninstall the app from the phone
    2. updating the code to the latest version
    3. cleaning the bin and gen folders
    4. compiling the code
    5. install fresh copy on the phone
 
With the help of this command you can have the latest code installed on a phone for testing.
    
Now the steps

You can execute the following command in order to create a new android project from command line.
 * Before executing the commands first navigate to the tools directory of your SDK.   
 * Add the platform-tools as well as the tools directories to your PATH (environment variable).
    
android create project --target 1 --name vahidApp --path ./vahidAppProject --activity myActivity --package org.vahid.android

target: id of the android platform library you are building against
name: name of the project
path: location of the project
activity: name of the default activity
package: your package namespace
   
After successful execution of the command one directory will be created (your project directory) in the current folder. Go to the newly created project folder and copy the following files to your own project.

    ant.properties : define key.store, key.alias
    build.xml : add few more ant targets (read later below)
    local.properties : defines address of the SDK
   
We are going to create 2 more files.

    dist.properties: defines address of the ant (android needs Ant version 1.8 or later. If you, for any  reason need to have older versions of Ant on you computer and can not change the ANT_HOME and PATH then, you can have your older versions and add the address of the new one in this property file)
    distribution.cmd: define all the commands and the available options. The commands will find the address of the ant from dist.properties.
   
If you open build.xml you will see that this file imports the android-sdk actual build file. How does it know where is the location of the sdk? well, from local.properties.

Now, you can override or add additional targets after the import tag. For example, we want to clean, update the code to the latest version and create a fresh .apk file and install it on the phone. This can be done by the following ant target.

First uninstall is called which removes the app from the phone. Remember you have to define the package address of your app. Its defined in your manifest.xml. Then cleans the bin and gen directories and calls our custom target "updateProject" which update the code to the latest version from version control and finally, calls "release"from  imported android build file. These sequence of ant calls will create project's .apk file and finally installs it on the phone.

Project's ant build file (added custom targets)

    <target name="uninstall">
        <exec executable="adb">
            <arg value="-d"/>
            <arg value="uninstall"/>
            <arg value="com.sg.distribution"/>
        </exec>
    </target>

    <target name="install" depends="uninstall, create">
         <exec executable="adb">
            <arg value="-d"/>
            <arg value="install"/>
            <arg value="./bin/${ant.project.name}-release.apk"/>
        </exec>
    </target>

    <target name="create" depends="clean">
        <antcall target="updateProject"/>
        <antcall target="release"/>
    </target>

 <target name="updateProject">      
        <echo message="Updating project...."/>
        <exec executable="svn.exe" failonerror="true">
          <arg line="update"/>
        </exec>      
    </target>

By adding these 2 ant targets we can make sure our .apk file is build upon the latest code changes. In second updateProject target we use CollabNet's svn.exe (add it to the path environment variable) and passing a parameter to update the code. Of course, our example uses Subversion as the version control.

dist.cmd command file

@echo off
REM --Begin reading path of ant from a property file
FOR /F "eol=; tokens=2,2 delims==" %%i IN ('findstr "antPath" dist.properties') DO (
set antPath=%%i)
REM --END
SET ANT_HOME=%antPath%
:start
if "%1"=="-install" goto install

:install
call %antPath%\bin\ant install
goto done

:done
shift
if ""%1""=="""" goto endOfFile
goto start
:endOfFile

This file read the ant path from the dist.properties file and set ANT_HOME for just the current session to the path read and execute each ant based on the read path. These code is part of the actual file.

Wednesday, September 12, 2012

SOAP for Android

As you know Android does not support SOAP. The reason for that might be, SOAP is complicated on mobile clients. Generating code from WSDL is cumbersome and finally communicating in SOAP means more network traffic which is something to consider in hand-held devices. As a result using REST with JSON is a sensible approach. Because, its flexible in data type returns. JSON is lightweight and simple to parse. JSON is the preferred method of data interchange on hand-held devices.

Still if you have to use SOAP, here are couple of examples using ksoap2 . Ksoap2 is an API for SOAP on Android.

Example 1

This could be an example of primitive return type in ksoap2.

Lets assume we have to authenticate to a system before calling any business web methods. After authentication the system will give us valid JSESSIONID.  The target system provides a web method from which we can login then,  it will return us valid JSESSIONID. This id will be send along with any further method calls to identify us as a valid user.

You can see how to pass primitive data to a web method and parse primitive result from that.

import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.PropertyInfo;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapPrimitive;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;
import android.util.Log;

public class LoginUtil {
    //Name of the method we want to call
    public static String METHOD_NAME = "login";
    public static String SOAP_ACTION = NAMESPACE + METHOD_NAME;
    //The name space (you can get this from WSDL)
    public static String NAMESPACE = "http://webservice.web.security.com/";

    public static String lognToThesystem(String username,String password, String host, String port) {
       SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);       
        //Setting the property to be passed to the web service method
        PropertyInfo propInfoUsername = new PropertyInfo();
        propInfoUsername.name = "arg0";
        propInfoUsername.type = PropertyInfo.STRING_CLASS;

        request.addProperty(propInfoUsername, username);
       //Setting the property to be passed to the web service method      
        PropertyInfo propInfoPassword = new PropertyInfo();
        propInfoPassword.name = "arg1";
        propInfoPassword.type = PropertyInfo.STRING_CLASS;

        request.addProperty(propInfoPassword, password);

        String url = "http://" + host + ":" + port + "/app/ws/login?wsdl";
      
        SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
        envelope.setOutputSoapObject(request);

        HttpTransportSE androidHttpTransport = new HttpTransportSE(url);
        SoapPrimitive resultsRequestSOAP = null;
        try {
            androidHttpTransport.call(SOAP_ACTION, envelope);
            //Since we know the result is of type primitive then, cast it to SoapPrimitive
            resultsRequestSOAP = (SoapPrimitive) envelope.getResponse();
        } catch (Exception e) {
            Log.e("Error", e.getMessage());
        }
        return resultsRequestSOAP == null ? "" : resultsRequestSOAP.toString();
    }
}

Example 2

Now we are going to look at a more complex example. In the previous example we obtained a JSESSIONID. We want to call a method (getAllUsers) but we have to provide the JSESSIONID with this request otherwise the system wont let us call the aforementioned method. The return type of this request call is not primitive and consists of complex objects. This method returns all the users with the condition that  if any of their properties have changed between fromDate and toDate (this is just the business rule). The constructor of this class receives all the necessary data needed for this method call.

You can see how to pass Date to a web service method and parse a list of objects return from that. Also you can see how to add a header property to the request.

Return type object:

public class WebServiceEntity {
    private long id;
    private String name;
    private int type;
   
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getType() {
        return type;
    }
    public void setType(int type) {
        this.type = type;
    }
}

Caller class:

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Vector;

import org.ksoap2.HeaderProperty;
import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.MarshalDate;
import org.ksoap2.serialization.PropertyInfo;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;

import android.util.Log;

import com.vahid.dto.WebServiceEntity;

public class GeneralRecords {
    private final String NAMESPACE = "http://webservice.web.vahid.com/";
    private final String METHOD_NAME = "getAllUsers";
    private final String SOAP_ACTION = NAMESPACE + METHOD_NAME;
    private String jsessionId;
    private String url;
    private Date fromDate;
    private Date toDate;
    private Vector<SoapObject> result = null;
  
    public GeneralRecords(String jsessionId,String host, String port, Date fromDate, Date toDate) {
        this.jsessionId = jsessionId;
        this.fromDate = fromDate;
        this.toDate = toDate;
        this.url = "http://" + host + ":" + port + "/service/vahid/general?wsdl";
    }

public List<WebServiceEntity> getAllEntiites() {
        //Create a soap object
        SoapObject soapObject = new SoapObject(NAMESPACE, MethodName);
       //Defining the fromDate property
        PropertyInfo propFromCal = new PropertyInfo();
        propFromCal.name = "arg0";
        propFromCal.type = MarshalDate.DATE_CLASS;
      
        soapObject.addProperty(propFromCal, fromDate);
        //Defining the toDate property
        PropertyInfo propToCal = new PropertyInfo();
        propToCal.name = "arg1";
        propToCal.type = MarshalDate.DATE_CLASS;

        soapObject.addProperty(propToCal, toDate);
      
         SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
         envelope.setOutputSoapObject(soapObject);
//Use MaeshalDate so that the system knows how to serialize and deserialize objects you are trying to pass through the web service
         MarshalDate md = new MarshalDate();
         md.register(envelope);
     
         //Make the call to target web service
         HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);
         Object response = null;
         try {
             //Create a list and assign all the necessary header properties. In our case we only need JSESSIONID
             List headers = new ArrayList();
             HeaderProperty jsessionIdProperty = new HeaderProperty("Cookie", "JSESSIONID="     +            jsessionId);
             headers.add(jsessionIdProperty);
             androidHttpTransport.call(SOAP_ACTION, envelope, headers);
             response = envelope.getResponse();
         } catch (Exception e) {
             Log.e("Error", e.getMessage());
         }

         if (response instanceof SoapObject) {
             result = new Vector();
             result.add((SoapObject) response);
         } else if (response instanceof Vector) {
             result = (Vector<SoapObject>) response;
         }
         //Retrieve object from soap
         List<WebServiceEntity> webServiceEntity = new ArrayList<WebServiceEntity>();
         for (SoapObject soap : result) {
             WebServiceEntity wse = new WebServiceEntity();
             wse.setId(Long.parseLong(soap.getProperty(0).toString()));
             wse.setName(soap.getProperty(1).toString());
             wse.setType(Integer.parseInt(soap.getProperty(2).toString()));
             webServiceEntity.add(wse);
         }
         return webServiceEntity;
    }
}