Top Banner
20 ISUG TECHNICAL JOURNAL Getting Started with Offline-Enabled Android Applications By Mark Gearhart, SAP NS2 Mark Gearhart is a Senior Consultant with SAP. He is involved in the development of database architectures and rich internet applications for temporal and realtime systems. His background encompasses the energy and financial industries with a special interest in data storage architectures and fast user interfaces. He can be reached at mark. [email protected]. Offline-enabled mobile applications are very useful when you venture into remote areas of the world, far away from data centers and internet connections. In this article, Mark Gearhart, a Senior Consultant at SAP, describes a database test of the Sybase Unwired Platform and a native Android application designed to operate offline. MOBILITY A ll I really wanted to do was to create a mobile Android application that could read from an Enterprise Information System (EIS) data- base and display the result on the device screen. That’s not a big effort using the Sybase Unwired Platform. After all, that’s what their Hybrid Web Containers were designed for, and it takes just a couple minutes to build this type of application. However, I also wanted to retain the data on the device and display it even if the internet or EIS were down. This is a significant requirement, and causes things to get complicated quickly. In a review of recent mobility re- quests, I have noted a frequent require- ment for the ability to operate in this offline-enabled mode. Fortunately, the Sybase Unwired Platform supports both offline-enabled and online-only appli- cation types. Thanks to this, enterprises can choose the architecture that works best for the application, rather than having to force all application require- ments into a one-size-fits-all model. Offline-Enabled Applications Offline-enabled applications are built around the Sybase Unwired Platform’s Mobile Business Object data synchro- nization capabilities, with a native client that runs on the mobile device. This type of offline-enabled architec- ture is best suited to generally complex applications, which someone might use to do the vast majority of their day-to- day work. Examples are Mobile CRM Sales, Mobile Field Service, and ap- plications for people whose entire day is generally spent away from the office or away from the internet. There are two features of the native architecture that make it ideal for this: the native client, and the offline ca- pabilities of data synchronization. The native client works best for complex, multi-screen applications. Browsers are not well suited for this type of com- plexity and rich UI requirements. The downside of having to write a new UI for each platform is often irrelevant, as these types of applications are often associated with company-provided devices, rather than expected to run on employee-owned smartphones. The other feature is offline enable- ment. With data synchronization, a copy of the relevant data is stored on a mobile database on the client device, so that updates can be made while there is no connection. Real-time queries are supported in an offline mode because the data is stored locally, ready to be synchronized when the connection is established again. Offline enablement is important from a cost perspective. In many
9
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Native 2

20 isug technical journal

Getting Started with Offline-Enabled Android Applications

By Mark Gearhart, SAP NS2

Mark Gearhart is a Senior Consultant with SAP. He is involved in the development of database architectures and rich internet applications for temporal and realtime systems. His background encompasses the energy and fi nancial industries with a special interest in data storage architectures and fast user interfaces. He can be reached at [email protected].

Offl ine-enabled mobile applications are very useful when you venture into remote areas of the world, far away from data centers and internet connections. In this article, Mark Gearhart, a Senior Consultant at SAP, describes a database test of the Sybase Unwired Platform and a native Android application designed to operate offl ine.

MOBILITY

A ll I really wanted to do was to create a mobile Android application that

could read from an Enterprise Information System (EIS) data-base and display the result on the device screen. That’s not a big effort using the Sybase Unwired Platform. After all, that’s what their Hybrid Web Containers were designed for, and it takes just a couple minutes to build this type of application. However, I also wanted to retain the data on the device and display it even if the internet or EIS were down. This is a significant requirement, and causes things to get complicated quickly.

In a review of recent mobility re-quests, I have noted a frequent require-ment for the ability to operate in this offline-enabled mode. Fortunately, the Sybase Unwired Platform supports both offline-enabled and online-only appli-cation types. Thanks to this, enterprises can choose the architecture that works best for the application, rather than having to force all application require-ments into a one-size-fits-all model.

Offline-Enabled ApplicationsOffline-enabled applications are built around the Sybase Unwired Platform’s Mobile Business Object data synchro-nization capabilities, with a native

client that runs on the mobile device. This type of offline-enabled architec-ture is best suited to generally complex applications, which someone might use to do the vast majority of their day-to-day work. Examples are Mobile CRM Sales, Mobile Field Service, and ap-plications for people whose entire day is generally spent away from the office or away from the internet.

