Monthly Archives: March 2014

Android AsyncTask

I have seen with many apps that the main thread is sometimes (ab)used by doing too many asynchronous tasks in it. This is very easily resolved by making use of the AsyncTask class in android.os.AsyncTask.

A simple example. Let us assume that you want to call a set of WordPress REST URL’s and get a bunch of JSON back to work with. This is actually really simple and non-blocking if you do it properly with AsyncTask.

The key here is that you need to subclass all of your calls with AsyncTask calls.The class will need to override at least one method

doInBackground(Params ...)

and in some cases, you will probably want to override

onPostExecute(Result)

.

I think that the method names are sufficient description for what they do in this case.

With that in mind, let’s create our “Task” class:

package com.myapp.tasks;

import java.util.ArrayList;
import java.util.List;

import com.myapp.PostLister;
import com.myapp.model.PostInfo;

import de.akquinet.android.androlog.Log;
import android.os.AsyncTask;

public class PostListTask extends AsyncTask<String, Integer, List<PostInfo>> {

	private static final String TAG = "PostListTask";

	@Override
	protected List<PostInfo> doInBackground(String... params) {
		List<PostInfo> posts = new ArrayList<PostInfo>();
		for (String urlid : params) {
			PostLister postlist = new PostLister();
			PostInfo post = postlist.getURL(urlid);
			posts.add(post);
		}
		if (isCancelled()) {
			Log.e(TAG, "User cancelled listing " + params);
		}
		Log.i(TAG, "Post list done..");
		return posts;
	}
}

Once that is done, we need to fill in the missing classes

package com.myapp;

import org.json.JSONException;
import org.json.JSONObject;

import com.myapp.model.PostInfo;
import com.myapp.util.JSONParser;

public class PostLister {

	public static final String url="http://paulscott.co.za/blog/wp-json.php/posts/";
	public static final String urlid = "";
	public static final String TAG_CONTENT = "content";
	public static final String TAG_TITLE = "title";
	public static final String TAG_LINK = "link";
	public static final String TAG_ID = "ID";
	public static final String TAG_SLUG = "slug";
	public static final String TAG_DATE = "date";

	JSONParser jParser = new JSONParser();
	
	public PostInfo getURL(String urlid) {
		JSONObject json = jParser.getJSONFromUrlByGet(url+urlid);
		try {
			String str_content = json.getString(TAG_CONTENT);
			String str_title = json.getString(TAG_TITLE);
			String str_link = json.getString(TAG_LINK);
			String str_ID = json.getString(TAG_ID);
			String str_slug = json.getString(TAG_SLUG);
			String str_date = json.getString(TAG_DATE);
			PostInfo post = new PostInfo(str_content, str_title, str_link, str_ID, str_slug, str_date);
			return post;
		} catch (JSONException e) {
			// whatever...
		}
		return null;
		
	}
}

As you can see, we are only using a few fields from the JSON produced by the WP-JSON plugin, but you get the gist right?

Now for a model

package com.myapp.model;

public class PostInfo {
	
	private String content;
	private String title;
	private String link;
	private String id;
	private String slug;
	private String date;
	
	public PostInfo(String content, String title, String link, String id,
			String slug, String date) {
		super();
		this.content = content;
		this.title = title;
		this.link = link;
		this.id = id;
		this.slug = slug;
		this.date = date;
	}

	public String getContent() {
		return content;
	}

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

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getLink() {
		return link;
	}

	public void setLink(String link) {
		this.link = link;
	}

	public String getId() {
		return id;
	}

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

	public String getSlug() {
		return slug;
	}

	public void setSlug(String slug) {
		this.slug = slug;
	}

	public String getDate() {
		return date;
	}

	public void setDate(String date) {
		this.date = date;
	}	
}

Pretty standard stuff.

Once we are ready to fire it off, we simply invoke the AsyncTask with

AsyncTask<String, Integer, List<PostInfo>> posts = new PostListTask().execute("492", "491");

Which you can pretty much do whatever you want with:

try {
        	List<PostInfo> res = posts.get();
        	for(PostInfo post : res) {
        	    String content = post.getContent();
        	    Log.d(TAG, content);
        	}
        } catch (InterruptedException e) {
        	e.printStackTrace();
        }
        catch (ExecutionException e) {
        	e.printStackTrace();
        }

