Android – Google Contact API 3.0 Example

One of the application I am developing for Android had a functionality to import Google contacts. I faced quite a few issues to implement this and was not able to find any tutorial / example on internet providing detailed information.
Now that I’ve implemented this feature, thought of sharing my knowledge with the community with this blog post. Hope this is helpful.

Important Links -
Google Contact API Documentation -  https://developers.google.com/google-apps/contacts/
Google Developer Console – https://console.developers.google.com/
GData Java Client – https://code.google.com/p/gdata-java-client/downloads/
Guava Libraries – https://code.google.com/p/guava-libraries/
Contact API Scope – https://www.googleapis.com/auth/contacts.readonly

Google Project Setup – First of all, you will have to register your project with Google. To do so -
1) Login to Google Developer Console https://console.developers.google.com/project/ and create new Project.

Create_Project

2) Once project is created, we will have to generate identifiers for OAuth 2.0 authentication. Navigate to ‘APIS & AUTH’ > ‘Credentials’ and click on ‘Create new Client ID’.

Create_Project_ID

3) From Create Client ID Popup, select Application Type as ‘Installed Application’ and Select ‘Other’ for ‘Installed Application Type’ and click ‘Create Client ID’ button.

Create_Client_ID_Popup

4) This should generate the Client ID, Client Secret and Redirect URL for native application. These details will be required for authentication.

Create_Client_ID

5) Now it’s time to enable Contact API for this project. Navigate to ‘APIS & AUTH’ > ‘APIs’, search for ‘Contacts API’ and Click ‘OFF’ button. This should enable Contacts API.

Authorizing request using OAuth 2.0
Typical flow for OAuth 2.0 authentication is -
1) Application makes request to Google to fetch contacts (Scope)
2) Google shows OAuth Dialog to users for their approval
3) Once user provides approval, a short lived ‘Access Token’ is sent to requesting application
4) Application then makes request to get contacts using this Access Token

Now lets look at the actual code to authorize and fetch contacts details from an Android application.

We will create an Activity which will show OAuth window to users using WebView and once user permission is granted, we will make a call to fetch Google Contacts.

Lets start with Layout -
1) activity_import_gmail_contacts.xml – Create a layout which will be used by our Activity

<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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.tusharwadekar.android.ttmm.ImportGmailContactsActivity" >

    
    <Button
        android:id="@+id/importContactButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="#33B5E5"
        android:text="@string/importContactButton" />
    
    <ListView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:paddingBottom="5dp"
        android:layout_above="@id/importContactButton" />
    
</RelativeLayout>

2) auth_dialog.xml – Create a layout which will be used to display Google OAuth window

<?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" >
      <WebView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/webv"/>
</LinearLayout>

Create a class GetAccessToken. This class will be used to parse Access Token received from Google.

package com.tusharwadekar.android.ttmm.oauth;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONException;
import org.json.JSONObject;
import android.util.Log;
public class GetAccessToken {
	static InputStream is = null;
    static JSONObject jObj = null;
    static String json = "";
    public GetAccessToken() {
    }
    List<NameValuePair> params = new ArrayList<NameValuePair>();
    Map<String, String> mapn;
    DefaultHttpClient httpClient;
    HttpPost httpPost;
    public JSONObject gettoken(String address,String token,String client_id,String client_secret,String redirect_uri,String grant_type) {
        // Making HTTP request
        try {
            // DefaultHttpClient
            httpClient = new DefaultHttpClient();
            httpPost = new HttpPost(address);
            params.add(new BasicNameValuePair("code", token));
            params.add(new BasicNameValuePair("client_id", client_id));
            params.add(new BasicNameValuePair("client_secret", client_secret));
            params.add(new BasicNameValuePair("redirect_uri", redirect_uri));
            params.add(new BasicNameValuePair("grant_type", grant_type));
            httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded");
            httpPost.setEntity(new UrlEncodedFormEntity(params));
            HttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            is = httpEntity.getContent();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    is, "iso-8859-1"), 8);
            StringBuilder sb = new StringBuilder();
            String line = null;
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
            is.close();
            json = sb.toString();
            Log.e("JSONStr", json);
        } catch (Exception e) {
          e.getMessage();
            Log.e("Buffer Error", "Error converting result " + e.toString());
        }
        // Parse the String to a JSON Object
        try {
            jObj = new JSONObject(json);
        } catch (JSONException e) {
            Log.e("JSON Parser", "Error parsing data " + e.toString());
        }
        // Return JSON String
        return jObj;
    }
}