There are two features of the native architecture that make it ideal for this: the native client, and the offline ca-pabilities of data synchronization. The native client works best for complex, multi-screen applications. Browsers are not well suited for this type of com-plexity and rich UI requirements. The downside of having to write a new UI for each platform is often irrelevant, as these types of applications are often associated with company-provided devices, rather than expected to run on employee-owned smartphones.

The other feature is offline enable-ment. With data synchronization, a copy of the relevant data is stored on a mobile database on the client device, so that updates can be made while there is no connection. Real-time queries are supported in an offline mode because the data is stored locally, ready to be synchronized when the connection is established again.

Offline enablement is important from a cost perspective. In many

Page 2: Native 2

august 2012 21

getting started with offline-enabled android applications

Figure 1: Reference Material

emerging markets, data plans are incredibly expensive. Enterprises in those areas would much rather update all relevant data in the warehouse or office using corporate WiFi at the beginning and end of the day, and limit any online usage to situations that legitimately require real-time lookups. There is no point spending data plan money on reloading the customer’s address again.

Offline enablement is also important so that one can move freely. Even in the most high-tech environments, ships at sea or troops on deployment will not be in contact with a base that contains the EIS where the data resides. Not only is connectivity disabled due to geographic distances, it is disabled for security. For these situations, having access to downloaded information on the device obviates the need to remain connected.

Online-Only ApplicationsBy contrast, the Sybase Unwired Platform also supports online-only applications. I won’t spend any time with it for this article other than to say that online-only applications connect directly to the EIS. There is no local database on the device, and hence, no offline capabilities. This architecture is best suited to lightweight mobile applications that normally involve some simple task or business process. Dashboards that get “up-to-the-minute” enterprise information, and business-approval requests are typical. The UI design is not complex.

The online-only application runs inside a Hybrid Web Container, and is based on HTML5 and Javascript. The container handles the configuration and display of the ap-plication for the specific device OS. This is a great thing. The application itself is managed once, on a single codeline; and there is no need for multiple front ends for iOS, Android, etc. This is ideal for environments where both Android and iOS must be supported. Unfortunately, this also means that a Hybrid Web Container based application, which would benefit from a very nice UI, cannot use widgets specific to Android or iOS. The application must use the lowest com-mon denominator. A native application has no such widget restriction because it is built for a specific operating system. Even though the nicest looking and most capable applica-tions tend to be native applications, they also take much longer to build since they require low-level coding, as we are about to see.

Let’s Build an Offline-Enabled ApplicationTo prove that a offline-enabled application works, we will build a very simple application that (a) creates an Sybase

UltraLite database on the device, (b) opens up a database connection to the Sybase Unwired Server, and (c) down-loads data from the EIS data source to the UltraLite database on the device. Then, when the EIS data source is unavail-able, we will verify that the UltraLite database still exists on the device and contains the downloaded data.

Since this is a database and synchronization test, we are not going to distract things with a user interface yet; this will be done later after we have a basic database framework in place.

The Sybase Unwired Platform architecture to support this type of application consists of a Customer MBO (Mobile Business Object) which resides in the Unwired Server and in the UltraLite database on the device. This MBO is the container which contains instances (rows) of the Customer table originating from the EIS, plus object queries such as fi ndAll, which we will use in our application. In our case, the EIS is just an SQL Anywhere database, although it could have been any of the SAP Business Suites or any other data source. Sybase provides the UltraLite database for the device as well as the Sybase Unwired Server which contains a cached copy of the MBO’s. Sybase also provides the Sybase Mobilink software needed to synchronize data from the EIS to the device.

Necessary Reference MaterialAfter you’ve downloaded the Sybase Unwired Platform from the Sybase Product Download Site at https://subscribenet.com, there are at least three references (see Figure 1) you will need if you are not already an expert in Android and the Sybase Unwired Platform – some SUP installation instruc-tions, a Sybase Unwired Platform Developers Guide, and a good Android reference manual:

Page 3: Native 2

22 isug technical journal

getting started with offline-enabled android applications

22 isug technical journal

getting started with offline-enabled android applications

Installing both the Sybase Unwired Platform and Android Development Kit is not trivial. It requires a lot of flipping back and forth between several installation guides and internet sites, all in the correct order. I wrote an installa-tion guide that captures, all in one spot, a method for install-ing the Sybase Unwired Platform version 2.1, ESD #3 for Android devices. The version and ESD are important, since the installation often changes significantly from one release to the next. This 28-page installation guide is included as a downloadable with the article at my.isug.com.