Where the String array we send is a list of the posts that we want to retrieve.

Dead simple, fast and efficient! Yay!

Android Volley – HTTP async Swiss Army Knife

This serves as a post to help you get started with Android Volley. Volley is used for all sorts of HTTP requests, and supports a whole bang of cool features that will make your life way easier.

It is a relatively simple API to implement, and allows request queuing, which comes in very useful.

The code below is a simple example to make a request to this blog and get a JSON response back, parse it and display it in a simple TextView widget on the device.

package za.co.paulscott.volleytest;

import org.json.JSONObject;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;

public class MainActivity extends ActionBarActivity {

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

        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, new PlaceholderFragment())
                    .commit();
        }
        
        txtDisplay = (TextView) findViewById(R.id.txtDisplay);

		RequestQueue queue = Volley.newRequestQueue(this);
		String url = "http://paulscott.co.za/blog/wp-json.php/posts/100";

		JsonObjectRequest jsObjRequest = new JsonObjectRequest(Request.Method.GET, url, null, new Response.Listener<JSONObject>() {

			@Override
			public void onResponse(JSONObject response) {
				Log.i("volleytest",response.toString());
				String txt = response.toString();
				Log.i("volleytest", txt);
				txtDisplay.setText(txt);
				findViewById(R.id.progressBar1).setVisibility(View.GONE);
			}
		}, new Response.ErrorListener() {

			@Override
			public void onErrorResponse(VolleyError error) {
				// TODO Auto-generated method stub

			}
		});

		queue.add(jsObjRequest);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    /**
     * A placeholder fragment containing a simple view.
     */
    public static class PlaceholderFragment extends Fragment {

        public PlaceholderFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_main, container, false);
            return rootView;
        }
    }

}

Maven for Android

This is a quick howto on setting up and using Maven for your Android projects. Maven Android integration is not yet excellent, but is coming along nicely, and if you are familiar with Maven projects, will make managing your dependencies a lot easier!

I will be working with Ubuntu, but your set up will be similar. Just adapt paths etc for your setup as you need.

Ubuntu ships with Maven2, but we need Maven-3.0.5 at least in order to work with Android. I prefer to install maven manually because you don’t need to stress about pinning and other such nonsense from a binary distro. I also usually install stuff in /opt/ so that is where we will be working from.

The first thing that you need to do, is to grab the maven distribution file. I used 3.2.1, but anything later than 3.0.5 should work OK.

wget http://apache.saix.net/maven/maven-3/3.2.1/binaries/apache-maven-3.2.1-bin.tar.gz

Extract the archive and copy it to /opt/

sudo cp apache-maven-3.2.1 /opt/

Great! First steps completed! You are doing well so far!
I am assuming that you have a semi-recent JDK installed, in our case we need JDK 6+. Check for your JDK version with

java -version

If all comes back OK, we are ready to proceed.

Get the path to your JDK now with

locate bin/java | grep jdk

and make a note of it. Mine is at

/opt/java7/jdk1.7.0_45

Edit your bashrc file (located at /etc/bash.bashrc on Ubuntu) and add the following parameters (modify according to your paths) to the end of the file:

export ANDROID_HOME=/opt/android-sdk-linux
export M3_HOME=/opt/apache-maven-3.2.1
export M3=$M3_HOME/bin
export PATH=$M3:$PATH
export JAVA_HOME=/opt/java7/jdk1.7.0_45
export PATH=$JAVA_HOME/bin:$PATH:/opt/java7/jdk1.7.0_45

Load up your new basrc file with

source /etc/bash.bashrc

and check that everything is OK.
You should now be able to test your brand new Maven3 installation with

mvn -version

If that seems OK, you are ready to install the Android m2e connector in Eclipse. Please note that this works best in Eclipse Juno or later (I use Kepler).

Open up Eclipse, and choose to install software from the Eclipse Marketplace. This is found in Help -> Eclipse Marketplace. Do a search for “android m2e” and install the Android configurator for M2E 0.4.3 connector. It will go ahead and resolve some dependencies for you and install.

You should now be able to generate a new Android project in Eclipse with New Project -> Maven -> new Maven project and in the archetype selection, look only in the Android catalogue or filter on “de.akquinet.android.archetypes” and choose the android quickstart project.

If this fails, you can also generate a new project on the command line and simply import it to Eclipse.