Create a class GoogleConstants. This class will hold all the constants required for Authentication and fetching Google Contacts.

package com.tusharwadekar.android.ttmm.util;

public class GoogleConstants {
	public static String CLIENT_ID = "";
	// Use your own client id

	public static String CLIENT_SECRET = "";
	// Use your own client secret

	public static String REDIRECT_URI = "http://localhost";
	public static String GRANT_TYPE = "authorization_code";
	public static String TOKEN_URL = "https://accounts.google.com/o/oauth2/token";
	public static String OAUTH_URL = "https://accounts.google.com/o/oauth2/auth";
	public static String OAUTH_SCOPE = "https://www.googleapis.com/auth/contacts.readonly";
	
	public static final String CONTACTS_URL = "https://www.google.com/m8/feeds/contacts/default/full";
	public static final int MAX_NB_CONTACTS = 1000;
	public static final String APP = "";
}

Now lets create an activity ImportGmailContactsActivity.

package com.tusharwadekar.android.ttmm;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;

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

import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;

import com.google.gdata.client.contacts.ContactsService;
import com.google.gdata.data.contacts.ContactEntry;
import com.google.gdata.data.contacts.ContactFeed;
import com.google.gdata.data.extensions.Email;
import com.google.gdata.data.extensions.Name;
import com.tusharwadekar.android.ttmm.model.Contact;
import com.tusharwadekar.android.ttmm.oauth.GetAccessToken;
import com.tusharwadekar.android.ttmm.util.ConstantUtil;
import com.tusharwadekar.android.ttmm.util.GoogleConstants;

public class ImportGmailContactsActivity extends BaseActivity {
	final String TAG = getClass().getName();

