Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

Monday, November 25, 2013

Android Continuous Integration Guides: Ebook I



 This book helps you explored Continuous Integration in practice providing valuable information to start applying it soon to your Android projects.

Employs Ant to automate the building process, git to create a simple version control system repository to store our source code and manage the changes, and finally installs and configures Jenkins as the Continuous Integration of choice. In this journey we detail the creation of jobs for automating the building process of TemperatureConverter, its dependency library LocalViewServer and its tests and we emphasized on the relationship between the projects.

Finally, we analyze a way of getting XML results from Android tests and implement this to obtain an attractive interface to monitor the running of tests, their results, and the existing trends and using and showing EMMA code coverage reports.

This will save you precious time and experimentation leading you through a step-by-step guide.

Visit Google Play Books to find more.

Friday, August 17, 2012

monkeyrunner: detecting the OS

Sometimes you monkeyrunner script should know the Operating System it is running on. In the big majority of the cases you don't have to worry if you are running on Linux or Mac OS X, but things are not so smooth on Windows.

I'll give you an example. I've received some bug reports about AndroidViewClient not being able to find adb. AndroidViewClient tries to be clever and not to invoke adb if it's going to fail because it's not found or it's not executable. To determine this, it is using:


        if not os.access(adb, os.X_OK):
            raise Exception('adb="%s" is not executable' % adb)

the trick here is that for Windows platforms adb should include the trailing .exe.
Then the problem is to determine the OS the script is running on.

There are several ways of determining the OS in python and jython. Let's see what are the results using monkeyrunner

Command Linux Mac OS X Windows
os.getenv('os') None None Windows_NT
os.name java java java
platform.system() Java Java Java
sys.platform java1.6.0_26 java1.6.0_33 java1.7.0_05
java.lang.System.getProperty('os.name') Linux Mac OS X Windows XP

From the previous table we can determine that the best way of obtaining the OS from a monkeyrunner script is

     java.lang.System.getProperty('os.name')

I hope this helps you

Thursday, November 10, 2011

Android: Using monkey from Java


The latest version of the Android SDK and tools include chimpchat, a library that facilitates the use of monkey from Java. This is equivalent to monkeyrunner, which is the bridge between monkey and the Python scripting language.
While Python is an incredibly powerful and expressive scripting language and will permit you creating tests with just a few statements, there are some occasions when you don't want to introduce a new language to the project leaving your Java confort zone or you prefer to leverage the use of previously created libraries instead of writing new ones.
In such cases, you can now have the same access to monkey running on the device with the help of chimpchat, as we are going to demonstrate.


Creating a Java project
Our first step will be to create a new Java project and we will add the required libraries to the Java Build Path as External Jars.
We are naming the project JavaMonkey, for obvious reasons.




We are adding these libraries from Android SDK, which are used directly or indirectly by our project, to the Java Build Path:

  • chimpchat.jar
  • ddmlib.jar
  • guavalib.jar
  • sdklib.jar


JavaMonkey.java
Our intention is to create a simple class, serving the purpose of a simple example to get as started. We will be simply:

  1. Creating a JavaMonkey object
  2. initializing it, this implies creating the connection with any emulator or device found or throwing an exception is not connection was made before the timeout expires
  3. listing all the properties in the device or emulator
  4. shutting down the connection

Following, is the JavaMonkey class: 



/**
 * Copyright (C) 2011  Diego Torres Milano
 */
package com.example.javamonkey;

import java.util.TreeMap;

import com.android.chimpchat.ChimpChat;
import com.android.chimpchat.core.IChimpDevice;

/**
 * @author diego
 *
 */
public class JavaMonkey {

        private static final String ADB = "/Users/diego/opt/android-sdk/platform-tools/adb";
        private static final long TIMEOUT = 5000;
        private ChimpChat mChimpchat;
        private IChimpDevice mDevice;

        /**
         * Constructor
         */
        public JavaMonkey() {
                super();
        TreeMap<String, String> options = new TreeMap<String, String>();
        options.put("backend", "adb");
        options.put("adbLocation", ADB);
        mChimpchat = ChimpChat.getInstance(options);
        }

        /**
         * Initializes the JavaMonkey.
         */
        private void init() {
                mDevice = mChimpchat.waitForConnection(TIMEOUT, ".*");
                if ( mDevice == null ) {
                        throw new RuntimeException("Couldn't connect.");
                }
                mDevice.wake();
        }

