Developing Chameleon Widgets

Last Updated: Nov 20, 2012


Get Started

Building widgets for Chameleon is really fast and simple. All widgets are HTML, Javascript, and CSS which should be familiar to developers and designers everywhere!

Download Widget Template

The above is a link to a zip file containing our basic widget template that we use to build all of our widgets. Using it is the fastest way to get a widget started.


Dev Widgets

We have a few useful widgets that you can install into Chameleon that assist in the development process:

Sizing Widget

Install Sizing Widget

When instantiated on a Chameleon Dashboard, this widget will log information on the size of the widget as you lay it out and move it. Can be useful when working with your CSS styles.

Lifecycle Widget

Install Lifecycle Widget

When instantiated on a Chameleon Dashboard, this widget will log all life cycle events as they happen. This can be useful in learning what all of the events are.


Using The "Make A Widget" Widget

The "Make A Widget" widget's primary function is to allow you to load and test the widgets you are creating. You can create an instance of it at any time and load the widget you are working on by URL.

"Make A Widget" can be found in the widget list when you edit your dashboard. It will always appear at the end of the list:

When you add an instance of it to a dashboard, you will be presented with a prompt. The prompt allows you to load a widget by it's base url for testing. It also offers a "Load Documentation" which brings you here.

If a valid widget is added by it's base url, it will appear on the dashboard as normal. The only difference is that it's chrome color will always be grey. This helps distinguish a widget in testing mode (dev mode as we call it), from one that is officially installed.

