dcsimg

Hands-on: Multiple Activities and Data Persistence in Android

Building on our Android-based TicketResponder applicatoin, we add a new screen and data persistence.

Motivation

This article builds upon the work of our previous articles in building a real-world data collection application for Android. In this article we add an additional user interface screen to capture necessary configuration data which will be used by the application in a subsequent step. The code samples in this article demonstrate:

  • The creation of a new user interface screen (Activity) with multiple controls
  • Navigation between two different Activity classes
  • Creating and using a basic Intent
  • The retrieval/storage of configuration data via the program

These are some necessary steps along the path to building the full TicketResponder application.

Persistence Options

Before jumping directly into the code, I thought it might be helpful to take a quick tour of data persistence options in Android. Storing data in Android can be accomplished through a number of mechanisms. Which technique is right for your application depends on a number of factors including:

  • How much data is to be stored
  • How frequently the data is accessed
  • The kind of manipulations you need to perform on/with the data
  • Which applications need access to the data

If your application needs to store some small data elements such as strings or integer values, you might first consider SharedPreferences. SharedPreferences work in a manner similar to an “ini” file with key/value pairs often found on other desktop and server platforms. We will use this technique later in this article as we add a Settings screen to our application to store username, password, and a server internet address, or URL.

If you have larger amounts of data to manage, it is also possible to store data in text or binary files, much like you would use in a server or desktop application. The familiar java.io package is available for manipulating files including a variety of Stream-based classes familiar to Java programmers. One item to note with respect to working with files is that you must use a non-standard means of opening/creating the file to properly navigate the process/security context within the Linux-based Android environment. Your files should be referenced by way of your application’s context, as shown below:

FileOutputStream fos = this.context.openFileOutput("somefilename.xml", Context.MODE_PRIVATE);
fos.write("\n".getBytes());
fos.write("somedatagoeshere".getBytes());
fos.close();

The example above opens a file for output within its own directory. Once the file is opened, we can access it via a FileOutputStream or other type of file I/O class. Once the file is opened, we write two lines of text to the file and then close it the file. This technique of working with a file-based persistence method would be appropriate for storing a local-cache of application specific files for applications such as RSS readers, virus definition files, or perhaps a global high-scorers file downloaded from a gaming server on the Internet.

For more sophisticated data storage requirements, your application can leverage the powerful and flexible SQLite database. SQLite provides a significant subset of the popular Structured Query Langauge (SQL) found in more powerful (and costly) database technologies. Those familiar with relational databases such as MySQL, Microsoft SQL Server, Oracle and others will find that much of the terminology to be similar to what you are accustomed to — you work with databases, tables, fields/columns, etc. There are some differences with respect to how certain data types are treated but for a mobile platform the opportunity to have a real SQL database is just awesome. I’ve spent more hours than I care to think about navigating the RecordStores of the Research in Motion BlackBerry platform or the limited database structures of Palm OS. I’ve even written ISAM libraries for a mobile device but I’d rather not think about that right now. SQLite has become the defacto mobile database as it is now used in Android, iPhone, Palm webOS, and is the underlying data storage technology for HTML5 databases powered by WebKit.

Using an SQLite database is a good idea when you have larger data sources that you need to search or sort. Examples that come to mind include a list of client-specific data such as inventory data or a product list. An application can manipulate an SQLite database for its own purposes, however the application must know the schema of the data and in general, using an SQL database is a task aimed at developers building a closed application, meaning no other application knows or cares about your data per se. An SQLite database is “private” to the application which utilizes it and other applications cannot readily attach to it nor manipulate its data.

If your application intends to share data with a variety of other applications, you will want to consider a creating a ContentProvider. A ContentProvider is the preferred technique for sharing data amongst multiple applications. For example, the Contacts data (i.e. Address Book records) is accessed via a ContentProvider with standard queries.

Lastly, we would be remiss to omit the “network” as a place to store data. In fact, a mobile device with powerful Internet connectivity such as Android can leverage the storage facilities of a datacenter-hosted server on an as-needed basis. Over time we will see more of this kind of application — mobile users leveraging cloud services, including storage. In fact, this paradigm is so prevalent on the web today that it is often over-looked.