The Sybase Unwired Platform Developers Guide and an Android reference manual are also required. The De-velopers Guide contains the API documentation for things such as createDatabase() and synchronize(), and an Android reference manual is handy for understanding things like the Activity class and process threading. This kind of knowledge is required when developing native Android applications. Any good reference will work. Personally, I am using the book “Android 4 Application Development”, by Reto Meier, but there are many other good resources out there.

Defining the Android Activity LifecycleOf the four Android applica-tion components: Activities, Services, Content Providers, and Broadcast Receivers, we will be using Activities. An Activ-ity component is a single screen with a user interface. To ensure that Activities can react to state

changes, Android provides a series of event handlers that are fired when an Activity transits through its full, visible, and active lifetimes. Figure 2 summarizes these lifetimes in terms of Activity states.

A MainActivity.java class, which I have written and which extends the Activity class, is declared as the MAIN class with a LAUNCHER category in AndroidManifest.xml; therefore, it is executed when the application is launched. This MainActivity.java class contains all the Activity event handlers and you are free to override their default operations as desired.

For the simple application, we are concerned with the onCreate Activity. If we are going to automatically create a local device database and synchronize it from a remote data source, it would happen here. If we were going to maintain synchronization with the remote data source throughout the activity’s lifecycle, we would also synchronize within the onRestart Activity. There are also other opportunities for syn-chronization, such as when a user updates data on the device.

As shown in Figure 3, when the application is launched we present an action screen to test the creation and syn-chronization of a local database from the EIS data source.

Activity.onCreate

Activity.onRestoreInstanceState

Activity.onStart

Activity.onResume

Activity.onSaveInstanceState

Activity.onPause

Activity.onStop

Activity.onDestroy

Activity.onRestart

Active Lifetime

Visible Lifetime

Full Lifetime

Figure 2: Application Lifecycle

Figure 3: Our Simple Application

Page 4: Native 2

getting started with offline-enabled android applications

deceMber 2011 - januarY 2012 pb

august 2012 23

MO

BILIT

Y

We define a “Start Application” button within the onCre-ate Activity. We also define a “Get Status” button to dump the current status of the application. This scenario might be useful if you want to create an initial entry screen, and then initiate further actions when buttons are pushed. For another type of application, we could automatically callup a UI view to display local data when it becomes available rather than clicking a button to start the process. This can also be coded if desired.

The Sybase Unwired Platform documentation goes to great lengths to explain all the methods for tracing the execu-tion of code. And rightly so. There are a million moving parts to a mobile application, with potential errors in both the network and the servers all the way from the EIS down to the device. Therefore, we should likewise go to great lengths in setting up and enabling the supplied Callback Listeners.

Setting up Callback ListenersCallback listeners are provided so your application can moni-tor changes and notifications from Sybase Unwired Platform. When your application starts, it can register an application callback listener, a database callback listener and a synchro-nization status listener. These APIs provide notifications on an operation’s success or failure, or changes in data. Our three listeners are coded as follows.

The Application ListenerThe com.sybase.mobile.ApplicationCallback class is used for monitoring changes to application settings, messaging con-nection status, and application registration status:

private class MyApplicationCallback implements ApplicationCallback {

@Override public void onApplicationSettingsChanged(StringList nameList) { Log.i(DEBUG_TAG, "MyApplicationCallback.onApplicationSettingsChanged"); } @Override public void onConnectionStatusChanged(int connectionStatus, int errorCode, String errorMessage) { Log.i(DEBUG_TAG, "MyApplicationCallback.onConnectionStatusChanged"); showConnectionStatus(0,connectionStatus); }

@Override public void onDeviceConditionChanged(int condition) { Log.i(DEBUG_TAG, "MyApplicationCallback.onDeviceConditionChanged, condition = (" + condition + ")"); }

@Override public void onRegistrationStatusChanged(int registrationStatus, int errorCode, String errorMessage) { Log.i(DEBUG_TAG, "MyApplicationCallback.onRegistrationStatusChanged, error= ( " + errorMessage + ")"); showApplicationStatus(0,registrationStatus); }

@Override public void onHttpCommunicationError(int errorCode, String errorMessage, StringProperties httpHeaders) { Log.i(DEBUG_TAG, "MyApplicationCallback.onHttpCommunicationError, error= ( " + errorMessage + ")"); }}