        /**
         * List all properties.
         */
        private void listProperties() {
                if ( mDevice == null ) {
                        throw new IllegalStateException("init() must be called first.");
                }
                for (String prop: mDevice.getPropertyList()) {
                        System.out.println(prop + ": " + mDevice.getProperty(prop));
                }
        }

        /**
         * Terminates this JavaMonkey.
         */
        private void shutdown() {
                mChimpchat.shutdown();
                mDevice = null;
        }

        /**
         * @param args
         */
        public static void main(String[] args) {
                final JavaMonkey javaMonkey = new JavaMonkey();
                javaMonkey.init();
                javaMonkey.listProperties();
                javaMonkey.shutdown();
        }

}


Configuration
One of the important things you have to adapt to your environment is the location of the adb command. Otherwise if you don't set it you will receive:

E/adb: Failed to get the adb version: Cannot run program "adb": error=2, No such file or directory


Hope this helps you get started with chimpchat. As always, comments and questions are always welcome.

Wednesday, February 24, 2010

Android: Generate javadoc for your project

So you want to follow the best practices and generate the javadoc for your android project from Eclipse and as soon as you start you hit the first wall, messages like "package android.app does not exist" completely fill your output console.

What's wrong ?
And most important, how can you generate complete javadoc with links to android documentation ?




Find out how reading the complete article in Google Docs: http://docs.google.com/View?id=ddwc44gs_240hkc84xfd

Thursday, January 28, 2010

Android Testing: testing XML or JSON parsers

There are many occasions where your Android application relies on external XML or JSON messages or documents obtained from web services. These documents are used for data interchange between the local application and the server. There are many use cases where XML or JSON documents are generated by the local application to be sent to the server. Ideally, methods invoked by these activities have to be tested in isolation to have real unit tests and to achieve this we need to include some mock files somewhere in our APK to run the tests.
But the question is where can we include these files ?
Let's find it out.


Read the complete article in Google Docs:
http://docs.google.com/View?id=ddwc44gs_239fhchvfds

Tuesday, August 18, 2009

AutoAndroid: Pizza order sample









