Sie sind auf Seite 1von 9

MOBILITY

Getting Started with Offline-Enabled Android Applications


Ofine-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 ofine. 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 nancial industries with a special interest in data storage architectures and fast user interfaces. He can be reached at mark. gearhart@sap.com.
20 ISUG TECHNICAL JOURNAL

ll I really wanted to do was to create a mobile Android application that could read from an Enterprise Information System (EIS) database and display the result on the device screen. Thats not a big effort using the Sybase Unwired Platform. After all, thats 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 requests, I have noted a frequent requirement for the ability to operate in this offline-enabled mode. Fortunately, the Sybase Unwired Platform supports both offline-enabled and online-only application types. Thanks to this, enterprises can choose the architecture that works best for the application, rather than having to force all application requirements into a one-size-fits-all model. Offline-Enabled Applications Offline-enabled applications are built around the Sybase Unwired Platforms Mobile Business Object data synchronization capabilities, with a native

client that runs on the mobile device. This type of offline-enabled architecture is best suited to generally complex applications, which someone might use to do the vast majority of their day-today work. Examples are Mobile CRM Sales, Mobile Field Service, and applications 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 capabilities of data synchronization. The native client works best for complex, multi-screen applications. Browsers are not well suited for this type of complexity 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 enablement. 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

GETTING STARTED WITH OFFLINE-ENABLED ANDROID APPLICATIONS

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 customers 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 Applications By contrast, the Sybase Unwired Platform also supports online-only applications. I wont 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 businessapproval 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 application 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 common 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 applications tend to be native applications, they also take much longer to build since they require low-level coding, as we are about to see. Lets Build an Offline-Enabled Application To 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) downloads data from the EIS data source to the UltraLite database on the device. Then, when the EIS data source is unavailable, 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 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 MBOs. Sybase also provides the Sybase Mobilink software needed to synchronize data from the EIS to the device. Necessary Reference Material After youve 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 instructions, a Sybase Unwired Platform Developers Guide, and a good Android reference manual:

Figure 1: Reference Material


AUGUST 2012 21

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 installation guide that captures, all in one spot, a method for installing 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 Developers 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 Lifecycle Of the four Android application components: Activities, Services, Content Providers, and Broadcast Receivers, we will be using Activities. An Activity 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.
Activity. onCreate Activity. onRestoreInstanceState Activity. onStart Activity. onResume

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 activitys lifecycle, we would also synchronize within the onRestart Activity. There are also other opportunities for synchronization, 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 synchronization of a local database from the EIS data source.

Figure 3: Our Simple Application


Activity. onSaveInstanceState Activity. onPause Activity. onStop Activity. onDestroy

Activity. onRestart

Active Lifetime Visible Lifetime Full Lifetime

Figure 2: Application Lifecycle

22

ISUG TECHNICAL JOURNAL

GETTING STARTED WITH OFFLINE-ENABLED ANDROID APPLICATIONS

We define a Start Application button within the onCreate 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 execution 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 Listeners Callback listeners are provided so your application can monitor changes and notifications from Sybase Unwired Platform. When your application starts, it can register an application callback listener, a database callback listener and a synchronization status listener. These APIs provide notifications on an operations success or failure, or changes in data. Our three listeners are coded as follows. The Application Listener The com.sybase.mobile.ApplicationCallback class is used for monitoring changes to application settings, messaging connection 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 Listener The 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 registerCallbackHandler 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;

DECEMBER 2011 - JANUARY 2012

PB

MOBILITY

AUGUST 2012

23

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 Listener The 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.APPLICA TION_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.APPLICA TION_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.APPLICA TION_DA TA_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 Demand In 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 properties of the application: The showRegistrationStatus Class Each 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

24

ISUG TECHNICAL JOURNAL

GETTING STARTED WITH OFFLINE-ENABLED ANDROID APPLICATIONS

data replication can take place. Every Messaging application is uniquely identified by its 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 device ID every time a connection is made by the application. The SUP messaging engine communicates with the application 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.
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 + ")");

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 CONNECTED, you will see an Online status for the Application Connection in the Sybase Control Center as shown in Figure 4:

DECEMBER 2011 - JANUARY 2012

PB

The showConnectionStatus Class The 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 applications applicationSettings have been received from the server, the application is now in a suitable state for database subscriptions and/or synchronization. So, a CONNECTED state is the required state for transferring data to and from the server:
private void showConnectionStatus(int step, int status) { String debugString; switch (status) { case ConnectionStatus.CONNECTED: debugString = "CONNECTED"; break;

My Android Device

Status is Online

Figure 4: Sybase Control Center and Registration The showDatabaseStatus Class For a database status, wed 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.ndAll");

MOBILITY

AUGUST 2012

25

GETTING STARTED WITH OFFLINE-ENABLED ANDROID APPLICATIONS

GenericList<SUP101.Customer> customers = Customer.ndAll(); int n = customers.size(); Log.i(DEBUG_TAG, "Customer.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"); }

Button startAppBtn = (Button) 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();

} });

