Attribute Editor

The purpose of this sample is to demonstrate how to view and edit the attributes of a map layer provided from an ArcGIS Server feature layer (exposed from within a feature server service). It also uses a map server service of the same data and a cached tiled map service from ArcGIS.com as a basemap. Users can click a feature on the map to view the attributes in a dialog box. If the fields are editable, a user can change the attributes of the field. Validation of the field type provides the user with different ways of adding attributes; number fields do not allow text input, date fields provide a date picker dialog box, and fields that are used for defining a subtype provide drop-down lists. Once attributes have been changed, they can be posted to the feature layer in an edit operation.

Sample Design

The most important classes in the sample are the classes that extend Activity (AttributeEditorActivity) and BaseAdapter (AttributeListAdapter).

AttributeEditorActivity initializes the MapView and ArcGISFeatureLayer objects with the appropriate onStatusChangedListener and onClickListener method. Activity also contains the logic to launch the attributes dialog box and apply edits once the user has finished changing the attributes.

The AttributeListAdapter class contains the logic to use information from the feature layer and the feature returned from the on-click query to build the attributes dialog box. This includes parsing the field, subtype, and graphic attributes to build up some additional POJO (Plain Old Java Objects) classes to store this information for retrieval when the list is built and for applying the edits. The dialog box is constructed from an Android ListView; each row in the list view is built from other View elements depending on the field type.

The feature layer is added to the map as ArcGISFeatureLayer in Selection mode and is backed by an ArcGIS dynamic map service layer based on the exact data. Clicking the map performs a query on the feature layer, returns the first feature found, and places its attributes into the dialog box. Changes in attribute values are detected from the dialog box, and if changes are found, they are applied to the server using an apply edits operation on the feature service. If an edit has been performed on the subtype field, the underlying ArcGIS dynamic map service layer is also refreshed to show any rendering changes of the edited features (in this example, the subtype field is that which is associated with feature rendering). This approach ensures that the minimum amount of data is passed over the network and is therefore suitable for mobile access using a 3G network. This is due to the often smaller size and quicker display of images returned from an ArcGIS dynamic map service layer than the feature information returned for the same extent for an ArcGIS feature layer (where the JSON response from the server has to be parsed and each feature drawn).

Sample Code

/* Copyright 2014 ESRI
*
* All rights reserved under the copyright laws of the United States
* and applicable international laws, treaties, and conventions.
*
* You may freely redistribute and use this sample code, with or
* without modification, provided you include the original copyright
* notice and use restrictions.
*
* See the sample code usage restrictions document for further information.
*
*/

package com.esri.arcgis.android.samples.attributeeditor;

import java.util.HashMap;
import java.util.Map;

import android.app.Activity;
import android.app.Dialog;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Spinner;

import com.esri.android.map.MapView;
import com.esri.android.map.ags.ArcGISDynamicMapServiceLayer;
import com.esri.android.map.ags.ArcGISFeatureLayer;
import com.esri.android.map.ags.ArcGISTiledMapServiceLayer;
import com.esri.android.map.ags.ArcGISFeatureLayer.MODE;
import com.esri.android.map.event.OnSingleTapListener;
import com.esri.android.map.event.OnStatusChangedListener;
import com.esri.arcgis.android.samples.attributeeditor.FeatureLayerUtils.FieldType;
import com.esri.core.geometry.Envelope;
import com.esri.core.geometry.Point;
import com.esri.core.map.CallbackListener;
import com.esri.core.map.FeatureEditResult;
import com.esri.core.map.FeatureSet;
import com.esri.core.map.Graphic;
import com.esri.core.symbol.SimpleFillSymbol;
import com.esri.core.symbol.SimpleLineSymbol;
import com.esri.core.tasks.SpatialRelationship;
import com.esri.core.tasks.ags.query.Query;

/**
 * Main activity class for the Attribute Editor Sample
 */
public class AttributeEditorActivity extends Activity {

  MapView mapView;

  ArcGISFeatureLayer featureLayer;
  ArcGISDynamicMapServiceLayer dmsl;

  Point pointClicked;

  LayoutInflater inflator;

  AttributeListAdapter listAdapter;
  
  Envelope initextent;

  ListView listView;

  View listLayout;

  public static final String TAG = "AttributeEditorSample";

  static final int ATTRIBUTE_EDITOR_DIALOG_ID = 1;

  @Override
  public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

   
   