	private Dialog auth_dialog;
	private ListView list;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_import_gmail_contacts);
		list = (ListView) findViewById(R.id.list);
		launchAuthDialog();
	}

	private void setContactList(List<Contact> contacts) {
		ArrayAdapter<Contact> adapter = new ArrayAdapter<Contact>(this,
				android.R.layout.simple_list_item_multiple_choice, contacts);
		list.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
		list.setAdapter(adapter);
	}

	private void launchAuthDialog() {
		final Context context = this;
		auth_dialog = new Dialog(context);
		auth_dialog.setTitle(getString(R.string.web_view_title));
		auth_dialog.setCancelable(true);
		auth_dialog.setContentView(R.layout.auth_dialog);

		auth_dialog.setOnCancelListener(new OnCancelListener() {
			@Override
			public void onCancel(DialogInterface dialog) {
				finish();
			}
		});

		WebView web = (WebView) auth_dialog.findViewById(R.id.webv);
		web.getSettings().setJavaScriptEnabled(true);
		web.loadUrl(GoogleConstants.OAUTH_URL + "?redirect_uri="
				+ GoogleConstants.REDIRECT_URI
				+ "&response_type=code&client_id=" + GoogleConstants.CLIENT_ID
				+ "&scope=" + GoogleConstants.OAUTH_SCOPE);
		web.setWebViewClient(new WebViewClient() {
			boolean authComplete = false;

			@Override
			public void onPageStarted(WebView view, String url, Bitmap favicon) {
				super.onPageStarted(view, url, favicon);
			}

			@Override
			public void onPageFinished(WebView view, String url) {
				super.onPageFinished(view, url);
				if (url.contains("?code=") && authComplete != true) {
					Uri uri = Uri.parse(url);
					String authCode = uri.getQueryParameter("code");
					authComplete = true;
					auth_dialog.dismiss();
					new GoogleAuthToken(context).execute(authCode);
				} else if (url.contains("error=access_denied")) {
					Log.i("", "ACCESS_DENIED_HERE");
					authComplete = true;
					auth_dialog.dismiss();
				}
			}
		});
		auth_dialog.show();
	}

	private class GoogleAuthToken extends AsyncTask<String, String, JSONObject> {
		private ProgressDialog pDialog;
		private Context context;

		public GoogleAuthToken(Context context) {
			this.context = context;
		}

		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(context);
			pDialog.setMessage("Contacting Google ...");
			pDialog.setIndeterminate(false);
			pDialog.setCancelable(true);
			pDialog.setOnCancelListener(new OnCancelListener() {
				@Override
				public void onCancel(DialogInterface dialog) {
					finish();
				}
			});
			pDialog.show();
		}

		@Override
		protected JSONObject doInBackground(String... args) {
			String authCode = args[0];
			GetAccessToken jParser = new GetAccessToken();
			JSONObject json = jParser.gettoken(GoogleConstants.TOKEN_URL,
					authCode, GoogleConstants.CLIENT_ID,
					GoogleConstants.CLIENT_SECRET,
					GoogleConstants.REDIRECT_URI, GoogleConstants.GRANT_TYPE);
			return json;
		}

		@Override
		protected void onPostExecute(JSONObject json) {
			pDialog.dismiss();
			if (json != null) {
				try {
					String tok = json.getString("access_token");
					String expire = json.getString("expires_in");
					String refresh = json.getString("refresh_token");
					new GetGoogleContacts(context).execute(tok);
				} catch (JSONException e) {
					e.printStackTrace();
				}
			}
		}
	}

	private class GetGoogleContacts extends
			AsyncTask<String, String, List<ContactEntry>> {

		private ProgressDialog pDialog;
		private Context context;

		public GetGoogleContacts(Context context) {
			this.context = context;
		}

		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(context);
			pDialog.setMessage("Authenticated. Getting Google Contacts ...");
			pDialog.setIndeterminate(false);
			pDialog.setCancelable(true);
			pDialog.setOnCancelListener(new OnCancelListener() {
				@Override
				public void onCancel(DialogInterface dialog) {
					finish();
				}
			});
			pDialog.show();
		}

		@Override
		protected List<ContactEntry> doInBackground(String... args) {
			String accessToken = args[0];
			ContactsService contactsService = new ContactsService(
					GoogleConstants.APP);
			contactsService.setHeader("Authorization", "Bearer " + accessToken);
			contactsService.setHeader("GData-Version", "3.0");
			List<ContactEntry> contactEntries = null;
			try {
				URL feedUrl = new URL(GoogleConstants.CONTACTS_URL);
				ContactFeed resultFeed = contactsService.getFeed(feedUrl,
						ContactFeed.class);
				contactEntries = resultFeed.getEntries();
			} catch (Exception e) {
				pDialog.dismiss();
				Toast.makeText(context, "Failed to get Contacts",
						Toast.LENGTH_SHORT).show();
			}
			return contactEntries;
		}

		@Override
		protected void onPostExecute(List<ContactEntry> googleContacts) {
			if (null != googleContacts && googleContacts.size() > 0) {
				List<Contact> contacts = new ArrayList<Contact>();

				for (ContactEntry contactEntry : googleContacts) {
					String name = "";
					String email = "";

					if (contactEntry.hasName()) {
						Name tmpName = contactEntry.getName();
						if (tmpName.hasFullName()) {
							name = tmpName.getFullName().getValue();
						} else {
							if (tmpName.hasGivenName()) {
								name = tmpName.getGivenName().getValue();
								if (tmpName.getGivenName().hasYomi()) {
									name += " ("
											+ tmpName.getGivenName().getYomi()
											+ ")";
								}
								if (tmpName.hasFamilyName()) {
									name += tmpName.getFamilyName().getValue();
									if (tmpName.getFamilyName().hasYomi()) {
										name += " ("
												+ tmpName.getFamilyName()
														.getYomi() + ")";
									}
								}
							}
						}
					}
					List<Email> emails = contactEntry.getEmailAddresses();
					if (null != emails && emails.size() > 0) {
						Email tempEmail = (Email) emails.get(0);
						email = tempEmail.getAddress();
					}
					Contact contact = new Contact(name, email,
							ConstantUtil.CONTACT_TYPE_GOOGLE);
					contacts.add(contact);
				}
				setContactList(contacts);

			} else {
				Log.e(TAG, "No Contact Found.");
				Toast.makeText(context, "No Contact Found.", Toast.LENGTH_SHORT)
						.show();
			}
			pDialog.dismiss();
		}

	}

}

This activity does following tasks -
1) Once activity is create, launchAuthDialog() is called. This method is responsible to show Auth Dialog.
2) Have a look at loadUrl. Here we are passing following key information -
OAuth URL – https://accounts.google.com/o/oauth2/auth
Redirect URL – http://localhost
Client ID – Your Project ID which was created while  project setup.
Scope – Here we are using Contacts API so scope is – https://www.google.com/m8/feeds/contacts/default/full

