Home  >  Development  >  features

AIR Data Synchronization via LiveCycle Data Services ES 2.6

AddThis Social Bookmark Button

I have long said offline applications are one of Adobe AIR's greatest abilities/features. It isn't numero uno but, in my book, it is definitely in my top 5 feature list. Doing better offline sync with database data was made easier with the addition of SQLite into the AIR runtime versus using XML, shared objects, or some of mechanism. Now Adobe has taken it one step further and made it so simple we are going to create a complete offline/online application in 30 minutes or less.

With LiveCycle Data Services ES 2.6 ("LCDS"), Adobe has added native caching for AIR and the Flash Player. This is an amazing advancement due to the small amount of code required to complete offline synchronization compared to a custom solution. The goal of this article is to provide a start to finish guide to building an AIR application with offline data synchronization from LCDS. I am a tactile learner and I like to write and present this way so instead of chatting...let's code!

Note: As of this article publication date, LCDS is presently in a Beta 2 phase so the following code and screenshots could be different in the final release.

Prerequisites: * Adobe Flex Builder 3 - http://www.adobe.com/go/flex
* Adobe AIR runtime - http://www.adobe.com/go/air
* LiveCycle Data Services ES 2.6 Beta 2 installer - http://labs.adobe.com/technologies/livecycle_dataservices2_6/
* Basic knowledge of Adobe AIR, Flex, and LiveCycle Data Services ES

Installing LCDS

The first step to building your application is to download and install LCDS. Download LCDS from Adobe Labs and start the installer. After the installer initializes you will come to the Welcome screen. Click Next. The next two screens (License Agreement and Serial Number) are normal screens as well so click Next through these. If you have a license, feel free to enter it in Step 3. Leaving the serial number blank will install the Express version, which is what we will use. Step 4, Installation Location, is completely up to you. The default is "c:\lcds" but I prefer putting all server software in a base "servers" folder so I use "c:\servers\lcds" (with a potential version number on the "lcds" folder). Step 5, Installation Options, is pertaining to your deployment. If you select "LiveCycle Data Services with Tomcat" an instance of Tomcat will be included in the install. If you do not have JRun, Tomcat, or another Java application server available, select this option. If you do have another Java application server and want to install LCDS there, use the "LiveCycle Data Services J2EE web application" option. For the purposes of this article, we will use "LiveCycle Data Services with Tomcat". On Step 6, Pre-Installation Summary, all of your selected options are listed. This is the last step before installing so double check your install path, etc before clicking Install.

Once you are comfortable with your settings and have clicked Install, the install will begin, complete, and provide you with Step 8, Installation Complete (see Figure 1). You can uncheck "Launch readme file?", unless you actually read "readme" files, and click Done. We are ready to start LCDS.

InstallerComplete.jpg
Figure 1 - Last step in install process.

Starting LCDS