    mapView = new MapView(this);
	initextent = new Envelope(-10868502.895856911, 4470034.144641369,
			-10837928.084542884, 4492965.25312689);
	mapView.setExtent(initextent, 0);
	ArcGISTiledMapServiceLayer tmsl = new ArcGISTiledMapServiceLayer(
			"http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer");
	mapView.addLayer(tmsl);

	dmsl = new ArcGISDynamicMapServiceLayer("http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/Petroleum/KSFields/MapServer");
	mapView.addLayer(dmsl);

	featureLayer = new ArcGISFeatureLayer(
			"http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/Petroleum/KSFields/FeatureServer/0",
			MODE.SELECTION);
	setContentView(mapView);
	
    SimpleFillSymbol sfs = new SimpleFillSymbol(Color.TRANSPARENT);
    sfs.setOutline(new SimpleLineSymbol(Color.YELLOW, 3));
    featureLayer.setSelectionSymbol(sfs);

    // set up local variables
    inflator = LayoutInflater.from(getApplicationContext());
    listLayout = inflator.inflate(R.layout.list_layout, null);
    listView = (ListView) listLayout.findViewById(R.id.list_view);

    // Create a new AttributeListAdapter when the feature layer is initialized
    if (featureLayer.isInitialized()) {

      listAdapter = new AttributeListAdapter(this, featureLayer.getFields(), featureLayer.getTypes(),
          featureLayer.getTypeIdField());

    } else {

      featureLayer.setOnStatusChangedListener(new OnStatusChangedListener() {

        private static final long serialVersionUID = 1L;

        public void onStatusChanged(Object source, STATUS status) {

          if (status == STATUS.INITIALIZED) {
            listAdapter = new AttributeListAdapter(AttributeEditorActivity.this, featureLayer.getFields(), featureLayer
                .getTypes(), featureLayer.getTypeIdField());
          }
        }
      });
    }

    // Set tap listener for MapView
    mapView.setOnSingleTapListener(new OnSingleTapListener() {

      private static final long serialVersionUID = 1L;

      public void onSingleTap(float x, float y) {

        // convert event into screen click
        pointClicked = mapView.toMapPoint(x, y);

        // build a query to select the clicked feature
        Query query = new Query();
        query.setOutFields(new String[] { "*" });
        query.setSpatialRelationship(SpatialRelationship.INTERSECTS);
        query.setGeometry(pointClicked);
        query.setInSpatialReference(mapView.getSpatialReference());

        // call the select features method and implement the callbacklistener
        featureLayer.selectFeatures(query, ArcGISFeatureLayer.SELECTION_METHOD.NEW, new CallbackListener<FeatureSet>() {

          // handle any errors
          public void onError(Throwable e) {

            Log.d(TAG, "Select Features Error" + e.getLocalizedMessage());

          }

          public void onCallback(FeatureSet queryResults) {

            if (queryResults.getGraphics().length > 0) {

              Log.d(
                  TAG,
                  "Feature found id="
                      + queryResults.getGraphics()[0].getAttributeValue(featureLayer.getObjectIdField()));

              // set new data and notify adapter that data has changed
              listAdapter.setFeatureSet(queryResults);
              listAdapter.notifyDataSetChanged();

              // This callback is not run in the main UI thread. All GUI
              // related events must run in the UI thread,
              // therefore use the Activity.runOnUiThread() method. See
              // http://developer.android.com/reference/android/app/Activity.html#runOnUiThread(java.lang.Runnable)
              // for more information.
              AttributeEditorActivity.this.runOnUiThread(new Runnable() {

                public void run() {

                  // show the editor dialog.
                  showDialog(ATTRIBUTE_EDITOR_DIALOG_ID);

                }
              });
            }
          }
        });
      }
    });