This document can be read in Google Docs (http://docs.google.com/View?id=ddwc44gs_203g7xcxfr9), cut and paste link if you have problems accessing it.






We analyzed the basic concepts behind autandroid in a previous post, now let's introduce a slightly more interesting example.


pizza order example












This sample includes autoandroid.jar and can be downloaded from http://codtech.com/downloads/android/index.html#source as an Eclipse project (AutoAndroidSamples.zip).





This introductory example provides some automatic behavior of UI components.

Our objective is to obtain, writing as less code as possible, the following functionality.



A standard Activity displays some Buttons to launch different samples. Right now we have only two Buttons corresponding to our samples.













Clicking the Pizza order samples Button launches the Dialog.











Some automatic behavior has been defined in the XML file and thus it's automatically available in the Dialog.

This behavior includes:



  • If quantity is 0, then OK is disabled


  • Quantity value is automatically updated depending on the seek bar position



  • All values are exported so they can be retrieved from the parent Activity


  • Cancel and OK buttons have the corresponding default behavior




Once some values are entered, pressing the OK Button the Dialog is dismissed and the values are passed back to the invoking Activity.











pizza_order_sample.xml


This is the layout of our sample dialog.

We can do it as we normally do using ADT's Layout Editor.













However, to provide the extra behavior we have to add some properties in the XML view of the editor.










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

<LinearLayout

    xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:auto="http://schemas.android.com/apk/res/com.codtech.android.samples.autoandroid"

    android:orientation="vertical" android:layout_height="fill_parent"

    android:layout_width="300dip"  

    android:id="@+id/LinearLayoutPizzaOrder">



    <com.codtech.android.auto.widget.AutoRadioGroup

        android:id="@+id/RadioGroupPizza"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        auto:export="true"

        auto:name="pizza">

        <RadioButton android:id="@+id/RadioButton01"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_marginLeft="6dip" android:text="Margherita"

            android:layout_marginTop="-6dip" android:checked="true">

        </RadioButton>

        <RadioButton android:id="@+id/RadioButton02"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_marginLeft="6dip" android:text="Prosciutto">

        </RadioButton>

        <RadioButton android:id="@+id/RadioButton03"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_marginLeft="6dip"

            android:text="Quattro Stagioni">

        </RadioButton>

    </com.codtech.android.auto.widget.AutoRadioGroup>

    

    <TextView android:id="@+id/TextView02"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content" android:text="Additionals"

        android:textStyle="bold" android:layout_marginLeft="3dip"

        android:layout_marginTop="3dip"></TextView>

        

    <com.codtech.android.auto.view.AutoCheckBox 

        android:id="@+id/CheckBoxExtraMozzarella"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_marginLeft="6dip"

        android:text="Extra mozzarella"

        auto:export="true">

    </com.codtech.android.auto.view.AutoCheckBox>

        

    <com.codtech.android.auto.view.AutoCheckBox

        android:id="@+id/CheckBoxPepperoni"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_marginLeft="6dip"

        android:text="Pepperoni"

        auto:export="true">

    </com.codtech.android.auto.view.AutoCheckBox>

        

    <TextView android:id="@+id/TextView03"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content" android:text="Quantity"

        android:textStyle="bold" android:layout_marginLeft="3dip"

        android:layout_marginTop="3dip"></TextView>



    <LinearLayout android:id="@+id/LinearLayout02"

        android:layout_height="wrap_content"

        android:layout_width="fill_parent"

        android:orientation="horizontal">

        <com.codtech.android.auto.view.AutoSeekBar 

            android:layout_height="wrap_content"

            android:layout_margin="6dip"

            android:layout_width="wrap_content"

            android:layout_weight="1" android:id="@+id/SeekBarQuantity"

            auto:update="@+id/TextViewQuantity"

            android:max="10"

            auto:export="true"

            auto:name="quantity"

            auto:sensitize="@+id/ButtonPizzaOrderDialogOk">

        </com.codtech.android.auto.view.AutoSeekBar>

        <TextView android:layout_width="wrap_content"

            android:layout_weight="0" android:layout_margin="6dip"

            android:layout_height="fill_parent"

            android:layout_gravity="center"

            android:gravity="center_vertical|right"

            android:textStyle="bold" android:id="@id/TextViewQuantity"

            android:maxLength="3" android:text="0"

            android:background="#555555"

            ></TextView>

    </LinearLayout>

    

    <RelativeLayout android:id="@+id/LinearLayout01"

        android:layout_height="wrap_content"

        android:layout_width="fill_parent"

        android:layout_margin="3dip"

        android:background="@color/dialog_action_background">

        <Button android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_alignParentLeft="true"

            android:text="Cancel" android:width="100dip"

            android:id="@+id/ButtonPizzaOrderDialogCancel"

            auto:dialog_action="cancel"

            android:layout_margin="3dip"></Button>

        <Button android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_alignParentRight="true"

            android:text="OK" android:width="100dip"

            android:id="@id/ButtonPizzaOrderDialogOk"

            auto:dialog_action="positive"

            android:clickable="false"

            android:enabled="false"

            android:layout_margin="3dip"

            ></Button>

    </RelativeLayout>



</LinearLayout>







Let's explain the changes:




  1. Define the namespace auto. The name that appears after http://schemas.android.com/apk/res is usually the package name of your application and will be used in an attribute definition (attrs.xml).


  2. The root layout must have an ID as it's used by some methods in the library and should be identifiable. The name here is LinearLayoutPizzaOrder but it could be whatever you like.


  3. Then, a com.codtech.android.auto.widget.AutoRadioGroup which is a class that extends RadioGroup and provides some automatic behavior.


  4. Mark the AutoRadioGroup as exported so we can obtain its value later. This is achieved using auto:export="true".



  5. As it is exported we give it a name, text in this case, to obtain the value later. auto:name="pizza" does the trick.



  6. Two AutoCheckBoxes,also exported hold additional options.


  7. An AutoSeekBar, exported under the name "quantity" provides also two other options, automatically update a text field depending on the seek bar value using auto:update="@+id/TextViewQuantity". It also automatically sensitize the OK Button using auto:sensitize="@+id/ButtonPizzaOrderDialogOk".


  8. Finally, mark the buttons using auto:dialog_action="positive" and auto:dialog_action="cancel".



Styleable attributes


The way we are adding these attributes is by defining styleable attributes in a file usually attrs.xml.










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



<resources>

<declare-styleable name="com.codtech.android.samples.autoandroid">

    <attr name="name" format="string" />

    <attr name="init" format="string" />

    <attr name="sensitize" format="reference" />

    <attr name="show" format="reference" />

    <attr name="update" format="reference" />

    <attr name="export" format="boolean" />

    <attr name="dialog_ok" format="boolean" />

    <attr name="dialog_action" format="string" />

</declare-styleable>

</resources>




AutoAndroidSamples.java


This is our sample Activity.










/*

 * Copyright © 2009 COD Technologies Ltd.  www.codtech.com


 *


 * $Id: AutoAndroidSamples.java 131 2009-08-15 00:28:47Z diego $


 *


 *


 */




package com.codtech.android.samples.autoandroid;




import android.app.Activity;


import android.app.Dialog;


import android.content.DialogInterface;


import android.content.DialogInterface.OnDismissListener;


import android.os.Bundle;


import android.view.View;


import android.view.View.OnClickListener;


import android.widget.Button;


import android.widget.Toast;




import com.codtech.android.auto.app.AutoDialog;






public class AutoAndroidSamples extends Activity implements OnClickListener, OnDismissListener {


    private static final int DIALOG_SAMPLE_ID = R.id.Button01;


    private static final int DIALOG_PIZZA_ORDER_SAMPLE_ID = R.id.Button02;


    


    private AutoDialog ad01;


    private AutoDialog ad02;




    /** Called when the activity is first created. */


    @Override


    public void onCreate(Bundle savedInstanceState) {


        super.onCreate(savedInstanceState);


        setContentView(R.layout.samples);


        


        ((Button) findViewById(R.id.Button01)).setOnClickListener(this);


        ((Button) findViewById(R.id.Button02)).setOnClickListener(this);


    }




    /* (non-Javadoc)


     * @see android.view.View.OnClickListener#onClick(android.view.View)


     */


    @Override


    public void onClick(View v) {


        showDialog(v.getId());        


    }




    


    /* (non-Javadoc)


     * @see android.app.Activity#onCreateDialog(int)


     */


    @Override


    protected Dialog onCreateDialog(int id) {


        switch (id) {


        case DIALOG_SAMPLE_ID:


            ad01 = new AutoDialog(this, R.layout.dialog_sample,


                R.string.dialog_sample_title);


            ad01.setOnDismissListener(this);


            return ad01;




        case DIALOG_PIZZA_ORDER_SAMPLE_ID:


            
ad02 = new AutoDialog(this, R.layout.pizza_order_sample,

               
R.string.dialog_pizza_order_sample_title);

            
ad02.setOnDismissListener(this);

            
return ad02;

            


        default:


            break;


        }


        


        return super.onCreateDialog(id);


    }




    /* (non-Javadoc)


     * @see android.app.Activity#onPrepareDialog(int, android.app.Dialog)


     */


    @Override


    protected void onPrepareDialog(int id, Dialog dialog) {


        // TODO Auto-generated method stub


        super.onPrepareDialog(id, dialog);


    }




    /* (non-Javadoc)


     * @see android.content.DialogInterface.OnDismissListener#
onDismiss(android.content.DialogInterface)


     */


    @Override


    public void onDismiss(DialogInterface dialog) {


        if ( dialog instanceof AutoDialog ) {


            AutoDialog ad = (AutoDialog)dialog;


            


            if ( ad.isCanceled() ) {


                return;


            }


            


            
Bundle bundle = ad.getAutoBundle();

            


            if ( ad.equals(ad01) ) {


                Toast.makeText(this, "Entered value: " + bundle.getString("text"),


                    Toast.LENGTH_SHORT).show();


            }


            else if ( ad.equals(ad02)) {


                
Toast.makeText(this, composePizzaOrderMessage(bundle),

                   
Toast.LENGTH_SHORT).show();

            }


        }


    }




    /**


     * @param bundle


     * @return


     */


    private String composePizzaOrderMessage(Bundle bundle) {


        final int quantity = bundle.getInt("quantity");


        if ( quantity > 0 ) {


            final StringBuilder msg = new StringBuilder(String.format("Make %d %s pizza%s",


               quantity, bundle.get("pizza"), (quantity > 1) ? "s" : ""));


            final boolean extraMozzarella = bundle.getBoolean("extra mozzarella");


            final boolean pepperoni = bundle.getBoolean("pepperoni");


            if ( extraMozzarella ) {


                msg.append(" with extra mozzarella");


                if ( pepperoni ) {


                    msg.append(" and pepperoni");


                }


            }


            else if ( pepperoni ) {


                msg.append(" with pepperoni");


            }


            


            return msg.toString();


        }




        return "No pizzas ordered";


    }




}



Creating the AutoDialog specifying the layout and the title in the constructor is all we need to have our Dialog working.

When the Dialog is dismissed we can obtain all of the exported values in a Bundle using the getAutoBundle() method.




Conclusion


This is a more practical example of AutoAndroid. With almost no code we have managed common behavior that frequently appears in Android applications. Some other samples will follow demonstrating other features. Stay tuned.



If you have comments, ideas, critiques or whatever just drop me a line or leave a comment in the blog.




Copyright © 2009 Diego Torres Milano. All rights reserved.


















































Wednesday, August 12, 2009

Android: Is AutoAndroid possible ?









This document can be read in Google Docs (http://docs.google.com/View?id=ddwc44gs_195pgbkpfwn), cut and paste link if you have problems accessing it.






Some time ago, while working on some Linux projects requiring GUIs for some commands I've started autoglade, an Open Source project aiming to fill some gap between scripts and command line utilities and GUIs.

As a proof of concept, I'm wondering if a similar approach would be possible in Android, so let's find out what can be done.

Your comments and suggestions are greatly appreciated.


autoglade


autoglade's main objective is to automate as much as possible the design and implementation of Gnome/GTK based, cross platform applications whose GUI is designed with Glade.

A clear idea of what autoglade is about can be obtained taking a glimpse at autoglade tutorial: first steps (problems with SF's wiki ? access this Google cached version) .

More in-depth treatment of autoglade features and a very interesting cross-platform example, autoeditor, can be found at autoglade features.


autoandroid


Would it be at all possible to take a similar approach to streamline android UI design and implementation, factoring the repetitive tasks you have to write again and again for many applications ?

autoandroid library is distributed as a jar file that you can add to your android project java build path.




introductory example


This introductory example provides some automatic behavior of UI components.

Our objective is to obtain, writing as less code as possible, the following functionality.











This sample including autoandroid-0.2.jar can be downloaded from http://codtech.com/downloads/android/index.html#source as an Eclipse project (AutoAndroidSamples.zip).







A standard Activity displays our custom Dialog once the Button is clicked








The custom Dialog displayed has an EditText and a Button. The EditText starts empty and the Button disabled.








Once some text is entered the Button is activated. If the text is deleted the Button returns to its disabled state.








When the Button is pressed the text entered in the EditText is displayed by the main Activity using a Toast, that is we are easily getting the exported values from the Dialog.












dialog_sample.xml


This is the layout of our sample dialog.

We can do it as we normally do using ADT's Layout Editor.








However, to provide the extra behavior we have to add some properties in the XML view of the editor.










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



<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"


    
xmlns:auto="http://schemas.android.com/apk/res/com.codtech.android.samples.autoandroid"

    android:layout_height="wrap_content" android:layout_width="fill_parent"


    android:orientation="vertical"


    
android:id="@+id/Root">

    <
com.codtech.android.auto.view.AutoEditText android:id="@+id/EditText01"

        android:layout_height="wrap_content"


        android:layout_width="fill_parent" android:layout_margin="6dip"


        
auto:export="true"

        
auto:name="text"

        
auto:sensitize="@+id/Button01"

        android:hint="@string/edittext_hint" />


    <Button android:id="@id/Button01"


        android:layout_width="wrap_content"


        android:layout_height="wrap_content" android:text="OK" android:width="150dip"


        android:layout_gravity="right"


        android:enabled="false"


        android:clickable="false"


        
auto:dialog_action="positive" />

</LinearLayout>




Let's explain the changes:




  1. Define the namespace auto. The name that appears after http://schemas.android.com/apk/res is usually the package name of your application and will be used in an attribute definition (attrs.xml). More on this later.


  2. The root layout must have an ID as it's used by some methods in the library and should be identifiable. The name here is Root but it could be whatever you like, though Root seems to be a good option.


  3. Instead of EditText we use com.codtech.android.auto.view.AutoEditText which is a class that extends EditText and provides some automatic behavior. As you can see, this usually doesn't affect how the layout is displayed in the editor.


  4. Mark the AutoEditText as exported so we can obtain its value later. This is achieved using auto:export="true".



  5. As it is exported we give it a name, text in this case, to obtain the value later. auto:name="text" does the trick.



  6. We automatically sensitize Button01 depending on the content of this EditText. Note that here we are assigning the ID to the Button using @+id because it has not yet been defined and we want to create the ID.


  7. Mark Button01 as the positive action of the Dialog using auto:dialog_action="positive"




Styleable attributes


The way we are adding these attributes is by defining styleable attributes in a file usually attrs.xml.










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



<resources>


<declare-styleable name="com.codtech.android.samples.autoandroid">


    <attr name="name" format="string" />


    <attr name="init" format="string" />


    <attr name="sensitize" format="reference" />


    <attr name="show" format="reference" />


    <attr name="update" format="reference" />


    <attr name="export" format="boolean" />


    <attr name="dialog_ok" format="boolean" />


    <attr name="dialog_action" format="string" />


</declare-styleable>


</resources>




AutoAndroidSamples.java


This is our sample Activity.










package com.codtech.android.samples.autoandroid;



import android.app.Activity;


import android.content.DialogInterface;


import android.content.DialogInterface.OnDismissListener;


import android.os.Bundle;


import android.view.View;


import android.view.View.OnClickListener;


import android.widget.Button;


import android.widget.Toast;




import com.codtech.android.auto.app.AutoDialog;




public class AutoAndroidSamples extends Activity implements OnClickListener, OnDismissListener {


    private AutoDialog ad;




    /** Called when the activity is first created. */


    @Override


    public void onCreate(Bundle savedInstanceState) {


        super.onCreate(savedInstanceState);


        setContentView(R.layout.samples);


        


        ((Button) findViewById(R.id.Button01)).setOnClickListener(this);


    }




    @Override


    public void onClick(View v) {


        ad =
new AutoDialog(this, R.layout.dialog_sample,

            R.string.dialog_sample_title);

        ad.setOnDismissListener(this);


        ad.show();


    }




    @Override


    public void onDismiss(DialogInterface arg0) {


        if ( ! ad.isCanceled() ) {


            Bundle bundle =
ad.getAutoBundle();

            Toast.makeText(this, "Entered value: " +


                bundle.getString("text"), Toast.LENGTH_SHORT)


                .show();


        }


    }


}





  1. Set the layout to samples, a simple layout containing the button to display the Dialog when clicked


  2. Set the OnClickListener on this Button


  3. On the onClick handler create the AutoDialog using dialog_sample layout shown previously and a title we defined in strings.xml


  4. Set the OnDismissListener of the AutoDialog


  5. On the onDismiss handler check if the AutoDialog was canceled, a value that is automaticaly set, and if not get the Bundle containing the exported values, extract the String for "text" and display it as a Toast.




Conclusion


We have just scratched the ice.
There are some limitations imposed by the SDK, for example there's no a way to specify a list of resource ids in a styleable attribute, so if we want to update or sensitize a list of Views depending on the state of other View we need to find another way of doing it.

Anyway, this is an extremely simple example of AutoAndroid and its common use. Some other samples will follow demonstrating other features like Pizza Shop which is also an autoglade sample.





If you have comments, ideas, critiques or whatever just drop me a line or leave a comment in the blog.




Copyright © 2009 Diego Torres Milano. All rights reserved.















































Tuesday, July 07, 2009

Android: Testing on the Android platform - Is Toast leaking ?









This document can be read in Google Docs (http://docs.google.com/View?id=ddwc44gs_194s36s83fz), cut and paste link if you have problems accessing it.






A couple of days ago I've found an interesting post by skink on Android Developers group titled **never ever** use Toasts with Activity context. The post speaking about NotifyWithText in ApiDemos, states something like:


"...try to show any Toast, then exit NotifyWithText

demo. run it again - you will see getInstanceCount() increases leaking

Activities. repeat running demo couple of times. counter still

increases."


So the questions are:



  • Is Toast leaking Context objects ?


  • Is it a bug in Toast, in ApiDemos, in the documentation ?


  • Should we use Application Context instead ?




Let's try to find the answers using some Unit Tests as we have been investigating in previous articles in this blog.


ToastActivity


Let's recreate a oversimplified version of the Activity to display just the Toast depending on an extra parameter DISPLAY_TOAST in the Intent starting the Activity.










package com.codtech.android.training.toast;



import android.app.Activity;

import android.app.Application;

import android.content.Context;

import android.content.Intent;

import android.os.Bundle;

import android.util.Log;

import android.widget.Toast;



public class ToastActivity extends Activity {

    private static final String TAG = "ToastActivity";

    public static final String USE_ACTIVITY_CONTEXT =

        "com.codtech.android.training.toast.useActivityContext";

    public static final String DISPLAY_TOAST =

        "com.codtech.android.training.toast.displayToast";

    private Context toastContext;

    private boolean useActivityContext;

    private boolean displayToast;

    

    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);



        final Intent intent = getIntent();

        useActivityContext =

            intent.getBooleanExtra(USE_ACTIVITY_CONTEXT, true);

        displayToast =

            intent.getBooleanExtra(DISPLAY_TOAST, true);

        

        if ( useActivityContext ) {

            toastContext = this;

        }

        else if ( displayToast ) {

            toastContext =

                getApplication().getApplicationContext();

        }

    }

      

    

    /* (non-Javadoc)

     * @see android.app.Activity#onPause()

     */

    @Override

    protected void onResume() {

        super.onResume();

        if ( displayToast ) {

            Toast.makeText(toastContext,

                "Sample Toast: " + getInstanceCount(),

                Toast.LENGTH_SHORT).show();

        }

        else {

            Log.d(TAG, "No toast displayed");

        }

        finish();

    }





    /* (non-Javadoc)

     * @see android.app.Activity#onDestroy()

     */

    @Override

    protected void onDestroy() {

        super.onDestroy();

        Log.d(TAG, "Activity destroyed: " + this);

    }



}




ToastActivity Tests



As usual, let's create our test to.



In this very particular case we want it to run several times specified by ToastActivityTests.N, 50 actually but you can change it if you like, to see if displaying a Toast or not change things in some way.



RepeatedTestSuite



This class implements repeated tests.











package com.codtech.android.training.toast.tests;



import junit.framework.Test;

import junit.framework.TestSuite;



public class ReapeatedTestSuite extends TestSuite {

    

    public static Test repeatedSuite(Test test, int count) {

        TestSuite suite = new TestSuite("Repeated");

        

        // there's no RepeatedTest in android's junit

        for (int i=0; i<count; i++) {

            suite.addTest(test);

        }

    

        return suite;

    }

}




RepeatedActivityContextWithToast


This test will run testSingleActivityContextWithToast starting the activity displaying the Toast several times










/**

 *

 */

package com.codtech.android.training.toast.tests;



import junit.framework.Test;





/**

 * @author diego

 *

 */

public class RepeatedActivityContextWithToast extends ReapeatedTestSuite {

    public static Test suite() {

        return repeatedSuite(

            new ToastActivityTests("testSingleActivityContextWithToast"),

            ToastActivityTests.N);

    }

}





RepeatedActivityContextWithoutToast


This test will run testSingleActivityContextWithoutToast starting the activity, without displaying any Toast, several times










/**

 *

 */

package com.codtech.android.training.toast.tests;



import junit.framework.Test;





/**

 * @author diego

 *

 */

public class RepeatedActivityContextWithoutToast extends ReapeatedTestSuite {

    public static Test suite() {

        return repeatedSuite(

            new ToastActivityTests("testSingleActivityContextWithoutToast"),

            ToastActivityTests.N);


    }

}







ToastActivityTests


We decided to fail the test if the Activity instance count reaches N/4.












/**

 *

 */

package com.codtech.android.training.toast.tests;





import java.lang.reflect.Method;



import android.app.Instrumentation;

import android.content.Intent;

import android.os.Bundle;

import android.test.ActivityInstrumentationTestCase2;

import android.test.FlakyTest;

import android.test.suitebuilder.annotation.MediumTest;

import android.util.Log;



import com.codtech.android.training.toast.ToastActivity;



/**

 * @author diego

 *

 */

public class ToastActivityTests

    extends ActivityInstrumentationTestCase2<ToastActivity> {




    private static final String TAG = "ToastActivityTests";

    public static final int N = 50;



    /**

     * @param name

     */

    public ToastActivityTests(String name) {

        super("com.codtech.android.training.toast",

            ToastActivity.class);


        setName(name);

    }





    /* (non-Javadoc)

     * @see junit.framework.TestCase#setUp()

     */

    protected void setUp() throws Exception {

        super.setUp();

    }



    /* (non-Javadoc)

     * @see android.test.InstrumentationTestCase#tearDown()

     */

    protected void tearDown() throws Exception {

        super.tearDown();

    }





    @MediumTest

    public void testSingleActivityContextWithToast() {

        exersiseActivityLifecycle(intentFactory(true, true),

            "testSingleActivityContextWithToast");


    }

    

    @MediumTest

    public void testSingleApplicationContextWithToast() {

        exersiseActivityLifecycle(intentFactory(false, true),

            "testSingleApplicationContextWithToast");


    }

    

    @MediumTest

    public void testSingleActivityContextWithoutToast() {

        exersiseActivityLifecycle(intentFactory(true, false),

            "testSingleActivityContextWithoutToast");


    }

    

    @MediumTest

    public void testSingleApplicationContextWithoutToast() {

        exersiseActivityLifecycle(intentFactory(false, false),

            "testSingleApplicationContextWithoutToast");


    }

   



    /**

     * @param intent

     */

    private void exersiseActivityLifecycle(final Intent intent, final String name) {

        setActivityIntent(intent);

        final ToastActivity activity = getActivity();

        final Instrumentation instrumentation = getInstrumentation();



        // At this point, onCreate() has been called, but nothing else

        // Complete the startup of the activity

        instrumentation.callActivityOnStart(activity);

        instrumentation.callActivityOnResume(activity);

        // At this point you could test for various configuration aspects, or you could

        // use a Mock Context to confirm that your activity has made certain calls to the system

        // and set itself up properly.

        instrumentation.callActivityOnPause(activity);

        // At this point you could confirm that the activity has paused properly, as if it is

        // no longer the topmost activity on screen.

        instrumentation.callActivityOnStop(activity);



        Runtime.getRuntime().gc();

        Runtime.getRuntime().runFinalization();

        Runtime.getRuntime().gc();

      

        long aic = ToastActivity.getInstanceCount();

        assertTrue("instance count reached " + aic, aic < N/4);



        // At this point we are invoking onDestroy explicitly because we are iterating

        // and tearDown() will not be called

        instrumentation.callActivityOnDestroy(activity);

        

        // run N times, requires FlakyTest

        try {

            Method method = this.getClass().getMethod(name, new Class[] {});

            FlakyTest flakyTest = method.getAnnotation(FlakyTest.class);

            if ( flakyTest != null ) {

                assertTrue(count >= flakyTest.tolerance());

            }

        } catch (SecurityException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        } catch (NoSuchMethodException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

    }

    

    private static Intent intentFactory(boolean useActivityContext,

            boolean displayToast) {


        final Intent intent = new Intent();

        intent.setAction(Intent.ACTION_MAIN);

        intent.setClassName("com.codtect.android.training.toast",

            "com.codtect.android.training.toast.ToastActivity");


        intent.putExtra(ToastActivity.USE_ACTIVITY_CONTEXT, useActivityContext);

        intent.putExtra(ToastActivity.DISPLAY_TOAST, displayToast);

        return intent;

    }

}







Running the tests



Running RepeatedActivityContextWithoutToast


Running the tests we can verify that everything is fine.













diego@bruce:~$ adb shell am instrument -w -e class com.codtech.android.training.toast.tests.RepeatedActivityContextWithoutToast com.codtech.android.training.toast.tests/android.test.InstrumentationTestRunner



com.codtech.android.training.toast.tests.ToastActivityTests:...................

...............................


Test results for InstrumentationTestRunner=.........................................

.........

Time: 42.473



OK (50 tests)






Running RepeatedActivityContextWithToast


Running the test that displays the Toast we find a different result. Activity instance count reaches the maximum allowed and the test fails












diego@bruce:~$ adb shell am instrument -w -e class com.codtech.android.training.toast.tests.RepeatedActivityContextWithToast com.codtech.android.training.toast.tests/android.test.InstrumentationTestRunner



com.codtech.android.training.toast.tests.ToastActivityTests:...........

Failure in testSingleActivityContextWithToast:

junit.framework.AssertionFailedError: instance count reached 12

    at com.codtech.android.training.toast.tests.ToastActivityTests.

       exersiseActivityLifecycle(ToastActivityTests.java:195)


    at com.codtech.android.training.toast.tests.ToastActivityTests.

       testSingleActivityContextWithToast(ToastActivityTests.java:138)




...





Test results for InstrumentationTestRunner=............F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.

F.F.F.F.F.F.F.F.F.F

Time: 56.101



FAILURES!!!

Tests run: 50,  Failures: 39,  Errors: 0







Conclusion


We can see that the only difference between both tests is the Toast being displayed, and the results are completely different.

While the demonstration given in the Android Developer's thread seems correct, this seems correct too !

Comments, suggestions and corrections are gladly welcome.

If you are interested in the source code or APK just drop me a line or leave a comment in the blog.




Copyright © 2009 Diego Torres Milano. All rights reserved.