web.loadUrl(GoogleConstants.OAUTH_URL + “?redirect_uri=”
+ GoogleConstants.REDIRECT_URI
+ “&response_type=code&client_id=” + GoogleConstants.CLIENT_ID
+ “&scope=” + GoogleConstants.OAUTH_SCOPE);

2) onPageFinished is called when user provides approval or denies the request.
3) If user provides approval, a special Auth Code is sent back from Google. We make a request to get Access Token from Google using this Auth Code. Here class GoogleAuthToken comes in picture.
Have a look at doInBackground method. Here we are making a request to get Access Token from Google using class GetAccessToken.
Token URL – https://accounts.google.com/o/oauth2/token
Auth Code – Which is received from Google when user approves permission in Google OAuth Dialog
Client ID – Your Project ID which was created in  project setup.
Client Secret – Your Project Secret which was created in  project setup.
Redirect URL – http://localhost
Grant Type – authorization_code

Once response from Google is received, a JSON object having access_token is received in onPostExecute callback.
4) Now that we have Access Token, we can make a request to get Contacts. Here class GetGoogleContacts comes in picture.
Have a look at doInBackground method, here we are creating an instance of ContactService. We are passing Access Token in Request Header.
Next we create instance of ContactFeed which will be used to get list of ContactEntry
Once response is received, onPostExecute method is called and list of ContactEntry is passed to the same. That’s it… each ContactEntry object will have Google contact details.

Making AMF calls from AJAX (JSP, Java)

AMF (Action Message Format) Calls can be triggered from Java Client as well. AMFConnection class can be used for the same. Documentation is available at: http://livedocs.adobe.com/blazeds/1/javadoc/flex/messaging/io/amf/client/AMFConnection.html

Below is a simple example usage:

  • Setup BlazeDS and create simple destination as explained by Sujit Reddy on his blog. URL: http://sujitreddyg.wordpress.com/2008/01/14/invoking-java-methods-from-adobe-flex/
  • Create following two JSP files – ‘index.jsp’, ‘callAMF.jsp’. Copy these files on server. In my case, application name was ‘JavaAMFClientExample’. I copied files to root of ‘JavaAMFClientExample’ application on server.
  • Create Java Class ‘JavaAMFClientImpl’; add BlazeDS JAR files to Java project. Copy class file in appropriate directory on server. In my case path of class file was – <TomcatServer\webapps>\JavaAMFClientExample\WEB-INF\classes\com\tushar\JavaAMFClientExample\JavaAMFClientImpl.class
  • Run index.jsp and click ‘Call Service’ button. This will make an AJAX call to ‘callAMF.jsp’ and will display server response in same page.

index.jsp:

<HTML>
<head>
<style type="text/css">
table {
 border-width: 0px;
 border-spacing: 2px;
 border-style: dashed;
 border-color: gray;
 border-collapse: separate;
 background-color: white;
}
table td {
 border-width: 0px;
 padding: 0px;
 border-style: inset;
 border-color: gray;
 background-color: white;
 -moz-border-radius: 0px 0px 0px 0px;
}
p, li, td { font-family: verdana;  font-size: 12px }

</style>
<script type="text/javascript">
{
 var xmlHttp;
 try    {  // Firefox, Opera 8.0+, Safari
 xmlHttp=new XMLHttpRequest();
 }
 catch (e) {  // Internet Explorer
 try {
 xmlHttp=new ActiveXObject("Msxml2.XMLHTTP");
 }
 catch (e) {
 try {
 xmlHttp=new ActiveXObject("Microsoft.XMLHTTP");
 }
 catch (e) {
 alert("Your browser does not support AJAX!");
 }
 }
 }

 xmlHttp.onreadystatechange=function()
 {
 if(xmlHttp.readyState==4) {
 document.getElementById("response").innerHTML = xmlHttp.responseText;
 }
 }

 function onFormSubmit()
 {
 var destination = document.getElementById("destination");
 var method = document.getElementById("method");
 var name = document.getElementById("name");

 if(destination != null){
 destination = destination.value
 }
 if(method != null){
 method = method.value
 }
 if(name != null){
 name = name.value
 }
 var params = "destination="+destination+"&method="+method+"&name="+name+"&sid="+Math.random();
 var url="callAMF.jsp?"+params;
 xmlHttp.open("GET",url,true);
 xmlHttp.send(null);
 }
}
</script>
</head>
<BODY>
<table border="0" width="100%" cellspacing="0" cellpadding="0">
 <tr>
 <td>
 <table border="1" width="100">
 <tr>
 <td colspan="2" align="center"><div><h5>Test AMF Call</h1></div></td>
 </tr>
 <tr>
 <td>Destination:</td>
 <td><INPUT TYPE=TEXT id="destination" value="CreatingRpc" SIZE=20></td>
 </tr>
 <tr>
 <td>Method:</td>
 <td><INPUT TYPE=TEXT id="method" value="getResults" SIZE=20></td>
 </tr>
 <tr>
 <td>Name:</td>
 <td><INPUT TYPE=TEXT id="name" value="Tushar" SIZE=20></td>
 </tr>
 <tr>
 <td></td>
 <td><input type="submit" value="Call Service" onclick="onFormSubmit()" /></td>
 </tr>
 </table>
 </td>
 </tr>
 <tr>
 <td align="left"><div id="response"></td>
 </tr>