The Database ListenerThe com.sybase.persistence.CallbackHandler class is used to monitor notifications and changes related to the database. To register callback handlers at the package level, you can use the registerCallbackHandler method in the generated database class. To register for a particular MBO, use the registerCall-backHandler method in the generated MBO class. In our case, we have registered the callback handler for the entire database:

private class MyDatabaseCallback extends DefaultCallbackHandler {

@Override public int onSynchronize(GenericList<SynchronizationGroup> groups, SynchronizationContext context) {

Log.i(DEBUG_TAG,"MyDatabaseCallback.onSynchronize");

String debugString; switch(context.getStatus()) { case SynchronizationStatus.ASYNC_REPLAY_UPLOADED: debugString="ASYNC_REPLAY_UPLOADED"; break; case SynchronizationStatus.ASYNC_REPLAY_COMPLETED: debugString="ASYNC_REPLAY_COMPLETED"; break; case SynchronizationStatus.DOWNLOADING: debugString="DOWNLOADING"; break; case SynchronizationStatus.ERROR: debugString="ERROR"; break; case SynchronizationStatus.FINISHING: debugString="FINISHING"; break; case SynchronizationStatus.STARTING: debugString="STARTING"; break; case SynchronizationStatus.STARTING_ON_NOTIFICATION: debugString="STARTING_ON_NOTIFICATION"; break;

Page 5: Native 2

24 isug technical journal

getting started with offline-enabled android applications

24 isug technical journal

getting started with offline-enabled android applications

case SynchronizationStatus.UPLOADING: debugString"UPLOADING"; break; default: debugString = "Invalid SynchronizationStatus"; break; }

Log.i(DEBUG_TAG,"MyDatabaseCallback.synchronize status = " + context.getStatus() + " (" + debugString + ")");

return SynchronizationAction.CONTINUE; }}

The Synchronize ListenerThe com.sybase.persistence.SyncStatusListener class is used for debugging and performance measures when monitoring stages of a synchronization session, and can be used in the user interface to indicate synchronization progress:

private class MySyncStatusListener implements SyncStatusListener {

long start;

public MySyncStatusListener() { start = System.currentTimeMillis(); }

public boolean objectSyncStatus(ObjectSyncStatusData statusData) { long now = System.currentTimeMillis(); long interval = now - start; start = now; String infoMessage; int syncState = statusData.getSyncStatusState();

switch (syncState) { case SyncStatusState.SYNC_STARTING: infoMessage = "START [" + interval + "]"; break; case SyncStatusState.APPLICATION_SYNC_SENDING_HEADER: infoMessage = "SENDING HEADERS [" + interval + "]"; break; case SyncStatusState.APPLICATION_SYNC_SENDING_SCHEMA: infoMessage = "SENDING SCHEMA [" + interval + "]"; break; case SyncStatusState.APPLICATION_DATA_UPLOADING: infoMessage = “DATA UPLOADING [" + interval + "] " + statusData.getCurrentMBO() + ": (S>" + statusData.getSentByteCount() + ":" + statusData.getSentRowCount() + " R<" + statusData.getReceivedByteCount() + ":" + statusData.getReceivedRowCount() + ")"; break; case SyncStatusState.APPLICATION_SYNC_RECEIVING_UPLOAD_ACK: infoMessage = "RECEIVING UPLOAD ACK [" + interval + "]"; break; case SyncStatusState.APPLICATION_DATA_UPLOADING_DONE: infoMessage = "UPLOAD DONE [" + interval + "] " + statusData.getCurrentMBO() + ": (S>" + statusData.getSentByteCount() + ":"

+ statusData.getSentRowCount() + " R<" + statusData.getReceivedByteCount() + ":" + statusData.getReceivedRowCount() + ")"; break; case SyncStatusState.APPLICATION_DATA_DOWNLOADING: infoMessage = "DATA DOWNLOADING[" + interval + "] " + statusData.getCurrentMBO() + ": (S>" + statusData.getSentByteCount() + ":" + statusData.getSentRowCount() + " R<" + statusData.getReceivedByteCount() + ":" + statusData.getReceivedRowCount() + ")"; break; case SyncStatusState.APPLICATION_SYNC_DISCONNECTING: infoMessage = "DISCONNECTING [" + interval + "]"; break; case SyncStatusState.APPLICATION_SYNC_COMMITTING_DOWNLOAD: infoMessage = "COMMITTING DOWNLOAD [" + interval + "] " + statusData.getCurrentMBO() + ": (S>" + statusData.getSentByteCount() + ":" + statusData.getSentRowCount() + " R<" + statusData.getReceivedByteCount() + ":" + statusData.getReceivedRowCount() + ")"; break; case SyncStatusState.APPLICATION_SYNC_CANCELLED: infoMessage = "SYNC CANCELLED [" + interval + "]"; break; case SyncStatusState.APPLICATION_DATA_DOWNLOADING_DONE: infoMessage = "DATA DOWNLOADING DONE [" + interval + "]"; break; case SyncStatusState.SYNC_DONE: infoMessage = "DONE [" + interval + "]"; break; default: infoMessage = "STATE" + syncState + "[" + interval + "]"; break; }

Log.i(DEBUG_TAG, "SyncListener (" + infoMessage + ")"); return false; }}

Showing Status on DemandIn addition to listening for status changes as they happen, we would also like to show the status of the system on demand.For example, we have placed a button called “Get Status” on the application screen. When pressed, it shows the status of the registration, connection, and database. To show status, we code up three classes which inspect the pertinent proper-ties of the application:

The showRegistrationStatus ClassEach device must register with the Server before establishing a connection. The documentation states that “the process defines the properties for the device and backend interaction with Unwired Server.” This is vague. What it really means is that devices must be registered via SUP Messaging before

Page 6: Native 2

The showConnectionStatus ClassThe Connection Profile stores information detailing where and how the local database is stored, including location and page size. The connection profile also contains UltraLiteJ runtime tuning values. The documentation states that “when the ConnectionStatus of CONNECTED has been reached and the application’s applicationSettings have been received from the server, the application is now in a suitable state for database subscriptions and/or synchronization.” So, a CON-NECTED state is the required state for transferring data to and from the server:

deceMber 2011 - januarY 2012 pb

getting started with offline-enabled android applications getting started with offline-enabled android applications

MO

BILIT

Y

august 2012 25

private void showRegistrationStatus(int step, int status) {

String debugString; switch (status) { case RegistrationStatus.UNREGISTERED: debugString = "UNREGISTERED"; break; case RegistrationStatus.REGISTERED: debugString = "REGISTERED"; break; case RegistrationStatus.REGISTRATION_ERROR: debugString = "REGISTRATION_ERROR"; break; case RegistrationStatus.REGISTERING: debugString = "REGISTERING"; break; case RegistrationStatus.UNREGISTERING: debugString = "UNREGISTERING"; break; default: debugString = "Invalid Registration Status"; break; }

Log.i(DEBUG_TAG, "(" + step + ") application status=" + status + " (" + debugString + ")");}

private void showConnectionStatus(int step, int status) { String debugString; switch (status) { case ConnectionStatus.CONNECTED: debugString = "CONNECTED"; break;

The showDatabaseStatus ClassFor a database status, we’d like to know if the local database exists. If so, we can dump the MBO instances from the Customer MBO stored in the database. The databaseExists function is perfect for this:

private void showDatabaseStatus(int step) {

if (TestDB.databaseExists()) { Log.i(DEBUG_TAG, "(" + step + ") Database exists");

// If database exists, list all Customer MBO instances Log.i(DEBUG_TAG, "Customer.fi ndAll");

data replication can take place. Every Messaging applica-tion is uniquely identified by it’s physical device ID, so this requires the application to register with the Server and send a device ID. After registration, the Server cross checks the de-vice ID every time a connection is made by the application. The SUP messaging engine communicates with the applica-tion by using an AppID + Activation Code + Device ID.

If the application is not registered, then no messages can flow. When the application is registered, you will see this as a REGISTERED status from showRegistrationStatus.

case ConnectionStatus.CONNECTING: debugString = "CONNECTING"; break; case ConnectionStatus.CONNECTION_ERROR: debugString = "CONNECTION_ERROR"; break; case ConnectionStatus.DISCONNECTED: debugString = "DISCONNECTED"; break; case ConnectionStatus.DISCONNECTING: debugString = "DISCONNECTING"; break; case ConnectionStatus.NOTIFICATION_WAIT: debugString = "NOTIFICATION_WAIT"; break; default: debugString = "Invalid ConnectionStatus"; break; }

Log.i(DEBUG_TAG, "(" + step + ") connection status=" + status + "(" + debugString + ")");}

Once the application is REGISTERED and CON-NECTED, you will see an “Online” status for the Applica-tion Connection in the Sybase Control Center as shown in Figure 4:

Figure 4: Sybase Control Center and Registration

My Android Device Status is “Online”

Page 7: Native 2

26 isug technical journal

getting started with offline-enabled android applications

Button startAppBtn = (Button) fi ndViewById(R.id.button1); startAppBtn.setOnClickListener(new View.OnClickListener() { public void onClick(View v) {

// Start a thread to create database and sync with SUP Thread thread = new Thread(createApplicationTask, "Background"); thread.start(); } });

// Here is a button to show the status Button showStatusBtn = (Button) fi ndViewById(R.id.button2); statusBtn.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (app != null) { showRegistrationStatus(1,app.getRegistrationStatus()); showDatabaseStatus(1); showConnectionStatus(1,app.getConnectionStatus()); } else { Log.i(DEBUG_TAG, "application is null"); } } }); }

@Override public void onRestart() { Log.i(DEBUG_TAG, "onRestart"); super.onRestart(); TestDB.synchronize(new MySyncStatusListener()); }

@Override public void onDestroy() { Log.i(DEBUG_TAG, "onDestroy"); super.onDestroy();

// Do not delete database. We want it to persist. // if (!TestDB.databaseExists()) { // TestDB.deleteDatabase(); //} }

@Override public void onResume() { Log.i(DEBUG_TAG, "onResume"); super.onResume(); } }

private Runnable createApplicationTask = new Runnable() { public void run() { Log.i(DEBUG_TAG, "createApplicationTask"); createApplication(); } };

private void createApplication() { try { Log.i(DEBUG_TAG, "createApplication"); TestDB.registerCallbackHandler(new MyDatabaseCallback()); TestDB.setApplication(app); TestDB.getSynchronizationProfi le().setServerName(HOST);

package com.example.testclient;

public class MainActivity extends Activity {

private static fi nal String DEBUG_TAG = "TRACE"; private static fi nal int TIMEOUT = 20; private static fi nal String HOST = "192.168.1.138"; private static fi nal String USERNAME = "supAdmin"; private static fi nal String PASSWORD = "wk60#ls"; private static fi nal int PORT = 5001;

Application app;

@Override public void onCreate(Bundle savedInstanceState) {

Log.i(DEBUG_TAG, "onCreate"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); app = Application.getInstance(); ApplicationCallback appCallback = new MyApplicationCallback(); app.setApplicationCallback(appCallback); app.setApplicationIdentifi er("Test22"); app.setApplicationContext(MainActivity.this);

// Here is a button to start the application

Putting it All TogetherThe sequence of code in MainActivity.java roughly follows the sequence in the Sybase Unwired Platform documenta-tion, although the documentation does seem to skip around. There are some variations in the calling sequences which I chose not to follow. For example, the local database is automatically created when needed by the Object API. This seems to hide logic that should really be exposed by an explicit createDatabase() call. Without explicitly creating the database, you are left wondering when a local database is available, as I was on several occasions.

The following code shows the onCreate Activity logic as well as the sequence of database generation and synchroniza-tion when the “Start Application” button is pressed:

GenericList<SUP101.Customer> customers = Customer.fi ndAll(); int n = customers.size(); Log.i(DEBUG_TAG, "Customer.fi ndAll returned (" + n + ") customers");

for (int i = 0; i < n; ++i ) { Customer customer = customers.get(i); Log.i(DEBUG_TAG, "Last Name = (" + customer.getLname() + ")"); }

} else { Log.i(DEBUG_TAG, "(" + step + ") Database does not exists"); }}

Page 8: Native 2

august 2012 27

getting started with offline-enabled android applications

august 2012 27

getting started with offline-enabled android applications

MO

BILIT

Y

We use the Log.i call to log client messages to the LogCat pane of the Unwired Workspace. The Unwired Workspace is the Eclipse-base IDE provided with the Sybase Unwired Platform and is used to build and deploy the Android-based mobile applications. When the Android Emulator runs, it transmits client messages to this pane.

The local database can also be deleted if no longer needed. Although we do not do this in the sample applica-tion because we’d like the local database to persist, we could call the deleteDatabase() function in the onDestroy activity if desired.

When the application is launched:

07-22 21:37:44.892: onCreate07-22 21:37:46.883: onActivityResult07-22 21:37:46.883: onResume

When the Start Application button is pressed:

07-22 21:39:21.154: createApplicationTask07-22 21:39:21.154: createApplication07-22 21:39:21.383: createApplication.registerApplication07-22 21:39:21.733: (0) application status=202 (REGISTERING)07-22 21:39:26.142: (0) connection status=102 (CONNECTING)07-22 21:39:26.202: (0) connection status=105 (DISCONNECTED)07-22 21:39:27.302: (0) connection status=103 (CONNECTED)07-22 21:39:34.602: (0) application status=203 (REGISTERED)07-22 21:39:34.612: createApplication.createDatabase07-22 21:39:35.152: createApplication.openConnection07-22 21:39:35.192: createApplication.synchronize07-22 21:39:35.222: MyDatabaseCallback.onSynchronize07-22 21:39:35.222: MyDatabaseCallback.synchronize status = 1 (STARTING)07-22 21:39:35.312: SyncListener (START [120])07-22 21:39:35.434: SyncListener (SENDING HEADERS [72])07-22 21:39:35.434: SyncListener (DATA UPLOADING [4])07-22 21:39:35.442: SyncListener (DATA UPLOADING [3])07-22 21:39:35.442: SyncListener (DATA UPLOADING [3])07-22 21:39:35.442: SyncListener (DATA UPLOADING [1])07-22 21:39:35.462: SyncListener (UPLOAD DONE [1] )07-22 21:39:35.752: SyncListener (RECEIVING UPLOAD ACK [287])07-22 21:39:37.352: SyncListener (DATA DOWNLOADING[1433])07-22 21:39:37.372: SyncListener (DATA DOWNLOADING[17])07-22 21:39:37.382: SyncListener (DATA DOWNLOADING[12])07-22 21:39:37.552: SyncListener (DATA DOWNLOADING[4])07-22 21:39:37.588: SyncListener (COMMITTING DOWNLOAD [35])07-22 21:39:37.732: SyncListener (DISCONNECTING [143])07-22 21:39:37.732: SyncListener (DONE [4])07-22 21:39:37.822: MyDatabaseCallback.onSynchronize07-22 21:39:37.822: MyDatabaseCallback.synchronize status = 6 (ASYNC_REPLAY_UPLOADED)07-22 21:39:37.822: (0) Database exists07-22 21:39:37.822: Customer.findAll07-22 21:39:37.833: Customer.findAll returned (3) customers07-22 21:39:37.833: Last Name = (Gearhart)

ConnectionProperties conn = app.getConnectionProperties(); LoginCredentials login = new LoginCredentials(USERNAME, PASSWORD);

conn.setLoginCredentials(login); conn.setServerName(HOST); conn.setPortNumber(PORT);

if (app.getRegistrationStatus() != RegistrationStatus.REGISTERED) { Log.i(DEBUG_TAG, "createApplication.registerApplication"); app.registerApplication(TIMEOUT);

} else { Log.i(DEBUG_TAG, "createApplication.startConnection"); try { app.startConnection(TIMEOUT); } catch (Exception e) { Log.i(DEBUG_TAG, "createApplication could not start connection"); } }

// Create the local database if it does not exist Log.i(DEBUG_TAG, "createApplication.createDatabase"); if (!TestDB.databaseExists()) { TestDB.createDatabase(); }

// Open the database connection Log.i(DEBUG_TAG, "createApplication.openConnection"); TestDB.openConnection();

// Synchronize and listen for status. This is a blocking call. Log.i(DEBUG_TAG, "createApplication.synchronize"); TestDB.synchronize(new MySyncStatusListener());

// When we get here, we should have a database which // contains data, so show it. showDatabaseStatus(1);

} catch (Exception e) { Log.i(DEBUG_TAG, "createApplication fatal error"); e.printStackTrace(); } } }}

Running the Application OnlineWhen the application is launched, activity starts with on-Create and ends with onResume. Then, the application waits for further input. When the “Start Application” button is pressed, the database creation and synchronization executes. At the completion of synchronization, we have local data-base results:

Page 9: Native 2

ConclusionDeveloping offline-enabled Android applications has been fairly straightforward, at least so far. I have found that there are three difficult parts of the development which have noth-ing to do with the object API’s or the concept of offline versus online.

The first is the installation of the product. This can be frustrating unless you have the benefit of trial and error be-hind you, or you have a very good installation guide. The sec-ond thing is the choice of which Android SDK to use. After much debugging, I discovered that the it was not possible to successfully register the application with the Sybase Unwired Platform when using the latest version 4.1 of the Android SDK. I did, however, manage to get everything working with version 2.2. Finding compatible versions of everything is an ongoing task since the pieces come from different vendors. The third thing is the threading model within Android. It wasn’t used much in this simple application, other then to execute the code for the “Start Application” button. If I had wanted to launch Alert Dialogs, or Toast messages, I would have coded these as threads.

As the UI takes shape and we find ourselves simultane-ously rendering screens, synchronizing data, and executing other tasks, threading will become very important. In the meantime, good luck getting started with your offline-enabled Android applications.

28 isug technical journal

getting started with offline-enabled android applications

For either mode of operation, the local database is avail-able and contains data as shown below:

When the application is offline:

07-23 09:56:51.422: (0) application status=203 (REGISTERED)07-23 09:56:51.422: (0) Database exists07-23 09:56:51.432: Customer.createApplication.findAll07-23 09:56:51.432: Customer.findAll returned (3) customers07-23 09:56:51.432: Last Name = (Gearhart)07-23 09:56:51.432: Last Name = (Jordan)07-23 09:56:51.432: Last Name = (Manthorpe)07-23 09:56:51.443 (0) connection status=101 (CONNECTION_ER-ROR)

When the application is online:

07-22 21:41:25.822: (0) application status=203 (REGISTERED)07-22 21:41:25.822: (0) Database exists07-22 21:41:25.832: Customer.findAll07-22 21:41:25.872: Customer.findAll returned (3) customers07-22 21:41:25.872: Last Name = (Gearhart)07-22 21:41:25.872: Last Name = (Jordan)07-22 21:41:25.872: Last Name = (Manthorpe)07-22 21:41:25.872: (0) connection status=103 (CONNECTED)

Getting the Application StatusHere are the results when the “Get Status” button is pressed. When the application is online, the application status is REGISTERED and the connection status is CONNECT-ED. When the application is offline, the application status is still REGISTERED and the connection status could be either CONNECTION_ERROR or CONNECTING.

When the Start Application button is pressed:

07-22 22:02:27.353: createApplicationTask07-22 22:02:27.353: createApplication07-22 22:02:27.399: createApplication.startConnection07-22 22:03:32.153: (0) connection status=101 (CONNECTION_ER-ROR)07-22 22:03:32.175: createApplication could not start connection07-22 22:03:32.175: createApplication.createDatabase07-22 22:03:32.175: createApplication.openConnection07-22 22:03:32.175: createApplication.synchronize07-22 22:03:32.193: MyDatabaseCallback.onSynchronize07-22 22:03:32.193: MyDatabaseCallback.synchronize status = 1 (STARTING)07-22 22:03:32.263: SyncStatusListener (START [88])07-22 22:04:29.192: (0) application status=203 (REGISTERED)07-22 22:04:29.192: (0) Database exists07-22 21:39:37.822: Customer.findAll07-22 22:04:29.222: Customer.findAll returned (3) customers07-22 22:04:29.222: Last Name = (Gearhart)07-22 22:04:29.222: Last Name = (Jordan)07-22 22:04:29.222: Last Name = (Manthorpe)07-22 22:04:29.222: (0) connection status=101 (CONNECTION_ER-ROR)07-22 22:06:41.492: MyDatabaseCallback.onSynchronize07-22 22:06:41.492: MyDatabaseCallback.synchronize status = 5 (ERROR)

When the application is launched:

07-22 21:37:44.892: onCreate07-22 21:37:46.883: onActivityResult07-22 21:37:46.883: onResume

Running the Application OfflineWhen the EIS is offline, the connection process produces an error as expected. We also get a synchronization error as expected. The local database, however, remained unaffected. Therefore, at the completion of the sequence, we still have local database results:

07-22 21:39:37.833: Last Name = (Jordan)07-22 21:39:37.833: Last Name = (Manthorpe)07-22 21:39:44.552: (0) connection status=105 (DISCONNECTED)07-22 21:39:45.122: (0) connection status=103 (CONNECTED)