Here are the base urls for all of our current widgets. You can load any of these using "Make A Widget". (Do note that they won't all render nicely in a normal web browser as they are designed specifically for Chameleon)

Feel free to peruse the source of our widgets. They are the best example to refer to for now to see what's possible. (until you blow us away with your widgets =)


Publishing Widgets

Once a widget has been created and tested using "Make A Widget", users can install it into Chameleon using the chameleon://install uri. To publish your widget for install, simply create a link on a web page that can be visited by an Android device running Chameleon, and specify the href attribute for that link as follows:

chameleon://install?url=http://chameleon.teknision.com/widgets/template/

A more universal way to share your widget link is by pointing it to the install url on our website:

http://chameleonlauncher.com/install?url=http://chameleon.teknision.com/widgets/template/

By using the link above, users without an Android device running Chameleon will be forwarded to a webpage that: gives them more information about your widget, gives them an install link, and gives them a way to get Chameleon if they don't have it yet. These links are also shareable using social networking tools such as Twitter or Facebook.

If a user clicks on that link on an Android device running Chameleon, then Chameleon will prompt them asking if they would like to install the specified widget. Assuming that they allow it, and that everything else about the widget is ok, then the widget will install for use on their Home Screens.

Widgets can fail to install under certain circumstances, including:

  • No manifest file at the baseurl, or the manifest is formatted incorrectly.
  • Baseurl specified in the manifest does not match the baseurl the widget is being loaded from.
  • The guid of the widget is already in use by a widget already installed in Chameleon.
  • The server that hosts the widget is not reachable.
  • The widget url has been blacklisted by us.

Widget Structure and Resources

Chameleon widgets require a simple structure in order to function properly. The following section describes what elements are required for a widget as well as what resources exist that you can leverage:


Base URL

Every Chameleon widget is identified by the URL in which it is loaded from. This url is referred to as the "base url". This url points to the folder on your server in which the contents of the widget reside, not any specific file within it. The base url is the url you use to load your widget when you are testing it using "Make A Widget", or if you are adding it to the official widget list in Chameleon.


Widget Manifest

A Widget Manifest is an XML file used to tell Chameleon all about your widget. In order for a widget to load in Chameleon it must have a manifest.xml in it's base url directory. The manifest lets you specify how the widget loads, how the widget chrome behaves, allows you to specify the icons and colors to be used to represent your widget, and it allows all required assets to be listed so that the widget may be properly cached and used offline.

Here is an example of a manifest file


Index Page

We require that every widget's main page be called "index.html". This is required because Chameleon attempts to not only load the widget in the browser, but it will download and cache it for offline use. You should avoid using any server-side scripting in this page simply because it will not be evaluated when run offline, the cached result of it will instead. You can obviously use dynamic data all you want, but we suggest doing so through AJAX calls instead.


JavaScript Resources

We offer a few Javascript includes that you may use in your widgets to access extended Chameleon capabilities. Documentation for all Javascript includes can be found here.

http://chameleonlauncher.com/widgets/common/js/chameleon.js
Include this in your HTML file for easy access to all of the special features that Chameleon offers.

http://chameleonlauncher.com/widgets/common/js/chameleon.jquery.js
If you are using jQuery, then you can also include this file for some extra helper functions when working with Chameleon.


CSS Resources

We offer a few CSS files that you may include in your widget to quickly make them look and feel like the rest of the Chameleon widgets:

http://chameleonlauncher.com/widgets/common/css/chameleon.css
This file will apply all of the basic styling for a Chameleon Widget.

http://chameleonlauncher.com/widgets/common/css/chameleon-settings.css
This file will apply the styling we use for settings windows when configuring widgets.


Extended Capabilities

HTML, CSS, and Javascript alone will give you most of the tools you will need to build widgets, but Chameleon also offers some extended javascript capabilities that you can leverage in your widgets:

All of these capabilities can be utilized by including the chameleon.js javascript file in your widget.


Lifecycle Callbacks

Chameleon gives you the ability to respond to many events that are unique to a widget's lifecycle. These events are exposed as callback functions in javascript.

Here is a list of all of the lifecycle callbacks currently implemented:

  • onLoad
  • onCreate
  • onResume
  • onPause
  • onLayout
  • onScrollTop
  • onScrollElsewhere
  • onLayoutModeStart
  • onLayoutModeComplete
  • onConnectionAvailableChanged
  • onTitleBar
  • onConfigure
  • onRefresh
  • notChameleon

The template widget that we provide implements stubs for all of the callbacks available for a widget.


Offline Caching

Widgets will automatically cache all assets locally for offline use at runtime. You can be confident that if any asset(image,css file, javascript file, etc) has been requested when the user was online, it will also be available when they are offline. Chameleon will not cache data that is returned from remote server-side resources such as php files. It is the widget's responsibility to manage the storage of dynamic data.

Note that caching does not work when testing widgets using the "Make A Widget" widget. Caching would obviously be frustrating, because during the testing phase components are constantly being changed.


Managing State Data

Chameleon offers a suite of Javascript functions for saving and retrieving state data. It is important to understand the three different tiers in which data can be saved to, and how they differ:

Instance Data

Every widget instance has a data object in which it can store state data that belongs to only that instance. Any type of data can be stored on this object, but it is recommended that only simple state data required for the widget to initialize be stored here. Using our Twitter widget as an example, we store the id of the account that owns the instance, and the id of the feed type to load as instance data.

Avoid saving large data caches in this object, use LocalData instead. Instance data is attached to your dashboard object which is owned by your user profile. The future intention is that this user profile will have the option of being shared by multiple devices, so you only want data stored here that is necessary for the widget to initialize correctly.

Local Data

This tier is similar to Instance data in that data saved using it is only available to the widget instance that created it. The difference is that the data itself is saved as an individual file and loaded back on demand. This data is not attached to the dashboard and user profile, meaning that in the future it will not travel from device to device. You may save as many blocks of data that you need per widget by specifying an id for the block.

An example of how we used local data is to cache the most recent Tweets retrieved from twitter so the same tweets may be displayed if the device goes offline.

Shared Data

Multiple instances of a widget may wish to share data with each other, and Shared data provides this capability. The shared data object may be accessed and written to by any instance of a specific widget.

We use Shared data in our Twitter widget to store all of our account authentication credentials. This way all instances may pick from the same list of accounts available.


Launching Overlay Windows

Chameleon offers the ability for widgets to load another HTML page as a transparent modal window over the Dashboard. We use this capability to present all of our widget Settings panels. When these windows are launched, they are given access to the Chameleon Javascript APIs and are put in the same context as the widget that launched them.

Launched windows have the ability to return data back to the widget that launched them when they are closed.

If you choose to include the chameleon-settings.css file the HTML page you are launching as a window, it will automatically take on the look of the settings panels that we use for our widgets.

In the template widget we offer, there is already code present that illustrates how to launch a Settings window, along with having a pre-made Settings window example.


Android Intents

Chameleon allows you to use intents in Javascript to communicate with Android Activities and Services. Here are some examples of things you can do with intents:

  • Checking to see if an application is installed on the device
  • Launching applications installed on the device
  • Communicating back and forth with Services

Oauth Handshakes

Any widget that displays remote user data probably requires that the user authenticate using Oauth. Chameleon provides a simple Javascript mechanism for performing Oauth handshakes for version 1 and 2. Chameleon widgets behave more like web applications than installed applications, therefore Oauth 2.0 is always be the preferred choice. For Oauth 1.0, we require that keys be encrypted. Our encryption tool is not yet available for public use at this time.


Working with Android Services

Chameleon Widgets can access and utilize native functionality on the device by leveraging an Android Service. You may implement a service, bundle it in an APK file, install it and communicate with it using intents from your Chameleon Widget's javascript. In order to streamline that whole process and introduce some privacy and security, we have implemented a new Service SDK and made additions to the Widget SDK.

Chameleon Service Contracts

An Android Service must have "exported=true" set in it's manifest in order for Chameleon to contact it. This results in leaving your service open for other entities potentially running on your device to contact it as well. In many situations this might be ok, but there are also many where it is not.

If you ask for permission from the end user to access some of their private data, then the assumption is that your Service will keep that data to itself. In order to maintain this, Chameleon implements a contractual handshake that it can make with a Service that ensure that both parties can be confident that they are only exchanging information with each other and no one else. As of now, we are enforcing that all communication with native services implement this handshake.

Using the Service SDK

We have implemented a starting point for developers wanting to leverage an Android Service with Chameleon Service Contracts. The Service SDK contains an abstract implementation of the handshake process that you can quickly subclass and get up an running. It also simplifies the process of receiving data and sending data to and from Chameleon widgets.

The zip file below contains the classes needed to implement a ChameleonWidgetService along with simple examples:

Download Service SDK

Creating A Chameleon Widget Service

After downloading the Service SDK you will need to create a new Android Project that contains at least one Service in it's manifest that your widgets will contact. The Service as named in your Android Manifest must have the attribute "exported" set to true.

In order to communicate with Chameleon you must include the package of java classes named "com.chameleonlauncher.service". Inside of this package, there is a class named ChameleonWidgetService.java that the Service you intend to create must subclass. This class implements the entire Chameleon Service Contract handshake and a bunch of other things that simplify communicating with your widgets.

A subclass of ChameleonWidgetService looks like this in Java:

package com.chameleonlauncher.widgetservice.example;

import org.json.JSONException;
import org.json.JSONObject;
import android.util.Log;
import com.chameleonlauncher.service.*;

public class TemplateWidgetService extends ChameleonWidgetService 
{

	@Override
	//Called for all valid intents sent from Chameleon after the Chameleon Service Contract has been established.
	protected void onWidgetIntent(ChameleonWidgetRequest request) 
	{
		//Only respond to intents sent from the widget with the specified baseurl.
		if(request.baseurl.contentEquals("http://chameleon.teknision.com/widgets/remoteservice/")){
			
			//If the request contains JSON data, we can do something with it.
			if(request.data!=null)Log.d("TemplateWidgetService", "data:"+request.data.toString());
			
			
			try{	
				//Send a response back to the widget with JSON data.
				JSONObject response_data=new JSONObject();
				response_data.put("responsevar", "Response Value");
				respondTo(request, response_data);
				
			}catch(JSONException e){
				e.printStackTrace();
			}
		}
	}
	
}

The service above can be invoked by javascript in a widget using the following code:

//Call out to the service, send some data, and expect a response.	
chameleon.intent({
	component:{
		name:"com.chameleonlauncher.widgetservice.example.TemplateWidgetService",
		package:"com.chameleonlauncher.widgetservice.example",
		type:"chameleonservice"
		},
	data:{samplevar:"sample value"},
	result:function(data){
		//handle any JSON data sent back from the service.
		if(data!=null)alert(chameleon.toJSON(data));
	}
});

Prompting Users to Install Services

In order to use an Android Service with a Chameleon Widget, it has to be installed by the end user. There are two ways to achieve this: Downloading the Service's APK from the Google Play Store, or prompting the user to install the APK directly from a url.

Any widget that uses a native service should be aware that the service it relies on may not be present. The way to handle this is to use the chameleon.componentExists({component}) function like so:


var component={
	name:"com.chameleonlauncher.widgetservice.example.TemplateWidgetService",
	package:"com.chameleonlauncher.widgetservice.example",
	type:"chameleonservice"
},

if(chameleon.componentExists(component)){
	//do something with the service
}else{
	//prompt the user to get it
}

If you tie this check to the various lifecycle events of chameleon.widget({callbacks}) such as onLoad and onResume, then you will be able to change state if the service was being installed outside of Chameleon.

Dowloading it from the Google Play Store is pretty straightforward. Simply push the user to the Google Play Store link for your component using an intent:

chameleon.intent({
	action:"android.intent.action.VIEW", 
	data:"https://play.google.com/store/apps/details?id=com.chameleonlauncher.widgetservice.example"
});

Another option is to bundle the Service APK file with your Chameleon Widget. This means that the APK will be placed on your web server with your widgets assets. You may prompt the user to install the APK but it is dependent on whether or not they allow the installation of non-market apps in their settings. We have added the following functions to chameleon.js to help you detect if it is possible to install directly, as well as a function to prompt the user to choose to do it:

if(chameleon.allowsNonMarketInstall()){
	chameleon.install({url:"TemplateWidgetService.apk"});
}


Known Issues, Hacks, and Workarounds

Nothing ever goes exactly as planned, and building an HTML oriented widget system for Chameleon has had it's fair share of headaches due to bugs in native Android WebViews. Most of them can be handled on our side of things, but some of them cannot without little hacks on the Javascript side. We'll list any that require your attention here.

Rendering: Blank Screen and Double Draw bug

When an Android WebView is set to render with a transparent background (as all Chameleon Widgets are), a measurable amount of hell breaks loose. The WebViews seem to struggle with basic state changes in many HTML elements when they occur after the first draw. This bug manifests in the following two ways:

  • Blank Screen: The widget goes completely blank intermittently.
  • Double Draw: The new visual state of the element draws over the old visual state of the element.

If you experience this problem with your widget, the solution is to use chameleon.invalidate(). If you follow the link, an example of how to overcome this problem is provided.

We will continue to search for other less intrusive and hacky solutions to this bug.

Select Lists: They crash Chameleon

There is a well known bug in Android Web Views that forces us to choose between proper memory management, or automatic handling of native windows spawned by a web view. We chose the memory management, and decided to handle all native window spawning ourselves. While this should be straightforward, it's not, because Web Views don't let us intercept a request to spawn a native window generated by a select list.

A select list is a normal HTML <select> element that contains a sub list of <option> tags. When tapping these controls in Chameleon, the application will crash. As of now, there's now way around this, and to deal with it, we have implemented an alternative.

Here is an example of how a select list is normally defined:

<select id="carlist" >
  <option value="volvo">Volvo</option>
  <option value="saab">Saab</option>
  <option value="mercedes">Mercedes</option>
  <option value="audi">Audi</option>
</select>

Instead, define it for Chameleon like so:

<a id="carlist" href="#" class="selectbox"></a>

We use jQuery to modify the a tag, and make it behave like a select list. To take advantage of our hack, you will have to include chameleon.jquery.js and utilize the .chameleonSelectList() function.