For example, Amazon has created a service called Amazon Simple Storage Service where they provide a simple API for storing and retrieving data. Amazon (and other providers) take care of the dirty work of running a massively web-scaled datacenter, allowing the developer to focus on application functionality and not get bogged down with database server maintenance. If a given application is wildly successful and needs more storage, they just crank up the volume with the storage provider — no need to go out and buy new hardware and software to support the load. As a developer first and a reluctant system admin second, I can appreciate the allure of such arrangements. The challenge of course is to make sure that your business model justifies the purchase of more storage. Remember, if you’re losing money, you cannot make it up in volume!

We will examine both SQLite, ContentProviders, and even Cloud connectivity in future Android articles. For now, we’re going to focus on expanding our TicketResponder application by adding a second Activity. This Activity presents a user interface for the user to manipulate configuration data along with the code for saving and restoring the configuration data. These data elements are necessary for the application to access a remote server. Let’s look at the bigger picture to better understand what our application must accomplish.

The Requirements

If you recall from a prior article, our application collects a couple of data elements from the user. The application takes as input a ticket number (never mind for now where that came from — we’ll get there eventually!) and a desired status such as “In Progress”, “Waiting on Client”, “Closed”, etc. as shown in the image below.

enterticket.png

Once the user selects the “Update Ticket” button it is time to package the data into a server-friendly format and then transmit it to the server. We will cover the actual communications code in a future article — for now we need to manage to navigate between our two Activity classes and learn how to store and retrieve the data elements, which in our case consists of three string values: server address, username, and password.

The Intent and the Activity

Recall that an Activity is essentially a user interface screen. The user interface elements are defined in a layout file which is an xml file. This file is created either “by hand” or through the use of WSYWIG tools in the Android Developer Tools plugin for Eclipse. I typically copy and paste from one file to the next and then tweak the new one as desired. For more information on creating layout resources, please see prior article. Once we’ve got the user interface defined for our second Activity (see settings.xml in the source code for layout details), we need to know how to launch this second Activity. The image below depicts our new settings screen user interface.

settingsscreen1.png

The most straight-forward means to launch one Activity from another is to use an explicit Intent — that is, an Intent which explicitly identifies another Activity class. Here is a snippet of code taken from TicketResponder.java which implements this new functionality.

final ImageView settingsImage = (ImageView) findViewById(R.id.ImageSettings);
settingsImage.setOnClickListener(new ImageView.OnClickListener() {
		public void onClick(View v) {
			try {
				Intent in = new Intent(v.getContext(),TRSettings.class);
				startActivity(in);
			}
			catch (Exception e){
				Log.i(tag,"Failed to launch Settings Activity " + e.getMessage());
			}
		}
}
);

We must also define in our application what the user interaction must be to perform this operation. Note in the prior screen shot the “gear-like” icon in the lower left portion of the screen. When this icon is clicked, we run the above code to launch the TRSettings Activity. This code is an anonymous onClick handler for an ImageView widget. It simply creates a new Intent which identifies the TRSettings class (defined in TRSettings.java) and then starts that Activity by passing the Intent to the startActivity() method. Let’s have a closer look at the functionality of TRSettings Activity.

Shared Preferences Example

The purpose of the TRSettings class is to:

  1. Retrieve three data elements from persistent storage
  2. Display those three data elements: user name, password, server address in EditText widgets
  3. Handle a Button press by capturing data from the EditText widgets
  4. Validate that the user entered appropriate data. For our purposes, we just make sure the fields are not empty
  5. Store the new values to persistent storage
  6. Close this Activity and return to the prior screen

We implement our data persistence with the previously mentioned SharedPreferences class. We simply create an instance of our SharedPreferences with a call to the getSharedPreferences() method passing in a name for the preferences we want to work with and an access mode. In this case we use our application name of “TicketResponder” and the MODE_PRIVATE flag. These preferences are private to our own application. Remember, SharedPreferences are appropriate for simple storage such as our example of username/server address kinds of data elements. Other uses might include saving state from one invocation of our application to another — this is helpful for pausing a game during an incoming phone call. You certainly wouldn’t want your user to have to go back to level 1 just because the phone rang! This might also look like remembering the unique identifier of a contact record or inventory part so the application can re-load that data the next time the user starts the application. Providing this kind of feature goes a long way in making your application more usable and building loyal fans.

We next create an instance of the Editor class with a call to the edit() method of the SharedPreferences class instance. Data is retrieved with a call to the getString() method. Data is stored with calls to the putString() method. When all manipulation of the data is complete and we’re ready to go, we call the commit() method of the Editor instance. This performs the actual persistence of our data to the SharedPreferences instance.