</BODY>
</HTML>

callAMF.jsp:

<%@ page language="java" %>
<%@ page import="com.tushar.JavaAMFClientExample.*" %>

<%
String destination=request.getParameter("destination");
String method=request.getParameter("method");
String name=request.getParameter("name");

JavaAMFClientImpl javaAMFClientService = new JavaAMFClientImpl();
Object obj = javaAMFClientService.sendAMFMessage(destination, method, name);
%>
<p>Message from Server: <%=obj%><p>

JavaAMFClientImpl.java

package com.tushar.JavaAMFClientExample;

import flex.messaging.io.amf.client.AMFConnection;
import flex.messaging.io.amf.client.exceptions.ClientStatusException;
import flex.messaging.io.amf.client.exceptions.ServerStatusException;

public class JavaAMFClientImpl {

 public Object sendAMFMessage(String destination, String method, String name) throws ClientStatusException, ServerStatusException {
 // Create the AMF connection.
 AMFConnection amfConnection = new AMFConnection();
 // Connect to the remote url.
 String url = "http://localhost:8080/JavaAMFClientExample/messagebroker/amf";
 try
 {
 amfConnection.connect(url);
 }
 catch (ClientStatusException cse)
 {
 System.out.println(cse);
 return null;
 }

 // Make a remoting call and retrieve the result.
 try
 {
 Object result = amfConnection.call(destination+"."+method, name);
 return result;
 }
 catch (ClientStatusException cse)
 {
 System.out.println(cse);
 return null;
 }
 catch (ServerStatusException sse)
 {
 System.out.println(sse);
 return null;
 }
 finally {
 amfConnection.close();
 }
 }
}

Java Project:

Flex Client:

JSP Client:

JavaScript AMF Implementation:

James Ward has created a Pure JavaScript AMF implementation. Here are more details: http://css.dzone.com/news/amfjs-%E2%80%93-pure-javascript-amf

iPhone Simulator – Add Images in ‘Photos’

Here is a quick tip to add images in iPhone Simulator for Mac (Xcode).

  • Open iPhone Simulator
  • Navigate to image using ‘Finder’
  • Drag image from ‘Finder’ to iPhone Simulator
  • This will open the image in ‘Safari’ on iPhone Simulator
  • Now Click and Hold on the image in ‘Safari’
  • This will open a menu with option – ‘Save Image’, ‘Copy’ and ‘Cancel’
  • Hit ‘Save Image’. This will save the image for iPhone Simuator

Once images are saved, those are listed in Photos > Saved Photos

For me this was quite tricky so thought of sharing. Hope this helps someone :)

Flex 3 Auto-Resize TextInput Control? How?

Here is a quick solution to add Auto-Size functionality to TextInput control in Flex 3.

AutoSizeTextInput.class

package com.tushar.controls {
	
	import flash.events.Event;	
	import mx.controls.TextInput;
	
	public class AutoSizeTextInput extends TextInput {
		
		private var txtSpan:uint = 10;
		
		public function AutoSizeTextInput() {
			super();
			this.addEventListener(Event.CHANGE, onTextChange);
		}
		
		private function onTextChange(evnt:Event):void {
			this.textField.scrollH = 0;
			this.width = this.autoSizeWidth;
		}
		
		override protected function commitProperties():void {
			super.commitProperties();
			this.textField.scrollH = 0;
			this.width = this.autoSizeWidth;
		}
		
		private function get autoSizeWidth():uint {
			return (this.textWidth+txtSpan);			
		}
	}
}

Usage in MXML:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
	xmlns:custom="com.tushar.controls.*"
	layout="vertical">
	<custom:AutoSizeTextInput text="Hello world! "/>
