POSIT:BasicFunctions

From Notes

Jump to: navigation, search

Contents

Introduction

POSIT's basic functions are the adding, listing, and mapping of Finds. These are handled by the FindActivty, ListFindsActivity, and MapFindsActivity, respectively. All three of these Activities -- as are all Android Activities -- are implemented as sub classes of the Activity class:

public class FindActivity extends Activity 
                          implements OnClickListener, OnItemClickListener, LocationListener 

In addition, the FindActivity implements several interfaces. The OnClickListener interface enables it to handle click events on its buttons. The LocationListener interface allows it to receive location updates from the Android location service. The OnItemClickListener enables it to respond to menu events.

The FindActivity is used for two different actions -- to insert a new Find and to edit and existing Find. An Intent is used to distinguish between these. An Intent is an abstract description of an event to be performed. It is used with startActivity() to start activities in Android. Here are examples of how FindActivity is started. In the first case it is started from PositMain when the user clicks the Add Find button. In this case we are inserting a new find.

switch (view.getId()) {
case R.id.addFindButton:
	intent.setClass(this, FindActivity.class);
	intent.setAction(Intent.ACTION_INSERT);
	startActivity(intent);
	break;

In this second example, FindActivity is started from the ListFindsActivity when the user selects an existing Find from a list:

protected void onListItemClick(ListView l, View v, int position, long id) {
	super.onListItemClick(l, v, position, id);
	Intent intent = new Intent(this, FindActivity.class);
	intent.setAction(Intent.ACTION_EDIT);
	if (DBG) Log.i(TAG,"id = " + id);
	intent.putExtra(PositDbHelper.FINDS_ID, id);

	startActivityForResult(intent, FIND_FROM_LIST);
	FindActivity.SAVE_CHECK=false;
	}

Notice that in both cases, the Intent starts the FindActivity. However, in the first case it sets the Intent's action to ACTION_INSERT and in the second case it sets it to ACTION_EDIT. When FindActivity is created, it retrieves the Intent and its action and uses this information to handle the Activity:

	final Intent intent = getIntent();
	String action = intent.getAction();
        /* Code omitted here ... */
	if (action.equals(Intent.ACTION_EDIT)) {
		doEditAction();
		INTENT_CHECK=1;
	} else if (action.equals(Intent.ACTION_INSERT)) {
		doInsertAction();
	}

Adding a New Find

Alt
Figure 1: The Add Find View.

The Add Find button from the main POSIT screen will bring up the View shown in Figure 1. As for all POSIT (and nearly all Android) activities, the View is described by an XML file rather than in Java code. The XML file for the Add Find View is located in res/layout/add_find.xml. The file is too large to display in its entirety, but we can look at a couple of excerpts.

The Add Find View contains two buttons. These are specified in XML as follows:

<ImageButton android:id="@+id/idSaveButton"
	android:layout_width="120dp" android:layout_height="wrap_content"
	android:src="@android:drawable/ic_menu_save" android:text="Save Find"
	android:layout_toRightOf="@id/idLabel" />

<ImageButton android:id="@+id/idTakePictureButton"
	android:layout_width="120dp" android:layout_height="wrap_content"
	android:src="@android:drawable/ic_menu_camera" android:text="Take Picture"
	android:layout_toRightOf="@id/idSaveButton" />

Both are examples of ImageButtons -- i.e., buttons that are labeled by an image rather than text. The first button is named idSaveButton. By specifying it as shown here, Android will keep track of it as a resource element and its ID can be used to retrieve it. Its fully qualified name is R.id.idSaveButton.

Here's an example of some of the text fields used in the Add Find View:

<TextView android:id="@+id/latitudeLabel" android:text="@string/latitude"
	android:layout_width="wrap_content" android:layout_height="fill_parent" />
<TextView android:id="@+id/latitudeText"
	android:layout_width="wrap_content" android:layout_height="fill_parent"
	android:maxLines="1" />

The first TextView specifies the latitudeLabel resource and sets its text to a string value named @string/latitude, which is specified as a resource in res/values/strings.xml. In the View (Figure 1) you can see that this label is the string Latitude. This string is not meant to change during execution. The second TextView specifies the latitudeText, which in the view is set to the phone's latitude when the Find is being added.

Some of the fields in Add Find View (e.g., the latitude and longitude and time) are filled in automatically. Others (the name and description) are meant to accept input from the user. Here's an example of the code used in the onCreate() method to set up the view.

setContentView(R.layout.add_find);
mLatitudeTextView = (TextView)findViewById(R.id.latitudeText);
mLongitudeTextView = (TextView)findViewById(R.id.longitudeText);

In this case we're setting the view for this Activity to R.layout.add_find by reading and rendering the specifications in the add_find.xml file. We are initializing the instance variables mLatitudeTextView and mLongitudeTextView to their respect views using their respective resource identifiers (R.id.latitudeText and R.id.longitudeText). These variables can then be used throughout the Activity to access those views.

Saving the Find

After inputting data into the Name and Description fields, the user will (typically) click the Save button. This click event will be detected by Android and the Activity's onClick() method will be invoked. By overriding this method in the FindActivity class, the programmer can control what happens when the save button is clicked.

In order to respond to button clicks, the FindActivity object must register itself as a listener. This is done in onCreate():

ImageButton saveButton = (ImageButton)findViewById(R.id.idSaveButton);
saveButton.setOnClickListener(this);

Then, when that button is clicked and the onClick() method is invoked, there must be code there to handle that event. In the following code segment, data input by the user is retrieved from the various text fields that make up the Add Find View. If POSIT is currently in ad-hoc mode (i.e., the RWGService.isRunning()) then the data are also trasmitted to other finds. In any case, it is saved to POSIT's database.

public void onClick(View v) {
	Intent intent;
	switch (v.getId()) {
        
        /*  Code omitted here .... */
	case R.id.idSaveButton:
		ContentValues contentValues = retrieveContentFromView();
		Log.i("after retrive", (System.currentTimeMillis()-start)+"");
		if (RWGService.isRunning())
			sendAdhocFind(contentValues,null);//imageBase64String);
		doSave(contentValues);			
		break;			
	}			
}

In this case we use the retrieveContentFromView() helper method to retrieve the values from the user interface. A ContentValues is an Android type that associates a key (represented as a Java String) with various types of data (int, double, boolean, Object, etc.). In the following snippet (from which code has been removed), we retrieve the Find's name from the UI and insert it into a ContentValues, which is then returned.

	private ContentValues retrieveContentFromView() {
		ContentValues result = new ContentValues();

                // Get the Find's name and put it into the ContentValues object
		EditText eText = (EditText) findViewById(R.id.nameText);
		String value = eText.getText().toString();
		result.put(PositDbHelper.FINDS_NAME, value);

                /* Code omitted here for handling the other data elements ...*/
		
		return result;
	}

The doSave() method saves the Find to the database by passing its content and images to the insertToDB() method, if this is a new find, or by invoking updateToDB(), if this is an existing find. Both of those methods are found in Find.java.

	if (mState == STATE_INSERT) {            // if this is a new find
		mFind = new Find(FindActivity.this, guid);
		List<ContentValues> imageValues = Utils.saveImagesAndUris(this, mTempBitmaps);
			
		if (mFind.insertToDB(contentValues, imageValues)) {//insert find into database
			Utils.showToast(FindActivity.this, R.string.saved_to_database);
			mFind.setGuid(contentValues.getAsString(PositDbHelper.FINDS_GUID));
		} else {
			Utils.showToast(FindActivity.this, R.string.save_failed);
		}
	} else { 
		if (mFind.updateToDB(contentValues)) {
			Utils.showToast(FindActivity.this, R.string.saved_to_database);
		} else {
			Utils.showToast(FindActivity.this, R.string.save_failed);
		}
	}

Listing Finds

Alt
Figure 2: The List Finds View.
Figure 2 shows the List Finds View. It presents a summary list of the Finds stored in the phone's database. When an element of the list is clicked, Android invokes the onListItemClicked() method which is expected to handle the action.

The ListFindsActivity is a subclass of the built-in Android ListActivity. This provides it with useful List functionality.

public class ListFindsActivity extends ListActivity implements ViewBinder 

The ViewBinder interface is a particular useful feature. It enables you to directly associate data from the database with views from the user interface. In order to make use of its functionality, you must set up parallel arrays that can be used to map the data to the views. These are defined in PositDbHelper as follows:

	public static final String[] list_row_data = { 
		FINDS_ID,
		FINDS_GUID,  
		FINDS_NAME,
		FINDS_DESCRIPTION,
		FINDS_LATITUDE,
		FINDS_LONGITUDE,
		FINDS_SYNCED, //,
		BLAH,  // A filler to go with the R.id.num_photos view
		BLAH   // A filler to go with the R.id.find_image view
	};

	public static final int[] list_row_views = {
		R.id.row_id,		    
		R.id.idNumberText,
		R.id.name_id, 
		R.id.description_id,
		R.id.latitude_id,
		R.id.longitude_id,
		R.id.status, //,
		R.id.num_photos,
		R.id.find_image     // Thumbnail in ListFindsActivity
	};

The first element of the list_row_data array corresponds to the first element of the list_row_views data. Thus, the Find's latitude (as represented by FINDS_LATITUDE) will be displayed in the latitude text view, which is referenced by R.id.latitude_id.

When the ListFindsActivity is created or resumed it invokes the fillData() helper method to populate its View. This method begins by retrieving the two parallel arrays from PositDbHelper. It then retrieves the Find's data from the database. The data are returned as a Cursor, an object that provides read/write access to the rows of data that were returned. In this case the fetchFindsByProjectId() will return all Finds associated with the given project.

	private void fillData() {
		String[] columns = PositDbHelper.list_row_data;
		int [] views = PositDbHelper.list_row_views;
	
		mCursor = mDbHelper.fetchFindsByProjectId(project_id);	
		if (mCursor.getCount() == 0) { // No finds
			setContentView(R.layout.list_finds);
			mCursor.close();
			return;
		}
		startManagingCursor(mCursor); // NOTE: Can't close DB while managing cursor
		SimpleCursorAdapter adapter = 
			new SimpleCursorAdapter(this, R.layout.list_row, mCursor, columns, views);
		adapter.setViewBinder(this);
		setListAdapter(adapter); 
	}

Assuming that some rows are returned (count != 0), we use a SimpleCursorAdapter to associate the data (in columns) with the user interface elements (in views). This is done automatically for us. Android provides a way to modify the views before they are displayed -- this is provided by the ViewBinder interface -- but we won't go into that. Have a look at the code, which contains additional comments.

When the user clicks on a row of the list, the onListItemClick() method is invoked by Android. This method contains code (shown above) to display the Find in the AddFindView, where it can then be edited and saved. Of course, in order for ListFindsActivity to handle such events it must be registered as a listener for those kinds of events, which it is because the onListItemClick() method is inherited from ListActivity.

Mapping Finds

Alt
Figure 3: The Map Finds View.

The MapFindsActivity is invoked from the menu in ListFindsActivity. It is a subclass of the built-in Android MapActivity and inherits much of its functionality from its superclass:

public class MapFindsActivity extends MapActivity {

When MapFindsActivity starts up it displays a built-inMapView, to which one can attach features such as ZoomControls.

	protected void onCreate(Bundle icicle) {
		super.onCreate(icicle);
		setContentView(R.layout.map_finds);
		linearLayout = (LinearLayout) findViewById(R.id.zoomView);
		mMapView = (MapView) findViewById(R.id.mapView);
		mZoom = (ZoomControls) mMapView.getZoomControls();
		linearLayout.addView(mZoom);
	}

Here are the key elements from the MapView:

    <com.google.android.maps.MapView
        android:id="@+id/mapView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:clickable="true"
        android:apiKey="0bvEMJ9m96_dErQO9edJAzhIxs3xDXhIVcrScXw"/>
    <LinearLayout
    	android:id="@+id/zoomView"
    	android:layout_width="wrap_content"
    	android:layout_height="wrap_content"
    	android:layout_alignBottom="@id/mapView"
    	android:layout_centerHorizontal="true"/>

Note the inclusion of the Google Maps Key. This is necessary in order to download maps from Google.

An interesting feature of POSIT's map view is that it is able to display clickable geo-located icons representing the different Finds (Figure 3). When one of these items is clicked, it will display basic information about that Find. To see how that is done check out Mapview Tutorial.

The MapFindsActivity is a fairly short program. Its two main methods are mapFinds() and mapLayoutItems(). The first of these methods retrieves Finds data from the database, adds them to a Google map overlay and then adds the overlay to the map. The Finds data are retrieved indirectly through a Cursor object. The cursor is then passed to the mapLayoutItems() method.

	private void mapFinds() {
		mDbHelper = new PositDbHelper(this);

		SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
		
		mCursor = mDbHelper.fetchFindsByProjectId(sp.getInt("PROJECT_ID", 0));		
		if (mCursor.getCount() == 0) { // No finds
			Utils.showToast(this, "No Finds to display");
			finish();
			return;
		}

		mapOverlays = mMapView.getOverlays();
		mapOverlays.add(mapLayoutItems(mCursor));	
		mapController = mMapView.getController();

		mDbHelper.close();
	}

The mapLayoutItems() method iterates through its cursor, retrieving the Find's latitude, longitude, among other data. These data are retrieved from the individual columns of the cursor. Imagine that the cursor is a pointer to the Finds table, which consists of a number of rows (one for each Find) and a number of columns (representing the fields such as latitude, longitude, etc.).

private MyItemizedOverlay mapLayoutItems(Cursor c) { int latitude = 0; int longitude = 0; int itemId = 0;

drawable = this.getResources().getDrawable(R.drawable.androidmarker); MyItemizedOverlay mPoints = new MyItemizedOverlay(drawable, this, true); c.moveToFirst();

do { latitude = (int) (c.getDouble(c .getColumnIndex(PositDbHelper.FINDS_LATITUDE))*1E6); longitude = (int) (c.getDouble(c .getColumnIndex(PositDbHelper.FINDS_LONGITUDE))*1E6);

String itemIdStr = "" + c.getString(c.getColumnIndex(PositDbHelper.FINDS_ID)); String description = itemIdStr + "\n" + c.getString(c.getColumnIndex(PositDbHelper.FINDS_NAME)); description += "\n" + c.getString(c.getColumnIndex(PositDbHelper.FINDS_DESCRIPTION));

Log.i(TAG, latitude+" "+longitude+" "+description); mPoints.addOverlay(new OverlayItem(new GeoPoint(latitude,longitude),itemIdStr,description)); } while (c.moveToNext());

return mPoints; } </pre> Once the data are retrieved from the cursor, then are inserted into the overlay as GeoPoints. The MapView object knows how to handle these. When the user clicks on one of the icons in the overlay, MapView will open an view that displays the item's ID and description, which are passed to the overlay as it is being created.

Personal tools
NSF K-12