Figure 1 shows the url to the "lcds-samples" web application (http://localhost:8400/lcds-samples). Before you go there, we have to start LCDS and the sample database. To do so run the following batch files, in order: [LCDS ES install]\sampledb\startdb.bat, [LCDS ES install]\tomcat\bin\startup.bat. "startdb.bat" will open a command prompt window and start the HSQL sample databases (see Figure 2). "startup.bat" will start an instance of Tomcat (see Figure 3) on port 8400. You can minimize both windows as we won't need them anymore. Do not close them though. If you close them they will stop running.

Note: Keep in mind this is for a development environment. On a production machine LCDS would run inside your production Java application server, which would already be started.

StartLCDSDatabase.jpg
Figure 2 - Command Prompt for "startdb.bat" (sample databases).
TomcatStartup.jpg
Figure 3 - Command Prompt for "startup.bat" (Tomcat).

LCDS is now alive and well, barring no error messages in Figure 3. Open your browser and browse to: http://localhost:8400/. The LCDS welcome page has links to the new, and may I add very helpful, Console Application (see Figure 4), the Samples Application (a place to view some of the things you can accomplish with LCDS), and a Template Application (useful for starting a new application). Feel free to browse the links for a bit before moving on. I highly suggest looking at the Samples Application. You can really get a solid view of what LCDS offers.

LCDSConsoleApp.jpg
Figure 4 - The new Console Application in LCDS.

LCDS is installed, started, and ready to be used. It is time to write our application. Instead of focusing on the backend Java code we're going to use the sample CRM backend (http://localhost:8400/lcds-samples/#crm) but build our own application.

Setting up the Project

The setup process is the same as any normal AIR project. Open Flex Builder 3 and go to File->New->Flex Project. The initial screen will ask you for the project type (Web or Desktop), choose Desktop, give the project a name of your liking, and click Next. At this point you can click Finish. The AIR project has been created and we are ready to code.

Before we start writing code, we do need to add a few SWC files. Copy the following files in your projects /lib directory: [LCDS ES install]\tomcat\webapps\lcds\WEB-INF\flex\locale\en_US\fds_rb.swc, [LCDS ES install]\tomcat\webapps\lcds\WEB-INF\flex\libs\fds.swc, and [LCDS ES install]\tomcat\webapps\lcds\WEB-INF\flex\libs\air\airfds.swc. These files have the classes necessary to integrate with LCDS and "airfds.swc" provides the sweet Adobe AIR support for local caching.

Note: If you are in a different locale, substitute "en_US" with whatever locale folder contains the fds_rb.swc.

Note: The swc’s could have been added in the project creation step from the last screen in the wizard.

The Project

If you haven't felt the proper level of geekdom yet, stay tuned. We are about to build a desktop application capable of caching our content offline and manage online synchronization in under 130 lines of code! Oh, imports and some comments are included in the 130 as well. We will examine each code block individually before tie everything together.

User Interface

We have an empty project with three swc files included. Our first step is to create the user interface (UI). Our UI will be a mix between the CRM Mini and the full CRM sample applications. The focus of the application is the data so we won't worry about prettying up the visuals.

<mx:VBox width="100%" height="100%">
	<mx:HBox>
		<mx:LinkButton label="+" click="showNewCompanyWindow()" />
		<mx:LinkButton label="-" enabled="{companiesGrid.selectedIndex >= 0}" click="deleteCompany()" />
	</mx:HBox>
	<mx:DataGrid id="companiesGrid" width="100%" height="100%" dataProvider="{companies}" />
</mx:VBox> 
Listing 1 - User Interface (Note: Full source download link at end of article)

Listing 1 is the core UI. The first row, HBox, contains the “+” and “-“ LinkButtons with a DataGrid in the second row of the VBox. This is as visual as we will get for this application (see Figure 5). Ignore the click events and the dataProvider for companiesGrid for now. Keep in mind we will tie this all together as we move on.

Note: Take a look at the full CRM to see a much fuller UI for managing the data.

UI.jpg
Figure 5 - Our simple UI in Flex Builder Design View.

Application Initialization

The application initialization is the core piece to our app (see Listing 2) and is where we setup our connection to LCDS. Initially we create three variables. contacts is the holder our DataService will fill. companyDS is the beast of the party. It is the DataService and will handle all of the communication between our application and the server. monitor is a URLMonitor we will use to know when the LCDS server is online or offline. There is a lot more to finding out if a user is online or offline though. For a true offline/online experience you want to listen for a NETWORK_CHANGE event as well as use a monitor but we're keeping it simple.

We are listening for the applicationComplete event, on the WindowedApplication, to initialize our application in the init() function. Our first order of business is to create the DataService and set the destination to "crm-company". The next line sets the cacheID. cacheID is completely up to you, must be unique, is required, and can be changed on the fly. Although for this application we don't do anything with the events, the event listeners are added to keep runtime errors away and for monitoring in debug mode. You can use these events to handle faults and message events from the server as wella s listen for data conflict events.

Note: When the cacheID is changed on the fly, the cache is cleared. The in-memory data is still there for your manipulation though.

In order to connect to LCDS you have to specify the url to the server and the connection type. For our use we're going to connect using RTMP to our local server (localhost) over port 2037, the default port. To do so we create an RTMPChannel, a ChannelSet, add the RTMPChannel to the ChannelSet, and set the companyDS.channelSet to our custom ChannelSet. At this point we are ready to connect to LCDS. The last step in our initialization is to setup our URLMonitor to watch the LCDS server. Again, this is only for development.

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
						applicationComplete="init()">
	<mx:Script>
		<![CDATA[  
{...imports go here...} 
 		[Bindable]
		private var companies:ArrayCollection = new ArrayCollection();			
		private var companyDS:DataService;
			
		private var monitor:URLMonitor;
			
		private function init():void{
 			companyDS = new DataService("crm-company");
 			companyDS.cacheID = "insideRIATest";
 				
 			companyDS.addEventListener(MessageEvent.MESSAGE, handleDSMessage);
 			companyDS.addEventListener(DataServiceFaultEvent.FAULT, handleServiceFault);
 			
 			/*
 			Establish DS channel
 			*/
 			var channel:RTMPChannel = new RTMPChannel("my-rtmp", "rtmp://localhost:2037");
 			var channelSet:ChannelSet = new ChannelSet();
 			channelSet.addChannel(channel);
 			companyDS.channelSet = channelSet;
 				
 			/*
 			init network monitor
 			We're just going to monitor the LCDS server
 			*/
 			monitor = new URLMonitor(new URLRequest("http://localhost:8400"));
 			monitor.addEventListener(StatusEvent.STATUS, handleMonitorStatus);
 			monitor.start();
 		}  
{...rest of mx:Script code goes here...} 
		]]>
	</mx:Script> 
   {...ui goes here...} 
</mx:WindowedApplication>
Listing 2 - Application initialization (Note: Full source download link at end of article)

Putting it All Together

The application is initialized and we're ready to tie the UI to the data. The most meat of Listing 3 is the handleMonitorStatus event handler. This function sets autoSaveCache, autoCommit, and autoConnect. Stay with me here because you might think I'm telling a lie...but I'm not. To save your data to a local SQLite database, all you have to do is set autoSaveCache to true. Seriously, that is it. I didn't want to believe it either but our application data will automatically save the data locally without us manually making a database, messing with SQL syntax, etc. You can toggle this to control when you want data saved locally or you can also manually save data by using the saveCache function on the DataService as well as clear the cache using the clearCache function.

autoCommit tells the DataService to commit the pending changes to the server immediately, instead of queuing them for later. One thing to understand is this will try a commit instantly and will throw an error if the user is offline. You could watch for this error and call saveCache(...) instead of toggling autoSaveCache, if you wanted granular control over the cache. autoConnect is similar to autoCommit. When true the DataService will try to connect to the service and throw an error if a connection cannot be established. The only other thing we do in handleMonitorStatus is set the status bar to "Online" or "Offline (saving updates locally)" then call the getData function to load/refresh the data. Keep in mind handleMonitorStatus is toggling the DataService from online to offline, or vice versa, so calling getData will tell the companyDS to fill the companies ArrayCollection with data. Well, if we're offline, where does the DataService get the data? If we are online fill(..) will get the data from the server and from the local cache when offline. Since our DataGrid uses databinding for the dataProvider it will be updated automatically so we don’t have to worry about handling results from the fill() call. It means we do not have to write code for retrieving data online versus offline (ie - getRemoteData and getLocalData).

private function getData():void{
 	companyDS.fill(companies);
}

private function handleMonitorStatus(event:StatusEvent):void{
 	trace("monitor status: " + monitor.available);
 	companyDS.autoSaveCache = monitor.available;
 	companyDS.autoCommit = monitor.available;
 	companyDS.autoConnect = monitor.available;
 	
 	status = monitor.available ? "Online" : "Offline (saving updates locally)";
 	
 	getData();
}

private function showNewCompanyWindow():void{
 	var window:CompanyView = new CompanyView();
 	window.alwaysInFront = true;
 	window.open(true);
 	
 	enabled = false;
 	
 	window.addEventListener("save", handleSave);
 	window.addEventListener(Event.CLOSE, handleWindowClose);
}

private function handleSave(event:Event):void{
 	var window:CompanyView = CompanyView(event.target);
 	var newCompany:Company = window.company;
 	
 	if(!companies.contains(newCompany)){
  		companies.addItem(newCompany);
  	}else{
  		//update the company or alert the user of a duplicate
  	}
 	
 	window.close();
}
private function handleWindowClose(event:Event):void{
 	enabled = true;
}

private function handleDSMessage(event:MessageEvent):void{
 	//trace(event.message);
}

private function handleServiceFault(event:DataServiceFaultEvent):void{ 
 	trace("fault", event.message);
}

private function deleteCompany():void{
 	companies.removeItemAt(companiesGrid.selectedIndex);
} 
Listing 3 - Remaining mx:Script block (Note: Full source download link at end of article)

Take a look at Listing 1 again. You can see the "+" button has a click handler which calls showNewCompanyWindow(). This function creates a new application window based on the CompanyView component (see Listing 4). This is a simple MXML component taken from the full CRM sample app and tweaked to work in our application. The only purpose of this window is to allow a user to create a new company, click Save, and dispatch an event. There is a public variable named company which is used to store the data using databinding. In reverse the form is bound to the company variable so we could use this same form to allow for edits to a company. Edits are not integrated, at this point, but you could easily implement edit capabilities. This window also doesn’t validate the form. In a real application, the saveCompany() function would validate the form before dispatching the “save” event or validation would be done on the fly using a poka-yoke approach (http://rhjr.net/theblog/2007/04/21/poke-and-hope-and-poka-yoke/).

Let’s look at the flow for creating a new company. The “+” button is clicked and showNewCompanyWindow() is opened with an event listener added for the “save” and “close” events. For visual purposes we force the window to stay on the top and we disable the main application so it will fade out slightly, hopefully helping the user focus to the new window. Once the form is completed and the user clicks save, the handleSave function is called by way of the “save” event dispatched from the window. Once we have the user input, from the window.company variable, we do a check to see if the company exists in the companies ArrayCollection. If the company does not exist, simply add it to companies. The sweet part is when we’re online this information is sent to the server immediately, due to autoCommit being true. The sweeter part is when we’re offline, and/or when autoSaveCache is true, the information is automatically saved locally then when we get online again, and our URLMonitor triggers a handleMonitorStatus, the data is automatically sent to the server and all connected users are updated instantaneously.

<?xml version="1.0" encoding="utf-8"?>
<mx:Window xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:company="flex.samples.crm.company.*"
		   width="400" height="300" showGripper="true">
	<mx:Metadata>
		[Event(name="save")]
	</mx:Metadata>
	<mx:Script>
		<![CDATA[
			[Bindable]
        	private var industries:Array = ["Computers", "Health Care", "Manufacturing", "Textiles"];
        	
        	private function saveCompany():void{
         		dispatchEvent(new Event("save"));
         	}
		]]>
	</mx:Script>
	
	<mx:Binding source="companyName.text" destination="company.name"/>
	<mx:Binding source="address.text" destination="company.address"/>
	<mx:Binding source="city.text" destination="company.city"/>
	<mx:Binding source="state.text" destination="company.state"/>
	<mx:Binding source="zip.text" destination="company.zip"/>
	<mx:Binding source="companyIndustryCombo.selectedItem as String" destination="company.industry"/>
	
	<company:Company id="company" />

	<mx:Form width="100%" height="100%"  backgroundColor="#e6e6e6" id="companyForm">
	    <mx:FormItem label="Company Name" required="true">
	        <mx:TextInput id="companyName" width="250" text="{company.name}"/>
	    </mx:FormItem>
	    <mx:FormItem label="Address">
	        <mx:TextInput id="address" width="250" text="{company.address}"/>
	    </mx:FormItem>
	    <mx:FormItem label="City">
	        <mx:TextInput id="city" width="250" text="{company.city}"/>
	    </mx:FormItem>
	    <mx:FormItem label="State">
	        <mx:TextInput id="state" width="250" text="{company.state}"/>
	    </mx:FormItem>
	    <mx:FormItem label="Zip">
	        <mx:TextInput id="zip" width="250" text="{company.zip}"/>
	    </mx:FormItem>
	    <mx:FormItem label="Industry">
	        <mx:ComboBox id="companyIndustryCombo" dataProvider="{industries}" selectedItem="{company.industry}" width="250"/>
	    </mx:FormItem>
	</mx:Form>
	
	<mx:HBox>
		<mx:Button label="Save" click="saveCompany()" />
	</mx:HBox>
</mx:Window>
Listing 4 - CompanyView.mxml (Note: Full source download link at end of article)

The last UI functionality we incorporate is deleting an item. Listing 1 shows the “-“ button, when enabled, calls the deleteCompany function. In here we have another small piece of code that merely removes an item based on the selected index in the companiesGrid. The same applies here as it did with adding companies to companies. When online, it deletes the company online and from the cache. When offline, it deletes the company from the cache then pushes the delete request online when capable.

The Proof is in the CTRL+F11

How do we test? I’m glad you asked. Run the application from Flex Builder (CTRL+F11). Once it opens it will grab the latest data from the server, remember we have LCDS already open. Open the CRM Mini or full CRM, if you one isn’t already open. Feel free to test the application by deleting items or adding items. You will see the data updates instantly show up on the CRM you opened. Make edits in the CRM and you will see the application update as well. Sweet, huh? Yes, but that’s not all.

To see the magic, go to your Tomcat window and press CTRL+C. This will shutdown Tomcat and close the window. Go back to the application and manipulate the data (add or delete). You’ll see the grid update just as if you were online but the browser version of the CRM will not update because there is no server to communicate with and, if you created a company, the companyId will be 0. Start Tomcat. Sit back and just watch. Once the URLMonitor realizes the status of http://localhost:8400 is available and the “auto” properties are set to true, in handleMonitorStatus, all of your updates will be sent directly to the server and you will receive any new server updates. It is pretty amazing to watch your app take off and sync without you having to click a sync button or manually implement some sync logic, right? I know. Give the LCDS team a hug when you see them.

WorkingApp.jpg
Figure 6 - The working application.

It really feels like we should have to write more code or at least have more meetings about our synchronization approach, right? It is important to point out the small amount of code needed to add sync abilities to our app and the majority of the code is application code, meaning code pertaining to application functionality. In order to implement offline storage for this application we simply adjust three properties (autoSaveCache, autoCommit, and autoConnect) and call addItem or removeItemAt on our ArrayCollection to add or remove items. Within 11 lines of code, taking only what the DataService requires for connection and synchronization, the application is 100% offline enabled. Wow, this is powerful!

Next Steps

The very next thing to do is to get started. Take those 11 lines of code and update your application or start building a new one. Adobe has done the hard part. They took care of managing the cache and synchronization so all we, as developers, have to focus on is our application functionality.

A couple things we did not focus on within this article are conflict management and cache clearing. Seeing as we’re using LCDS, conflict management comes native. You can add a listener for a conflict event and manage them accordingly. The full CRM sample includes complete conflict management which you can use as a guide. As noted previously, a true offline experience would entail also listening for network events. Once you add the ability to edit companies, manage conflicts, and truly detect the user’s network state you can have a very powerful and capable application. One of the biggest things to know is this same offline capability is available within browser-based Flex application using shared objects. Sweet, huh? I know.

Thanks LCDS Team!

Resources

• Full Source Code for this article Download Source

• Adobe Labs: http://labs.adobe.com/technologies/livecycle_dataservices2_6/

• LCDS 2.6 Developer’s Guide: http://download.macromedia.com/pub/labs/livecycle_datservices/2.6/docs/lcds26_devguide_040908.pdf

• John C. Bland II (me), Geek loving LCDS: http://www.johncblandii.com

• Tom Jordhal, LCDS and BlazeDS team member: http://tjordahl.blogspot.com

• Christophe Coenraets, Senior Technical Evangelist : http://coenraets.org

• James Ward, RIA Cowboy: http://www.jamesward.org

About Author

John C. Bland II is founder of Katapult Media Inc. which focuses on software and web development using technologies such as ColdFusion, the Flash Platform, PHP, and the .NET Platform. Through Katapult, he works diligently on custom software and web products. As the manager of the Arizona Flash Platform User Group, John continues put back into the community which helped mold him into the developer he is today. John blogs regularly on his Geek Life blog: www.johncblandii.com.

Comments

7 Comments

FYI: LCDS 2.6 is public now. Be sure to check the latest docs for any changes in code.

http://www.adobe.com/products/livecycle/dataservices/

Also, another great resource on this topic is Christophe's InSync app: http://coenraets.org/blog/2008/05/insync-automatic-offline-data-synchronization-in-air-using-lcds-26/#more-80.

Manga Chizen said:

what the...

This is why people ignore the word LiveCycle when it comes to technology discussions..

Reading this end to end is like watching someone write 215 lines of code to output a button.

Adobe LDS = Epic Fail. Show me tools or move on..

Haha...wow Manga. I've never heard of LCDS (or LDS; j/k) referenced as an Epic Fail. :-)

Questions for ya:
What are your real gripes about LiveCycle?
What tools are you looking for specifically?

Let's open the discussion. Adobe is listening.

Lukas Ruebbelke said:

@Manga
what the...

Output a button? Are you kidding me? Did you even read the tutorial and work the code? I did and ended up with something just a TINY bit more useful than a button.

Show me a real gripe or move on..

Call me Labomba said:

Any chance of showing an LCDS installation on Java Application Server/Glassfish? I can't seem to find an article on this particular J2EE anywhere...

@Labomba
Have you tried installing it as a WAR then deploying it? I have no Glassfish experience so I couldn't tell you exactly but LCDS can install as a WAR.

Sky said:

Hi John,

Thanks for the example. I am trying to modify your example to work with a current app. that I'm working on. Before I spend too much time in this project, would you be able to tell me if what I want to do is possible?

The current project makes RPC to a ColdFusion backend which queries a database and returns the information. The goal is to make this application offline capable. So, if the user loses temporary Internet connectivity, the application will still run... and then when connectivity is restored, the data would automatically sync up.

It sounds like LCDS is a good solution for this problem, however, since the current app does not use LCDS would I have to change everything over to LCDS in order to make this workable? Or can I use RPC when there's Internet connection, and then switch to DataServices when the Internet is down?

Also, the remote DB is quite large ... with hundreds of tables. Will LCDS cache the whole DB locally? Would it cache the data retrieved? Or just the changed data?

Thanks.

Leave a comment


Type the characters you see in the picture above.

Poll: ECMAScript Reaction

The ECMA organization recently decided to stop work on ECMAScript 4 and begin a new version, tentatively described as ES "Harmony." How would you like to see this affect the evolution of ActionScript?

Vote | View Poll Results | Read Related Blog Entry

Tag Cloud

Related Books

Development Series

Get an overview of the tools and technologies that work together to allow developers to build Rich Internet Applications (RIAs) quickly and easily.

Anatomy of an Enterprise Flex RIA