</mx:Application>

Not sure if this method is perfect! Do let me know if there is any better way to implement this :)

AS3 FlexNativeMenu and dataprovider

Yesterday I faced an issue with FlexNativeMenu (popup menu) and its dataprovider. Looks like, FlexNativeMenu does not recognize the changes made to dataprovider.

Situation: I was wanted to disable the menu item based on the Label (clicking which I am showing the FlexNativeMenu as a Popup menu).

So, before showing the menu (FlexNativeMenu.display) I tried to update the Dataprovider (XML). But changes were not reflected in the menu! Further, Error #1009 was reported after clicking any menuItem.

I even tried to assign the dataprovider again hoping that this way problem will get resolved.
But this time also, changes were not reflected. However, Menu was updated after clicking twice on the same label. But clicking on any menuItem was still reporting Error #1009.

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
layout="horizontal" creationComplete="init();">
<mx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.events.FlexNativeMenuEvent;
import mx.containers.HBox;
import mx.controls.TextInput;
import mx.controls.Label;
import mx.controls.FlexNativeMenu;

private var phoneList:Array;
private var popupMenu:FlexNativeMenu;
private var lblWidth:uint;

private function init():void {
lblWidth = 50;
phoneList = new Array({type:"Home", number:"11111"}, {type:"Work", number:"22222"});
initPopupMenu();
phoneList.forEach(showPhoneList);
}

private function initPopupMenu():void {
popupMenu = new FlexNativeMenu();
popupMenu.dataProvider = defaultPhoneProps;
popupMenu.showRoot = false;
popupMenu.labelField = "@label";
}

private function showPhoneList(item:*, index:int, arr:Array):void {
var phoneLabel:Label = new Label();
var phoneNumber:TextInput = new TextInput();
var hBox:HBox = new HBox();
phoneLabel.id = "label";
phoneLabel.addEventListener(MouseEvent.CLICK, showContextMenu)
phoneLabel.text = item.type;
phoneLabel.width = lblWidth;
phoneNumber.id = "number";
phoneNumber.text = item.number;
hBox.id = "phone_"+item.type;
hBox.addChild(phoneLabel);
hBox.addChild(phoneNumber);
phoneListContainer.addChild(hBox);
}

private function showContextMenu(event:MouseEvent):void {
// Update Dataprovider
// Any changes to the dataprovider result in runtime error:
/*
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at mx.controls::FlexNativeMenu/itemSelectHandler()[E:\dev\3.1.0\frameworks\projects\airframework\src\mx\controls\FlexNativeMenu.as:1633]
at flash.display::NativeMenu/_display()
at flash.display::NativeMenu/display()
at mx.controls::FlexNativeMenu/display()[E:\dev\3.1.0\frameworks\projects\airframework\src\mx\controls\FlexNativeMenu.as:1364]
at NativeMenuTest/showContextMenu()[C:\Users\Tushar\Documents\Flex Builder 3\NativeMenuTest\src\NativeMenuTest.mxml:56]
*/
// For example, you update the dataprovider:
for each (var property:XML in defaultPhoneProps..menuitem) {
property.@toggled = "false";
property.@enabled = "true";
}
defaultPhoneProps..menuitem.(@label==event.currentTarget.text).@toggled = "true";
defaultPhoneProps..menuitem.(@label==event.currentTarget.text).@enabled = "false";
// Show Popup Menu
popupMenu.addEventListener(FlexNativeMenuEvent.ITEM_CLICK, onItemClick);
popupMenu.display(event.target.stage, event.stageX, event.stageY);
}

private function onItemClick(menuEvent:FlexNativeMenuEvent):void{
popupMenu.removeEventListener(FlexNativeMenuEvent.ITEM_CLICK, onItemClick);
//Alert.show(menuEvent.item.@label, "Native Menu Event")
}
]]>
</mx:Script>
<mx:XML id="defaultPhoneProps" format="e4x">
<menu label="Popup">
<menuitem label="Work" command="toggleIconMenu"
type="check" toggled="false" enabled="true"/>
<menuitem label="Home" command="toggleMenuBar"
type="check" toggled="false" enabled="true"/>
<menuitem label="Mobile" command="toggleIconMenu"
type="check" toggled="false" enabled="true"/>
<menuitem label="HomeFax" command="toggleIconMenu"
type="check" toggled="false" enabled="true"/>
<menuitem label="WorkFax" command="toggleIconMenu"
type="check" toggled="false" enabled="true"/>
</menu>
</mx:XML>
<mx:VBox id="phoneListContainer" />
</mx:WindowedApplication>

