Creating a New Activity

Error: Failed to load processor TracNav
No macro or processor named 'TracNav' found

Overview

Activities are the primary UI component of JavaRosa. They are also the workhorse of JavaRosa. A JavaRosa application is essentially many Activities strung together under the direction of a Shell. While the Shell coordinates the overall workflow of the application, the user interacts with Activities.

Activities encompass very specific nuggets of user interaction. Activities are the smallest, indivisible components of a workflow. The goal is that you can completely alter the workflow of an application by rearranging activities instead of modifying them. To that end, an Activity should be the smallest possible piece of a workflow that would never be rearranged -- a workflow atom.

What you may think of as 'an activity' is likely several activities working in concert. If you envision an activity having multiple 'modes', with many transitions between modes, you are likely thinking of several activities that work together.

An example of a poor Activity would be a FormManagerActivity, which presented a menu screen and handled each of the different actions you could do with a form. A better structure is to have a FormSelectActivity, a FormEntryActivity, and a FormSendActivity. Transitions between the different activities would be managed by the shell. Remember, shells are responsible for the workflow.

Activities are largely ignorant of each other. If an activity requires another activity to run (say to fetch a needed piece of information), it will notify the shell what information it needs, the shell will launch the requisite activity, and return the new information to the original activity; the first activity would not launch the new activity itself.

When creating a new Activity, keep this sense of purpose in mind.

Important Background

Terms: Shell, Activity, Context, Module

Interfaces: IShell, IActivity

Projects : org.javarosa.core

Getting Started

The first step in creating an Activity is finding the right location for it. Ideally, the Activity should be contained in its own project or module, to maximize its modularity. However, if there are other Activities with which the new one would share screens, data structures, and use cases, placing the Activity in that module is appropriate.

In general, an activity is launched with some parameters, performs some user interaction, and then returns to the shell with a return code and optional return parameters describing the result of that interaction.

A note about Contexts

Activities pass information back to the shell only through return codes and parameters. Activities should consider the context read-only! Do not modify the (global) context in your activity -- it is the shell's prerogative to manipulate the context based on the result of your activity. For example, although it is tempting to do so, a login activity should not set the 'logged-in user' in the context; it should return the user ID to the shell and the shell will add it to the context. The reason is coupling and modularity: there are times when we might want to validate a user's credentials without actually 'logging them in'. If the activity behaves properly (doesn't modify the context), we can do this easily.

Entry Points

Shells delegate control of the application to activities. It does this through two methods: start() and resume(). Once either of these methods is called, the acitivity is responsible for the application (processing user input) and has access to the screen. Other activites may continue to run in the background, however (though they will not alter the screen), and the activity may be pre-empted at any time by the shell.

The activity maintains control until it relinquishes it by calling shell.returnFromActivity(...), or it is pre-empted when the shell calls the activity's halt() method.

start(Context context)

The start method should complete all initialization that was not performed in the constructor, including all GUI initialization. The context object parameter is likely a subclass of the global context object. The context should contain required and optional parameters for running the Activity. It is preferred to pass all this information through the context rather than call separate initialize(...) methods, because this way the activity's current 'configuration' can be accessed and passed around generically.

When start() is called, the Activity should initialize itself and then take control of the application, for instance by setting the Display. Activities shouldn't touch the device's Display object directly, but rather call the shell's setDisplay() method. This allows the Shell to mediate requests for the Display, thus preventing confused Activities from breaking the Application's workflow.

In this method you should generally save off a reference to the activity's parent Shell.

resume(Context globalContext)

Resume is called when control is returned to a suspended activity -- both activities that suspended themselves explicitly, and activities that were pre-empted. The resume method should not perform any new initialization, except for that necessitated by changes to the context, or by new information that the activity requested from the shell and caused it to suspend itself in the first place.

The context parameter in this method exists to report changes to the context that may have occurred while the activity was suspended. However, it is only concerned with changes to global context parameters (logged-in user, etc), not to the activity-specific parameters required for activity initialization (it can be assumed these will never change throughout the life of the activity, unless the activity itself provides methods to do so). Consequently, these activity-specific parameters will be absent from this context, and you should call mergeInContext to properly integrate this new context into the activity's local context object.

Like start(), once called, it should take control of the Display, or in other ways take control of interpreting input.

Control Release

The running activity can either release control of the application by calling returnFromActivity on its parent shell, or it can have control removed from it by having its halt() method called.

halt()

The halt method is called when the Shell takes control from the activity.

An activity need not take any specific action upon halt(), but it should consider itself suspended, and should stop any background processing and timed/delayed alerts (unless these tasks are very important and the shell is prepared to handle them even when the activity is suspended). Upon resume(), the activity should be able to resume itself from the exact same state it was halted in. This method should ensure that such a state (particularly in the GUI) can be reconstructed later.

IShell.returnFromActivity(IActivity activity, String returnCode, Hashtable returnArgs)

An Activity whose task is complete, or an Activity which needs some sort of intervention (e.g., fetching some sort of data) before it can proceed, will yield control back to the shell by calling parentShell.returnFromActivity.

The shell will use the returnCode to determine whether to destroy or halt the activity. The possible return codes are enumerated in the Core API Constants static class. These are the constants labeled ACTIVITY_*. In general ACTIVITY_SUSPEND and ACTIVITY_NEEDS_RESOLUTION will suspend the Activity, and the others will terminate it permanently. Activities should not use return codes outside these defined constants, to make the Shell's life easier.

The ACTIVITY_NEEDS_RESOLUTION return code signals to the Shell that the Activity requires another Activity to be called before it can continue. The Activity cannot explicitly control what new Activity will be called, but can use its return arguments to signal to the Shell what it needs before it can continue. The exact contract as to how this will be done is left up to each Activity.

When the activity terminates for good, it should store the fruits of its labor (if any) in returnArgs. For example, a form select activity would store the ID of the selected form, if there was one. Even when an activity expects to be terminated, based on its return code, it should not call destroy() itself; the shell will do it.

Other Methods

destroy()

This method is called when the activity is being terminated by the shell and will never be resume()'ed. You should release any resources (sockets, etc.) here and optionally clean-up any large data structures. This method should only be called by the shell.

getActivityContext()

Gets the Context under which this Activity is running. This is generally just the Context that was passed into the start() method, but can also include any changes merged in in the resume() method. The purpose is mostly to access the activity's 'config parameters'.

contextChanged (Context context)

Alerts an activity to a change in the global context while the activity is running. Processing of this change should be similar to any change detected during a resume().

Tips / Common Sources of Confusion

  • Internal References - Activities need to keep track of both the Context that they are representing, as well as their parent Shell.
  • Returning Without Capturing Control - If an Activity is called, but it cannot initialize properly (required resource unavailable, etc.) it can terminate immediately by calling returnFromActivity (with the appropriate 'failed' return code) straight from the start() method.
    • Note: this causes a little weirdness with the stack and the Shell, but it all works out so long as the Shell doesn't execute any code after launching the activity that relies on the activity launching sucessfully. In practice, the Shell thread typically does nothing after launching/resuming an activity and simply goes to sleep.