    // TODO handle rotation
  }

  /**
   * Overidden method from Activity class - this is the recommended way of creating dialogs
   */
  @Override
  protected Dialog onCreateDialog(int id) {

    switch (id) {

      case ATTRIBUTE_EDITOR_DIALOG_ID:

        // create the attributes dialog
        Dialog dialog = new Dialog(this);
        listView.setAdapter(listAdapter);
        dialog.setContentView(listLayout);
        dialog.setTitle("Edit Attributes");

        // set button on click listeners, setting as XML attributes doesn't work
        // due to a scope/thread issue
        Button btnEditCancel = (Button) listLayout.findViewById(R.id.btn_edit_discard);
        btnEditCancel.setOnClickListener(returnOnClickDiscardChangesListener());

        Button btnEditApply = (Button) listLayout.findViewById(R.id.btn_edit_apply);
        btnEditApply.setOnClickListener(returnOnClickApplyChangesListener());

        return dialog;
    }
    return null;
  }

  /**
   * Helper method to return an OnClickListener for the Apply button
   */
  public OnClickListener returnOnClickApplyChangesListener() {

    return new OnClickListener() {

      public void onClick(View v) {

        boolean isTypeField = false;
        boolean hasEdits = false;
        boolean updateMapLayer = false;
        Map<String, Object> attrs = new HashMap<String, Object>();

        // loop through each attribute and set the new values if they have
        // changed
        for (int i = 0; i < listAdapter.getCount(); i++) {

          AttributeItem item = (AttributeItem) listAdapter.getItem(i);
          String value = "";

          // check to see if the View has been set
          if (item.getView() != null) {

            // determine field type and therefore View type
            if (item.getField().getName().equals(featureLayer.getTypeIdField())) {
              // drop down spinner

              Spinner spinner = (Spinner) item.getView();
              // get value for the type
              String typeName = spinner.getSelectedItem().toString();
              value = FeatureLayerUtils.returnTypeIdFromTypeName(featureLayer.getTypes(), typeName);

              // update map layer as for this featurelayer the type change will
              // change the features symbol.
              isTypeField = true;

            } else if (FieldType.determineFieldType(item.getField()) == FieldType.DATE) {
              // date

              Button dateButton = (Button) item.getView();
              value = dateButton.getText().toString();

            } else {
              // edit text

              EditText editText = (EditText) item.getView();
              value = editText.getText().toString();

            }

            // try to set the attribute value on the graphic and see if it has
            // been changed
            boolean hasChanged = FeatureLayerUtils.setAttribute(attrs, listAdapter.featureSet.getGraphics()[0],
                item.getField(), value, listAdapter.formatter);

            // if a value has for this field, log this and set the hasEdits
            // boolean to true
            if (hasChanged) {

              Log.d(TAG, "Change found for field=" + item.getField().getName() + " value = " + value
                  + " applyEdits() will be called");
              hasEdits = true;

              // If the change was from a Type field then set the dynamic map
              // service to update when the edits have been applied, as the
              // renderer of the feature will likely change
              if (isTypeField) {

                updateMapLayer = true;

              }
            }

            // check if this was a type field, if so set boolean back to false
            // for next field
            if (isTypeField) {

              isTypeField = false;
            }
          }
        }

        // check there have been some edits before applying the changes
        if (hasEdits) {

          // set objectID field value from graphic held in the featureset
        	attrs.put(featureLayer.getObjectIdField(),listAdapter.featureSet.getGraphics()[0].getAttributeValue(featureLayer.getObjectIdField()));
			Graphic newGraphic = new Graphic(null, null, attrs);
            featureLayer.applyEdits(null, null, new Graphic[] { newGraphic }, createEditCallbackListener(updateMapLayer));
        }

        // close the dialog
        dismissDialog(ATTRIBUTE_EDITOR_DIALOG_ID);

      }
    };

  }

  /**
   * OnClick method for the Discard button
   */
  public OnClickListener returnOnClickDiscardChangesListener() {

    return new OnClickListener() {

      public void onClick(View v) {

        // close the dialog
        dismissDialog(ATTRIBUTE_EDITOR_DIALOG_ID);

      }
    };

  }

  /**
   * Helper method to create a CallbackListener<FeatureEditResult[][]>
   * 
   * @return CallbackListener<FeatureEditResult[][]>
   */
  CallbackListener<FeatureEditResult[][]> createEditCallbackListener(final boolean updateLayer) {

    return new CallbackListener<FeatureEditResult[][]>() {

      public void onCallback(FeatureEditResult[][] result) {

        // check the response for success or failure
        if (result[2] != null && result[2][0] != null && result[2][0].isSuccess()) {

          Log.d(AttributeEditorActivity.TAG, "Success updating feature with id=" + result[2][0].getObjectId());

          // see if we want to update the dynamic layer to get new symbols for
          // updated features
          if (updateLayer) {

              dmsl.refresh();

          }
        }
      }

      public void onError(Throwable e) {

        Log.d(AttributeEditorActivity.TAG, "error updating feature: " + e.getLocalizedMessage());

      }
    };
  }
}
/* Copyright 2014 ESRI
 *
 * All rights reserved under the copyright laws of the United States
 * and applicable international laws, treaties, and conventions.
 *
 * You may freely redistribute and use this sample code, with or
 * without modification, provided you include the original copyright
 * notice and use restrictions.
 *
 * See the sample code usage restrictions document for further information.
 *
 */