Solution:
To solve this problem I had to work with the FlexNativeMenuItem directly.
Updated showContextMenu function:

private function showContextMenu(event:MouseEvent):void {
// Update NativeMenuItem directly along with the dataprovider
for each (var property:NativeMenuItem in popupMenu.nativeMenu.items) {
if(property.label == event.currentTarget.text){
property.enabled = false;
property.checked = true;
} else {
property.enabled = true;
property.checked = false;
}
}
// Show Popup
popupMenu.addEventListener(FlexNativeMenuEvent.ITEM_CLICK, onItemClick);
popupMenu.display(event.target.stage, event.stageX, event.stageY);
}

Flex – Double Click to edit functionality for List Control

When for a list control the property editable is set to true, one can click on the item and rename the label or edit the item if an item editor is set. Here is a work around if you wish to enable editing only on double-click:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="init();">

<mx:Script>
<![CDATA[

import mx.events.ListEvent;
import mx.core.EventPriority;

private function init():void {
myList.addEventListener(ListEvent.ITEM_DOUBLE_CLICK, onItemDoubleClick, 
			false, EventPriority.DEFAULT_HANDLER);
myList.addEventListener(ListEvent.ITEM_EDIT_END, onItemEditEnd, 
			false, EventPriority.DEFAULT_HANDLER);
}

private function onItemDoubleClick(event:ListEvent):void {
if (event.itemRenderer.data == "One") {
return;
}
if (event.isDefaultPrevented()) {
return;
}
myList.editable = true;
myList.editedItemPosition = {columnIndex:0, rowIndex:event.rowIndex};
}

private function onItemEditEnd(event:ListEvent):void {
myList.editable = false;
}
]]>
</mx:Script>

<mx:ArrayCollection id="myData">
<mx:String>One</mx:String>
<mx:String>Two</mx:String>
<mx:String>Three</mx:String>
<mx:String>Four</mx:String>
</mx:ArrayCollection>

<mx:Label text="Doubleclick any item to edit…"/>
<mx:List id="myList" doubleClickEnabled="true" dataProvider="{myData}" width="200"/>

</mx:Application>

Click here for live example.

Let me know incase of any bugs…

MXML ActionScript Classes

As you know, all MXML files are converted to ActionScript Classes which are later compiled in SWF file. If you want to view / study these automatically generated classes, you can use Compiler Argument -keep-generated-actionscript in Flex Builder Project.

In Flex Builder navigate to Project > Properties > Flex Compiler > Additional compiler arguments and add following configuration: -keep-generated-actionscript

This will create a folder named generated in project source folder which will have all ActionScript classes which Flex Builder automatically generates from MXML ({MXML file name}-generated).

Flex – Image Navigator Example

Image Navigator

Here is a small example of creating an Image Navigator for big images in Flex.

The big image is loaded in Canvas and an Image navigator will have a small prototype of this image. A small rectangle in Navigator can be used to scroll image. Click and drag small rectangle to scroll image. This rectangle corresponds to visible area of image in Canvas.

This example demonstrates usage of:

Image control

SWFLoader

Creating Sprite at runtime

Assigning Drag and drop functionality to sprite

Copying image using BitmapData

Controlling Scrollbar using ActionScript

Handling mouse events in Flex

Example: http://tushar.x10hosting.com/ImageNavigator/

Source: http://tushar.x10hosting.com/ImageNavigator/src/Navigator.mxml

ActionScript 3: Using URLLoader to send and load server variables

Here is a very simple example of two way communication with database using Flex and PHP. In this example, we are sending username and password to the PHP file from Flex. PHP file then validates the input and returns the appropriate response.

This example also demonstrates the simple PHP script to establish a database (MySQL) connection and validate username and password against the table in database.

In AS3, we can use flash.net.URLLoader, URLRequest and URLVariables class to send and load data. First create a class named SendAndLoadExample.

Class SendAndLoadExample:

package {

import flash.events.*
import flash.net.*;

public class SendAndLoadExample {

public function SendAndLoadExample() {}
public function sendData(url:String, _vars:URLVariables):void {
var request:URLRequest = new URLRequest(url);
var loader:URLLoader = new URLLoader();
loader.dataFormat = URLLoaderDataFormat.VARIABLES;
request.data = _vars;
request.method = URLRequestMethod.POST;
loader.addEventListener(Event.COMPLETE, handleComplete);
loader.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
loader.load(request);
}
private function handleComplete(event:Event):void {
var loader:URLLoader = URLLoader(event.target);
trace("Par: " + loader.data.par);
trace("Message: " + loader.data.msg);
}
private function onIOError(event:IOErrorEvent):void {
trace("Error loading URL.");
}
}
}

Now, create an object of SendAndLoadExample class in Flex.

SendAndLoadExample.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
		layout="vertical">
<mx:Script>
<![CDATA[
import flash.net.URLVariables;
private var mySendAndLoadExample:SendAndLoadExample;
mySendAndLoadExample = new SendAndLoadExample();
private function sendAndLoad():void {
var url:String = "http://[your server]/login.php";
var variables:URLVariables = new URLVariables();
variables.UserName = "tushar";
variables.Password = "my_password";
mySendAndLoadExample.sendData(url, variables);
}
]]>
</mx:Script>
<mx:Button label="Fetch data" click="sendAndLoad()"/>
</mx:Application> 

PHP Script for login check: login.php

<?   
$clientUserName=$_POST['UserName'];   
$clientPassword=$_POST['Password'];   
//////////////////////////////////////   
// Host name   
$host="[your server]"; 
// Mysql username
$username="[MySql database username]"; 
// Mysql password     
$password="[MySql database password]"; 
// Database name    
$db_name="[MySql database name]"; 
// Table name  
$tbl_name="[MySql table name having usernames and passwords]";  
function makeConnection() {   
// Connect to server and select databse.   
mysql_connect("$GLOBALS[host]", "$GLOBALS[username]", 
		"$GLOBALS[password]")or die("cannot connect");   
mysql_select_db("$GLOBALS[db_name]")or die("cannot select DB");   
}   
function fireQuery($query) {   
$result=mysql_query($query);   
return $result;   
}   
function printOutput($code, $msg){   
print "par=$code&msg=$msg";   
}   
//////////////////////////////////////   
function checkUserID($id, $password) {   
$sql="SELECT * FROM $GLOBALS[tbl_name] WHERE 
		userName='$id' and password='$password'";   
$result=fireQuery($sql);   
$count=mysql_num_rows($result);   
if($count==1){   
return true;   
}   
return false;   
}   
function init(){   
if(isSet($GLOBALS["clientUserName"]) 
	&& isSet($GLOBALS["clientPassword"])){   
makeConnection();   
if(checkUserID($GLOBALS["clientUserName"]
	, $GLOBALS["clientPassword"])){   
printOutput("1", "Login successful.");   
} else {   
printOutput("0", "Failed to login $GLOBALS[clientUserName].");   
}   
} else {   
printOutput("0", "Required parameters missing.");   
}   
}   
init();   
?> 

However, you can also use HTTPService to send and load data in Flex. Nitin has posted a simple example of using HTTPService in flex. Check it out here.

ActionScript 3: Creating custom event handlers using EventDispatcher

Creating custom event handlers in ActionScript 3 is pretty much simple. Here is a simple example which loads an image and fires a custom event named “onImageLoad” as soon as image is loaded.

Class imageLoader.as

package {
import flash.events.*;
import flash.net.URLRequest;
import flash.display.Loader;
import flash.display.Sprite;
public class imageLoader extends EventDispatcher {
private var _mc:Sprite;
private var url:String;
private var loader:Loader;
public function imageLoader(_mc:Sprite, url:String) {
this._mc = _mc;
this.url = url;
loadImg();
_mc.addChild(loader);
}
private function loadImg():void {
loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
var request:URLRequest = new URLRequest(url);
loader.load(request);
}
private function onComplete(event:Event):void {
dispatchEvent(new Event("onImageLoad"));
}
private function onIOError(event:IOErrorEvent):void {
trace("IO Error.");
}
}
}

Now in Flash, create a a object of imageLoader class and add a event listener (for our custom event “onImageLoad”).

var myImageLoader:imageLoader = new imageLoader(this, "images/someImg.jpg");
myImageLoader.addEventListener("onImageLoad", callbackHandler);
function callbackHandler(event:Event) {
trace("Image Loading Complete!");
}

Now test the file. Be sure to have someImg.jpg in images folder. Once image is loaded, you should be able to see “”Image Loading Complete!” message in output window.