mvn archetype:generate \
  -DarchetypeArtifactId=android-quickstart \
  -DarchetypeGroupId=de.akquinet.android.archetypes \
  -DarchetypeVersion=1.0.11 \
  -DgroupId=com.your.company \
  -DartifactId=myshinyapp

Once all of that is complete, dev carries on as usual. Remember that now dependencies are in your POM.xml document, so check that out first and ensure that you have some basics in there:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.your.company</groupId>
	<artifactId>myshinyapp</artifactId>
	<version>1.0-SNAPSHOT</version>
	<packaging>apk</packaging>
	<name>myshinyapp</name>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<platform.version></platform.version>
		<android.plugin.version>3.6.0</android.plugin.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>com.google.android</groupId>
			<artifactId>android</artifactId>
			<version>4.1.1.4</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>com.actionbarsherlock</groupId>
			<artifactId>actionbarsherlock</artifactId>
			<version>4.4.0</version>
		</dependency>

		<!-- Androlog is a logging and reporting library for Android -->
		<dependency>
			<groupId>de.akquinet.android.androlog</groupId>
			<artifactId>androlog</artifactId>
			<version>1.0.5</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.10</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>joda-time</groupId>
			<artifactId>joda-time</artifactId>
			<version>2.3</version>
		</dependency>

	</dependencies>
	<build>
		<finalName>${project.artifactId}</finalName>
		<pluginManagement>
			<plugins>
				<plugin>
					<groupId>com.jayway.maven.plugins.android.generation2</groupId>
					<artifactId>android-maven-plugin</artifactId>
					<version>${android.plugin.version}</version>
					<extensions>true</extensions>
				</plugin>
			</plugins>
		</pluginManagement>
		<plugins>
			<plugin>
				<groupId>com.jayway.maven.plugins.android.generation2</groupId>
				<artifactId>android-maven-plugin</artifactId>
				<configuration>
					<sdk>
						<platform>17</platform>
					</sdk>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

As you can see, I have included some other stuff, like ActionBarSherlock and JodaTime in case, as they are generally really useful, and it may save you some time just copying the dependency information!

Have fun!

How to start an Android app at boot time

I wanted to have my custom Android ROM boot up and start an application specific to my needs start immediately. This is the way to accomplish it:

In your AndroidManifest.xml document (application part):

<receiver android:enabled="true" android:name=".BootUpReceiver"
        android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
        <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
</receiver>

You also need to set up a permission with

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

and then create the BootUpReceiver class to handle it

public class BootUpReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
                Intent i = new Intent(context, MyWhateverActivity.class);  
                i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                context.startActivity(i);  
        }
}

Android Intent to install app dependencies

I finally find myself actually having to learn Android app development properly now, and came up against an issue where I need my app to [optionally] install another app form my F-Droid repo or the Google Play store.

Luckily, either way, you should be covered as the Android framework is clever enough to parse the market:// URI’s as “generic package manager” so if you have a Free ROM that does NOT include Google Play services, you should still be OK with this code.

I have set a button click event to go and check if the app is installed, and then install it with an alert.

Button boButton = (Button) findViewById(R.id.bo_button);

        boButton.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View arg0) {

                try{
                    Intent intent = new Intent("com.dstv.boxoffice.android.SCAN");
                    intent.setPackage("com.dstv.boxoffice.client.android");
                    startActivityForResult(intent, 0);
                } catch (Exception e) {
                    createAlert("BoxOffice Application not installed!", "This application uses " +
                            "the BoxOffice application by DStv, you need to install " +
                            "this before you can use this functionality!", true, "market://search?q=pname:com.dstv.boxoffice.client.android");
                }

            }
        });

The intent above will fire off an alert (see code below) and prompt the user to install the required package.

private void createAlert(String title, String message, Boolean button, final String marketURL) {
        AlertDialog alertDialog;
        alertDialog = new AlertDialog.Builder(this).create();
        alertDialog.setTitle(title);
        alertDialog.setMessage(message);
        if ((button == true)) {
            alertDialog.setButton("Download Now",
                    new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface arg0, int arg1) {
                            Intent browserIntent = new Intent(
                                    Intent.ACTION_VIEW,
                                    Uri.parse(marketURL));
                            startActivity(browserIntent);
                        }
                    });
        }
        alertDialog.show();
    }

Simple!