The final thing we do is call the finish() method which causes our Activity to close. Hitting the “back” button (or escape while running in the Android Emulator) causes the Activity to close without saving the configuration data. This is in essence a “cancel” operation. Here is the code for the Activity as found in TRSettings.java

package com.msi.linuxmagazine.ticketing;
import android.app.Activity;
import android.content.Context;
import android.view.View;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.util.Log;
import android.app.AlertDialog;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
public class TRSettings extends Activity {
	final String tag = "TRSettings";
	private SharedPreferences prefs = null;
	private Editor editor = null;
	public static final String SERVERADDRESS = "serveraddress";
	public static final String USERNAME = "username";
	public static final String PASSWORD = "password";
	public static final String PREFERENCESNAME = "TicketResponder";
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.settings);
        final EditText serverAddress = (EditText) findViewById(R.id.serverAddress);
        final EditText userName = (EditText) findViewById(R.id.userName);
        final EditText password = (EditText) findViewById(R.id.password);
        final Button saveButton = (Button) findViewById(R.id.saveButton);
        prefs = this.getSharedPreferences("TicketResponder", Context.MODE_PRIVATE);
        editor = prefs.edit();
        serverAddress.setText(prefs.getString(TRSettings.SERVERADDRESS, "http://servernamegoeshere/"));
        userName.setText(prefs.getString(TRSettings.USERNAME, "user"));
        password.setText(prefs.getString(TRSettings.PASSWORD,"password"));
        saveButton.setOnClickListener(new Button.OnClickListener() {
            public void onClick(View v) {
                try {
                	String server = serverAddress.getText().toString();
                	String user = userName.getText().toString();
                	String pword = password.getText().toString();
                	Log.i(tag,"server address is [" + server + "]");
                	Log.i(tag,"username is [" + user + "]");
                	Log.i(tag,"password address is [" + pword + "]");
                	// let's do some basic input filtering
                	if (user.trim().length() == 0 || server.trim().length() == 0 || pword.trim().length() == 0)
                	{
                		AlertDialog.Builder adb = new AlertDialog.Builder(v.getContext());
                        AlertDialog ad = adb.create();
                        ad.setMessage("All fields are required.");
                        ad.show();
                        return;
                	}
                	// let's store in a shared preference
                	editor.putString(TRSettings.SERVERADDRESS,server);
                	editor.putString(TRSettings.USERNAME,user);
                	editor.putString(TRSettings.PASSWORD,pword);
                	editor.commit();
                	finish();
                } catch (Exception e) {
                    Log.i(tag, "Error occurred [" + e.getMessage() + "]");
                }
            }
        });
    }
}

Updating AndroidManifest.xml

Running the code is easy — just launch the Android Emulator (or real device) through the Run menu in Eclipse as described in the previous article, which covered application debugging. If you do this, you’ll get an error in your application complaining that the application could not continue because the Activity doesn’t exist. Huh? After all that we cannot run our application to see the new Activity? What could it be? If you look in the Log you’ll see a message that looks like this:

08-31 23:05:11.130: INFO/TicketResponder(770): Failed to launch Settings Activity Unable to find explicit activity class {com.msi.linuxmagazine.ticketing/com.msi.linuxmagazine.ticketing.TRSettings}; have you declared this activity in your AndroidManifest.xml?

The problem is that we didn’t tell Android that we had an additional, runnable Activity class. In order to do this we need to modify our AndroidManifest.xml file to let Android know about our new Activity. Fortunately, this is a very simple thing to do — all we have to do is add a single line to our AndroidManifset.xml file: . Now our complete AndroidManifset.xml file has two Activity classes defined:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      android:versionCode="1"
      android:versionName="1.0" package="com.msi.linuxmagazine.ticketing">
    <application android:label="@string/app_name" android:icon="@drawable/network_services">
        <activity android:name=".TicketResponder"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    <activity android:name="TRSettings"></activity>
</application>
    <uses-sdk android:minSdkVersion="3" />
</manifest>

Our TicketResponder application now has two Activity classes that we can navigate between and the ability to store small amounts of data. The full source code is available on the Linux Magazine mobile source website in both a browsable & SVN checkout format and as a zip file. Coming up we’re going to look at adding some Location/GPS data to our application.

Comments on "Hands-on: Multiple Activities and Data Persistence in Android"

tkenney