package com.esri.arcgis.android.samples.attributeeditor;

import java.text.DateFormat;
import java.text.ParseException;
import java.util.Calendar;
import java.util.HashMap;

import com.esri.arcgis.android.samples.attributeeditor.FeatureLayerUtils.FieldType;
import com.esri.core.map.FeatureSet;
import com.esri.core.map.FeatureType;
import com.esri.core.map.Field;

import android.app.DatePickerDialog;
import android.app.DatePickerDialog.OnDateSetListener;
import android.content.Context;
import android.database.DataSetObserver;
import android.text.InputFilter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.TextView.BufferType;

/**
 * Adapter class which contains the logic of how to use and process the
 * FeatureLayers Fields and Attributes into an List Layout
 */
public class AttributeListAdapter extends BaseAdapter {

	FeatureSet featureSet;

	Field[] fields;

	FeatureType[] types;

	String typeIdFieldName;

	Context context;

	LayoutInflater lInflator;

	int[] editableFieldIndexes;

	String[] typeNames;

	HashMap<String, FeatureType> typeMap;

	AttributeItem[] items;

	DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.SHORT,
			DateFormat.SHORT);

	/**
	 * Constructor
	 */
	public AttributeListAdapter(Context context, Field[] fields,
			FeatureType[] types, String typeIdFieldName) {

		this.context = context;
		this.lInflator = LayoutInflater.from(context);
		this.fields = fields;
		this.types = types;
		this.typeIdFieldName = typeIdFieldName;

		// this.fieldsTemplateMap = createInitialFieldTemplateMap(this.fields);
		// parseTypes();

		// Setup processed variables
		this.editableFieldIndexes = FeatureLayerUtils
				.createArrayOfFieldIndexes(this.fields);
		this.typeNames = FeatureLayerUtils.createTypeNameArray(this.types);
		this.typeMap = FeatureLayerUtils.createTypeMapByValue(this.types);

		// register dataset observer to track when the underlying data is
		// changed
		this.registerDataSetObserver(new DataSetObserver() {

			public void onChanged() {

				// clear the array of attribute items
				AttributeListAdapter.this.items = new AttributeItem[AttributeListAdapter.this.editableFieldIndexes.length];

			}
		});
	}

	/**
	 * Implemented method from BaseAdapter class
	 */
	public int getCount() {

		return this.editableFieldIndexes.length;

	}

	/**
	 * Implemented method from BaseAdapter class. This method returns the actual
	 * data associated with a row in the list. In this case we return the field
	 * along with the field value as a custom object. We subsequently add the
	 * View which displays the value to this object so we can retrieve it when
	 * applying edits.
	 */
	public Object getItem(int position) {

		// get field associated with the position from the editableFieldIndexes
		// array created at startup
		int fieldIndex = this.editableFieldIndexes[position];

		AttributeItem row = null;

		// check to see if we have already created an attribute item if not
		// create
		// one
		if (items[position] == null) {

			// create new Attribute item for persisting the data for subsequent
			// events
			row = new AttributeItem();
			row.setField(this.fields[fieldIndex]);
			Object value = this.featureSet.getGraphics()[0]
					.getAttributeValue(fields[fieldIndex].getName());
			row.setValue(value);
			items[position] = row;

		} else {

			// reuse existing item to ensure View instance is kept.
			row = items[position];

		}

		return row;

	}

	/**
	 * Implemented method from BaseAdapter class
	 */
	public long getItemId(int position) {

		return position;

	}

	/**
	 * Implemented method from BaseAdapter class. This is the main method for
	 * returning a View which corresponds to a row in the list. This calls the
	 * getItem() method to get the data. It is called multiple times by the
	 * ListView and may be improved on by saving the previous result.
	 */
	public View getView(int position, View convertView, ViewGroup parent) {

		View container = null;

		AttributeItem item = (AttributeItem) getItem(position);

		// check field type
		// TODO if you want to support domains, add checks here and use the
		// createSpinnerViewFromArray to create spinners
		if (item.getField().getName().equals(this.typeIdFieldName)) {
			// This is the featurelayers type field

			container = lInflator.inflate(R.layout.item_spinner, null);
			// get the types name for this feature from the available values
			String typeStringValue = this.typeMap.get(
					item.getValue().toString()).getName();
			Spinner spinner = createSpinnerViewFromArray(container,
					item.getField(), typeStringValue, this.typeNames);
			item.setView(spinner);

			// TODO set listener to change types associated domain fields if
			// required

		} else if (FieldType.determineFieldType(item.getField()) == FieldType.DATE) {
			// create date picker for date fields

			container = lInflator.inflate(R.layout.item_date, null);
			long date = Long.parseLong(item.getValue().toString());

			Button dateButton = createDateButtonFromLongValue(container,
					item.getField(), date);
			item.setView(dateButton);

		} else {
			// create number and text fields
			// View object for saving in the AttrbuteItem once it has been set
			// up, for
			// accessing later when we apply edits.
			View valueView = null;

			if (FieldType.determineFieldType(item.getField()) == FieldType.STRING) {

				// get the string specific layout
				container = lInflator.inflate(R.layout.item_text, null);
				valueView = createAttributeRow(container, item.getField(),
						item.getValue());

			} else if (FieldType.determineFieldType(item.getField()) == FieldType.NUMBER) {

				// get the number specific layout
				container = lInflator.inflate(R.layout.item_number, null);
				valueView = createAttributeRow(container, item.getField(),
						item.getValue());

			} else if (FieldType.determineFieldType(item.getField()) == FieldType.DECIMAL) {

				// get the decimal specific layout
				container = lInflator.inflate(R.layout.item_decimal, null);
				valueView = createAttributeRow(container, item.getField(),
						item.getValue());

			}

			// set the rows view onto the item so it can be received when
			// applying
			// edits
			item.setView(valueView);

		}

		return container;

	}

	/**
	 * Sets the FeatureSet, called by the activity when a new queryResult is
	 * returned
	 * 
	 * @param featureSet
	 */
	public void setFeatureSet(FeatureSet featureSet) {

		this.featureSet = featureSet;

	}

	/**
	 * Helper method to create a spinner for a field and insert it into the View
	 * container. This uses, the String[] to create the list, and selects the
	 * value that is passed in from the list (the features value). Can be used
	 * for domains as well as types.
	 */
	Spinner createSpinnerViewFromArray(View container, Field field,
			Object value, String[] values) {

		TextView fieldAlias = (TextView) container
				.findViewById(R.id.field_alias_txt);
		Spinner spinner = (Spinner) container
				.findViewById(R.id.field_value_spinner);
		fieldAlias.setText(field.getAlias());
		spinner.setPrompt(field.getAlias());

		ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<String>(
				this.context, android.R.layout.simple_spinner_item, values);
		spinnerAdapter
				.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
		spinner.setAdapter(spinnerAdapter);
		// set current selection based on the value passed in
		spinner.setSelection(spinnerAdapter.getPosition(value.toString()));

		return spinner;
	}

	/**
	 * Helper method to create a date button, with appropriate onClick and
	 * onDateSet listeners to handle dates as a long (milliseconds since 1970),
	 * it uses the locale and presents a button with the date and time in short
	 * format.
	 */
	Button createDateButtonFromLongValue(View container, Field field, long date) {

		TextView fieldAlias = (TextView) container
				.findViewById(R.id.field_alias_txt);
		Button dateButton = (Button) container
				.findViewById(R.id.field_date_btn);
		fieldAlias.setText(field.getAlias());

		Calendar c = Calendar.getInstance();
		c.setTimeInMillis(date);
		dateButton.setText(formatter.format(c.getTime()));

		addListenersToDatebutton(dateButton);

		return dateButton;
	}

	/**
	 * Helper method to add the field alias and the fields value into columns of
	 * a view using standard id names. If the field has a length set, then this
	 * is used to constrain the EditText's allowable characters. No validation
	 * is applied here, it is assumed that the container has this set already
	 * (in XML).
	 */
	View createAttributeRow(View container, Field field, Object value) {

		TextView fieldAlias = (TextView) container
				.findViewById(R.id.field_alias_txt);
		EditText fieldValue = (EditText) container
				.findViewById(R.id.field_value_txt);
		fieldAlias.setText(field.getAlias());

		// set the length of the text field and its value
		if (field.getLength() > 0) {
			InputFilter.LengthFilter filter = new InputFilter.LengthFilter(
					field.getLength());
			fieldValue.setFilters(new InputFilter[] { filter });
		}

		Log.d(AttributeEditorActivity.TAG, "value is null? =" + (value == null));
		Log.d(AttributeEditorActivity.TAG, "value=" + value);

		if (value != null) {
			fieldValue.setText(value.toString(), BufferType.EDITABLE);
		} else {
			fieldValue.setText("", BufferType.EDITABLE);
		}

		return fieldValue;
	}

	/**
	 * Helper method to create the date button and its associated events
	 */
	void addListenersToDatebutton(Button dateButton) {

		// create new onDateSetLisetener with the button associated with it
		final ListOnDateSetListener listener = new ListOnDateSetListener(
				dateButton);

		// add a click listener to the button
		dateButton.setOnClickListener(new View.OnClickListener() {

			public void onClick(View v) {

				// if its a date, get the milliseconds value
				Calendar c = Calendar.getInstance();
				formatter.setCalendar(c);

				try {

					// parse to a double
					Button button = (Button) v;
					c.setTime(formatter.parse(button.getText().toString()));

				} catch (ParseException e) {
					// do nothing as should parse
				}

				int year = c.get(Calendar.YEAR);
				int month = c.get(Calendar.MONTH);
				int day = c.get(Calendar.DAY_OF_MONTH);

				// show date picker with date set to the items value (hence
				// built
				// outside of onCreateDialog)
				// TODO implement time picker if required, this picker only
				// supports
				// date and therefore showing the dialog will cause a change in
				// the time
				// value for the field
				DatePickerDialog dialog = new DatePickerDialog(context,
						listener, year, month, day);
				dialog.show();
			}
		});
	}

	/**
	 * Inner class for handling date change events from the date picker dialog
	 */
	class ListOnDateSetListener implements OnDateSetListener {

		Button button;

		public ListOnDateSetListener(Button button) {

			this.button = button;
		}

		public void onDateSet(DatePicker view, int year, int month, int day) {

			Calendar c = Calendar.getInstance();
			c.set(year, month, day);

			// Update the button to show the chosen date
			button.setText(formatter.format(c.getTime()));

		}
	}
}
/* Copyright 2014 ESRI
 *
 * All rights reserved under the copyright laws of the United States
 * and applicable international laws, treaties, and conventions.
 *
 * You may freely redistribute and use this sample code, with or
 * without modification, provided you include the original copyright
 * notice and use restrictions.
 *
 * See the sample code usage restrictions document for further information.
 *
 */