Putting it All Together The sequence of code in MainActivity.java roughly follows the sequence in the Sybase Unwired Platform documentation, 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 synchronization when the Start Application button is pressed:
package com.example.testclient; public class MainActivity extends Activity { private static nal String DEBUG_TAG = "TRACE"; private static nal int TIMEOUT = 20; private static nal String HOST = "192.168.1.138"; private static nal String USERNAME = "supAdmin"; private static nal String PASSWORD = "wk60#ls"; private static 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.setApplicationIdentier("Test22"); app.setApplicationContext(MainActivity.this); // Here is a button to start the application

// Here is a button to show the status Button showStatusBtn = (Button) 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.getSynchronizationProle().setServerName(HOST);

26

ISUG TECHNICAL JOURNAL

GETTING STARTED WITH OFFLINE-ENABLED ANDROID APPLICATIONS

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 Online When the application is launched, activity starts with onCreate 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 database results:
When the application is launched: 07-22 21:37:44.892: onCreate 07-22 21:37:46.883: onActivityResult 07-22 21:37:46.883: onResume

When the Start Application button is pressed: 07-22 21:39:21.154: createApplicationTask 07-22 21:39:21.154: createApplication 07-22 21:39:21.383: createApplication.registerApplication 07-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.createDatabase 07-22 21:39:35.152: createApplication.openConnection 07-22 21:39:35.192: createApplication.synchronize 07-22 21:39:35.222: MyDatabaseCallback.onSynchronize 07-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.onSynchronize 07-22 21:39:37.822: MyDatabaseCallback.synchronize status = 6 (ASYNC_REPLAY_UPLOADED) 07-22 21:39:37.822: (0) Database exists 07-22 21:39:37.822: Customer.findAll 07-22 21:39:37.833: Customer.findAll returned (3) customers 07-22 21:39:37.833: Last Name = (Gearhart)

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 application because wed like the local database to persist, we could call the deleteDatabase() function in the onDestroy activity if desired.

MOBILITY

AUGUST 2012

27

GETTING STARTED WITH OFFLINE-ENABLED ANDROID APPLICATIONS

Running the Application Offline When 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:
When the application is launched: 07-22 21:37:44.892: onCreate 07-22 21:37:46.883: onActivityResult 07-22 21:37:46.883: onResume

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)

For either mode of operation, the local database is available and contains data as shown below:
When the application is online: 07-22 21:41:25.822: (0) application status=203 (REGISTERED) 07-22 21:41:25.822: (0) Database exists 07-22 21:41:25.832: Customer.findAll 07-22 21:41:25.872: Customer.findAll returned (3) customers 07-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) When the application is offline: 07-23 09:56:51.422: (0) application status=203 (REGISTERED) 07-23 09:56:51.422: (0) Database exists 07-23 09:56:51.432: Customer.createApplication.findAll 07-23 09:56:51.432: Customer.findAll returned (3) customers 07-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_ERROR)

When the Start Application button is pressed: 07-22 22:02:27.353: createApplicationTask 07-22 22:02:27.353: createApplication 07-22 22:02:27.399: createApplication.startConnection 07-22 22:03:32.153: (0) connection status=101 (CONNECTION_ERROR) 07-22 22:03:32.175: createApplication could not start connection 07-22 22:03:32.175: createApplication.createDatabase 07-22 22:03:32.175: createApplication.openConnection 07-22 22:03:32.175: createApplication.synchronize 07-22 22:03:32.193: MyDatabaseCallback.onSynchronize 07-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 exists 07-22 21:39:37.822: Customer.findAll 07-22 22:04:29.222: Customer.findAll returned (3) customers 07-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_ERROR) 07-22 22:06:41.492: MyDatabaseCallback.onSynchronize 07-22 22:06:41.492: MyDatabaseCallback.synchronize status = 5 (ERROR)

Getting the Application Status Here 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 CONNECTED. When the application is offline, the application status is still REGISTERED and the connection status could be either CONNECTION_ERROR or CONNECTING.
28 ISUG TECHNICAL JOURNAL

Conclusion Developing 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 nothing to do with the object APIs 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 behind you, or you have a very good installation guide. The second 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 wasnt 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 simultaneously 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.

Das könnte Ihnen auch gefallen