Another option for persistence that your readers might be interested in learning about is Perst, an open-source, object oriented embedded database, http://www.mcobject.com/perst. Perst is written in Java, so it might be more appealing to developers who wish to avoid the “impedance mismatch” of an object-oriented programming language and relational database. Perst strives to make plain-old-java-objects (POJOs) persistent, as transparently as possible. In addition, Perst is available on all mobile devices (all JME/J2ME devices), so developers who\’d like their applications to be portable to BlackBerry, Android, Symbian, etc. should consider this. Perst also insulates developers from the vagaries of RMS and differences between RMS and JSR-75. When you get around to adding some Location/GPS data to your application, Perst offers support for R-Tree indexes.

Reply
abd

N.B

Issue
====
At runtime I was consistently getting the error below


D/AndroidRuntime( 779): Shutting down VM
W/dalvikvm( 779): threadid=3: thread exiting with uncaught exception (group=0x4000fe70)
E/AndroidRuntime( 779): Uncaught handler: thread main exiting due to uncaught exception
E/AndroidRuntime( 779): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.msi.linuxmagazine.ticketing/com.msi.linuxmagazine.ticketing.TRSettings}: java.lang.NullPointerException
E/AndroidRuntime( 779): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2268)
E/AndroidRuntime( 779): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2284)
E/AndroidRuntime( 779): at android.app.ActivityThread.access$1800(ActivityThread.java:112)
E/AndroidRuntime( 779): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1692)
E/AndroidRuntime( 779): at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime( 779): at android.os.Looper.loop(Looper.java:123)
E/AndroidRuntime( 779): at android.app.ActivityThread.main(ActivityThread.java:3948)
E/AndroidRuntime( 779): at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime( 779): at java.lang.reflect.Method.invoke(Method.java:521)
E/AndroidRuntime( 779): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:782)
E/AndroidRuntime( 779): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:540)
E/AndroidRuntime( 779): at dalvik.system.NativeStart.main(Native Method)
E/AndroidRuntime( 779): Caused by: java.lang.NullPointerException
E/AndroidRuntime( 779): at com.msi.linuxmagazine.ticketing.TRSettings.onCreate(TRSettings.java:44)
E/AndroidRuntime( 779): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1123)
E/AndroidRuntime( 779): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2231)
E/AndroidRuntime( 779): … 11 more
I/Process ( 574): Sending signal. PID: 779 SIG: 3
I/dalvikvm( 779): threadid=7: reacting to signal 3

Solution
=====
So to solve this i replaced in the source code TRSettings.java, the following lines
>>>

prefs = this.getSharedPreferences(\”TicketResponder\”, Context.MODE_PRIVATE);
editor = prefs.edit();

with the following code snippets below.

>>>