package com.esri.arcgis.android.samples.attributeeditor;

import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

import com.esri.core.map.FeatureType;
import com.esri.core.map.Field;
import com.esri.core.map.Graphic;

public class FeatureLayerUtils {

	public enum FieldType {
		NUMBER, STRING, DECIMAL, DATE;

		public static FieldType determineFieldType(Field field) {

			if (field.getFieldType() == Field.esriFieldTypeString) {
				return FieldType.STRING;
			} else if (field.getFieldType() == Field.esriFieldTypeSmallInteger
					|| field.getFieldType() == Field.esriFieldTypeInteger) {
				return FieldType.NUMBER;
			} else if (field.getFieldType() == Field.esriFieldTypeSingle
					|| field.getFieldType() == Field.esriFieldTypeDouble) {
				return FieldType.DECIMAL;
			} else if (field.getFieldType() == Field.esriFieldTypeDate) {
				return FieldType.DATE;
			}
			return null;
		}
	}

	/**
	 * Helper method to determine if a field should be shown in the list for
	 * editing
	 */
	public static boolean isFieldValidForEditing(Field field) {

		int fieldType = field.getFieldType();

		if (field.isEditable() && fieldType != Field.esriFieldTypeOID
				&& fieldType != Field.esriFieldTypeGeometry
				&& fieldType != Field.esriFieldTypeBlob
				&& fieldType != Field.esriFieldTypeRaster
				&& fieldType != Field.esriFieldTypeGUID
				&& fieldType != Field.esriFieldTypeXML) {

			return true;

		}

		return false;
	}

	/**
	 * Helper method to set attributes on a graphic. Only sets the attributes on
	 * the newGraphic variable if the value has changed. Returns true if the
	 * value has changed and has been set on the graphic.
	 * 
	 * @return boolean hasValueChanged
	 */
	public static boolean setAttribute(Map<String, Object> attrs,
			Graphic oldGraphic, Field field, String value, DateFormat formatter) {

		boolean hasValueChanged = false;

		// if its a string, and it has changed from the oldGraphic value
		if (FieldType.determineFieldType(field) == FieldType.STRING) {

			if (!value.equals(oldGraphic.getAttributeValue(field.getName()))) {

				// set the value as it is
				attrs.put(field.getName(), value);
				hasValueChanged = true;

			}
		} else if (FieldType.determineFieldType(field) == FieldType.NUMBER) {

			// if its an empty string, its a 0 number value (nulls not
			// supported), check this is a
			// change before making it a 0
			if (value.equals("")
					&& oldGraphic.getAttributeValue(field.getName()) != Integer
							.valueOf(0)) {

				// set a null value on the new graphic
				attrs.put(field.getName(), 0);
				hasValueChanged = true;

			} else {

				// parse as an int and check this is a change
				int intValue = Integer.parseInt(value);
				if (intValue != Integer.parseInt(oldGraphic.getAttributeValue(
						field.getName()).toString())) {

					attrs.put(field.getName(), Integer.valueOf(intValue));
					hasValueChanged = true;

				}
			}
		} else if (FieldType.determineFieldType(field) == FieldType.DECIMAL) {

			// if its an empty string, its a 0 double value (nulls not
			// supported), check this is a
			// change before making it a 0
			if ((value.equals("") && oldGraphic.getAttributeValue(field
					.getName()) != Double.valueOf(0))) {

				// set a null value on the new graphic
				attrs.put(field.getName(), 0.0);
				hasValueChanged = true;

			} else {

				// parse as an double and check this is a change
				double dValue = Double.parseDouble(value);
				if (dValue != Double.parseDouble(oldGraphic.getAttributeValue(
						field.getName()).toString())) {

					attrs.put(field.getName(), Double.valueOf(dValue));
					hasValueChanged = true;

				}
			}
		} else if (FieldType.determineFieldType(field) == FieldType.DATE) {

			// if its a date, get the milliseconds value
			Calendar c = Calendar.getInstance();
			long dateInMillis = 0;

			try {

				// parse to a double and check this is a change
				c.setTime(formatter.parse(value));
				dateInMillis = c.getTimeInMillis();

				if (dateInMillis != Long.parseLong(oldGraphic
						.getAttributeValue(field.getName()).toString())) {

					attrs.put(field.getName(), Long.valueOf(dateInMillis));
					hasValueChanged = true;
				}
			} catch (ParseException e) {
				// do nothing
			}
		}
		// }

		return hasValueChanged;

	}