try
{
prefs = this.getSharedPreferences(\”TicketResponder\”, Context.MODE_PRIVATE);
editor = prefs.edit();
}
catch (java.lang.NullPointerException ne)
{
Log.i(tag, \”Error occurred [\" + ne.getMessage() + \"]\”);
}

Cause
====
I guess the NullPointerException had to be caught as prefs was declared null on creation.
i.e
private SharedPreferences prefs = null;
private Editor editor = null;

Hope it helps while attempting to develop and implement the application in this tutorial.

Reply
fableson

@tkenney – thanks for the db reference – I\’ll check it out.

@abd – thanks for the code suggestion

Reply

We are a bunch of volunteers and starting a brand new scheme in our community. Your site offered us with helpful information to paintings on. You have done a formidable task and our entire neighborhood will probably be grateful to you.

Reply

I do accept as true with all of the ideas you have offered for your post. They’re very convincing and will certainly work. Still, the posts are too brief for novices. May just you please prolong them a bit from next time? Thanks for the post.

Reply

You really make it appear so easy together with your presentation however I find this matter to be really one thing that I believe I’d by no means understand. It kind of feels too complex and extremely huge for me. I’m taking a look ahead to your next post, I?ll try to get the grasp of it!

Reply

Hey just wanted to give you a quick heads up. The words in your content seem
to be running off the screen in Firefox. I’m not sure if this is a format issue or something to do with browser compatibility but I figured I’d post to let
you know. The design look great though! Hope you
get the problem solved soon. Many thanks

Reply

My programmer is trying to persuade me to move to .

net from PHP. I have always disliked the idea because of the
expenses. But he’s tryiong none the less. I’ve been using WordPress on several websites for about
a year and am worried about switching to another platform.
I have heard good things about blogengine.net. Is there a way I
can import all my wordpress posts into it?
Any help would be greatly appreciated!

Reply

Sitios de comparadores en Gran Bretaña están ahora utilizando un poder significativo,
sobre todo en los seguros de coches y motos.
Esto preocupa en el Reino Unido a aseguradores de coches
y motos, que les preocupa que el sector deje de ser rentables porque
el precio es el indice primordial.
En Australia, sitios de comparadores están creciendo en número, pero su papel ha dividido a la industria.

Algunas aseguradoras ven como un camino para la venta de servicios y animar a clientes a contratar un seguro.
Otros se oponen con vehemencia a él por motivos que se
limitan los márgenes.

Reply

Ridiculous story there. What occurred after? Thanks!

my site Neumáticos nuevo de verano

Reply

El Turismo Ecologico se ha convertido en una forma de vivenciar
esta actividad desde una perspectiva agradablemente activa,
donde el paseo, exploración o travesia implica encontrarse con algunas situaciones novedosas
o cuando menos, no del todo previsibles. El deporte y las
actividades ecologicas son las que definen la esencia
de esta percepcioon de viajar que se desarrolla básicamente en
la naturaleza y que a diario aumenta y es más aceptada por viajeros de todas las edades.

Reply

I always emailed this webpage post page to all my friends, for the
reason that if like to read it next my friends will too.

Reply

Very informative blog article.Really looking forward to read more. Really Cool.

Reply

There is evidently a bundle to know about this. I assume you made various nice points in features also.

Reply

I precisely wished to thank you very much all over again. I do not know the things that I could possibly have implemented in the absence of the entire thoughts shared by you over my topic. It had become an absolute terrifying matter in my circumstances, nevertheless coming across the specialised strategy you treated the issue made me to cry with contentment. I am thankful for this help and even hope you realize what an amazing job you happen to be doing instructing some other people by way of your web page. I’m certain you haven’t encountered all of us.

Reply

It is not my first time to pay a quick visit this web page,
i am browsing this web page dailly and take nice facts from here all the time.

Reply

I will immediately snatch your rss feed as I can not in finding your e-mail subscription link or newsletter service.
Do you have any? Kindly allow me understand so that I may just subscribe.

Thanks.

Reply

There are certainly a lot of details like that to take into consideration. That could be a great point to convey up. I offer the ideas above as normal inspiration however clearly there are questions like the one you deliver up where the most important factor might be working in honest good faith. I don?t know if greatest practices have emerged around issues like that, however I’m sure that your job is clearly recognized as a good game. Both girls and boys feel the impact of just a moment’s pleasure, for the rest of their lives.

Reply

Hiya, I’m really glad I’ve found this info. Nowadays bloggers publish only about gossip and internet stuff and this is actually irritating. A good website with interesting content, this is what I need. Thank you for making this site, and I will be visiting again. Do you do newsletters by email?

Reply

I’??ve read several good stuff here. Certainly value bookmarking for revisiting. I surprise how so much effort you place to create the sort of great informative website.

Reply

I will right away grasp your rss as I can’t to find your e-mail subscription link or e-newsletter service. Do you have any? Please allow me know in order that I may just subscribe. Thanks.

Reply

What i don’t understood is if truth be told how you’re now not really much more neatly-liked than you might be now. You’re so intelligent. You realize thus considerably with regards to this matter, produced me individually imagine it from numerous varied angles. Its like men and women don’t seem to be interested until it is something to accomplish with Lady gaga! Your own stuffs excellent. At all times maintain it up!

Reply

Here are some links to web-sites that we link to simply because we consider they’re really worth visiting.

Reply

My partner and I stumbled over here from a different websiteand thought I might check things out. I like what I see so i am just following you.Look forward to looking into your web page repeatedly.

Reply

Wonderful story, reckoned we could combine some unrelated data, nevertheless definitely worth taking a appear, whoa did one study about Mid East has got a lot more problerms also.

Reply

Please take a look at the websites we stick to, including this one particular, as it represents our picks through the web.

Reply

Wonderful story, reckoned we could combine a handful of unrelated data, nonetheless truly worth taking a appear, whoa did a single understand about Mid East has got much more problerms too.

Reply

Hi there to every single one, it’s truly a nice for me to
pay a quick visit this site, it includes precious Information.

Reply

I have been surfing on-line greater than three hours lately, yet I by no means discovered any interesting article like yours. It is lovely worth enough for me. In my opinion, if all webmasters and bloggers made good content material as you did, the internet shall be much more helpful than ever before.

Reply

Sites of interest we’ve a link to.

Reply

Sites of interest we have a link to.

Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>