	/**
	 * Helper method to find the Types actual value from its name
	 */
	public static String returnTypeIdFromTypeName(FeatureType[] types,
			String name) {

		for (FeatureType type : types) {
			if (type.getName().equals(name)) {
				return type.getId();
			}
		}
		return null;
	}

	/**
	 * Helper method to setup the editable field indexes and store them for
	 * later retrieval in getItem() method.
	 */
	public static int[] createArrayOfFieldIndexes(Field[] fields) {

		// process count of fields and which are available for editing
		ArrayList<Integer> list = new ArrayList<Integer>();
		int fieldCount = 0;

		for (int i = 0; i < fields.length; i++) {

			if (isFieldValidForEditing(fields[i])) {

				list.add(Integer.valueOf(i));
				fieldCount++;

			}
		}

		int[] editableFieldIndexes = new int[fieldCount];

		for (int x = 0; x < list.size(); x++) {

			editableFieldIndexes[x] = list.get(x).intValue();

		}

		return editableFieldIndexes;
	}

	/**
	 * Helper method to create a String array of Type values for populating a
	 * spinner
	 */
	public static String[] createTypeNameArray(FeatureType[] types) {

		String[] typeNames = new String[types.length];
		int i = 0;
		for (FeatureType type : types) {

			typeNames[i] = type.getName();
			i++;

		}

		return typeNames;

	}

	/**
	 * Helper method to create a HashMap of types using the Id (value) as the
	 * key
	 */
	public static HashMap<String, FeatureType> createTypeMapByValue(
			FeatureType[] types) {

		HashMap<String, FeatureType> typeMap = new HashMap<String, FeatureType>();

		for (FeatureType type : types) {

			typeMap.put(type.getId(), type);

		}

		return typeMap;

	}

}
Feedback on this topic?