We continue our discussion from Part 1 of Developing Proximity Alerts for Mobile Applications using the Android Platform. In this last part of my two-part post, we create the Mobile Proximity Alert Application, implementing the actual design we discussed in Part 1.
Now the fun begins!
One Data Entity
As far as data models go, this one is simple. One entity. The ProximityAlertEntity. As well as representing common DTO functionality, we will expand the ProximityEntity to include the repository functionality. After all, this is a fairly simple application. First, let’s look at the DTO functionality. It includes both the fields representing our alert needs and the necessary JSON serialization/deserialization methods.
public class ProximityAlertReminder extends BaseEntity {
private static long nextId = 0;
public long Id = -1;
public double Lat = 0;
public double Lng = 0;
public String Name = "";
public String Desc = "";
public int Month = -1;
public int DayOfMonth = -1;
public int Year = -1;
public ArrayList<String> Items = new ArrayList<String>();
public ProximityAlertReminder(long id, double lat, double lng, String name, String desc, int month, int dayOfMonth, int year) {
this.Lat = lat;
this.Lng = lng;
this.Id = id;
this.Name = name;
this.Desc = desc;
this.Month = month;
this.DayOfMonth = dayOfMonth;
this.Year = year;
}
public ProximityAlertReminder(double lat, double lng, String name, String desc, int month, int dayOfMonth, int year) {
this(nextId++, lat, lng, name, desc, month, dayOfMonth, year);
}
public ProximityAlertReminder(long id, String name, String desc) {
this(id, 0.0, 0.0, name, desc, Calendar.getInstance().get(Calendar.MONTH), Calendar.getInstance().get(Calendar.DAY_OF_MONTH), Calendar.getInstance().get(Calendar.YEAR));
}
public ProximityAlertReminder(JSONObject obj) throws JSONException {
deserializeFromObj(obj);
}
public ProximityAlertReminder(String serializedObj) throws JSONException {
deserialize(serializedObj);
}
public Object getId() {
return this.Id;
}
public JSONObject serializeToObj() {
JSONObject serializedObj = new JSONObject();
try {
serializedObj.put("Id", this.Id);
serializedObj.put("Lat", this.Lat);
serializedObj.put("Lng", this.Lng);
serializedObj.put("Name", this.Name);
serializedObj.put("Desc", this.Desc);
serializedObj.put("Month", this.Month);
serializedObj.put("DayOfMonth", this.DayOfMonth);
serializedObj.put("Year", this.Year);
ArrayList itemObjs = new ArrayList();
for (String item:this.Items) {
JSONObject serializedItemObj = new JSONObject();
serializedItemObj.put("Item", item);
itemObjs.add(serializedItemObj);
}
serializedObj.put("Items", new JSONArray(itemObjs));
}
catch(Exception ex) {
if (debug) Log.e(AppSettings.DEBUG_TAG, ex.getMessage(), ex);
}
return serializedObj;
}
public AlertReminder deserializeFromObj(JSONObject obj) throws JSONException {
this.Id = obj.getLong("Id");
this.Lat = obj.getDouble("Lat");
this.Lng = obj.getDouble("Lng");
this.Name = obj.getString("Name");
this.Desc = obj.getString("Desc");
this.Month = obj.getInt("Month");
this.DayOfMonth = obj.getInt("DayOfMonth");
this.Year = obj.getInt("Year");
if (obj.has("Items")) {
JSONArray jsonObjs = obj.getJSONArray("Items");
this.Items = new ArrayList(jsonObjs.length());
for (int i=0; i
JSONObject itemObj = jsonObjs.getJSONObject(i);
this.Items.add(itemObj.getString("Item"));
}
}
return this;
}
}
The ProximityAlertReminder repository functions are represented as static methods. Note that we are writing using SharedPreferences and not the database for persistence.
public class ProximityAlertReminder extends BaseEntity<ProximityAlertReminder> {
public static ArrayList<ProximityAlertReminder> load(Context caller) {
ArrayList<ProximityAlertReminder> reminders = new ArrayList<ProximityAlertReminder>();
try {
SharedPreferences settingsActivity = PreferenceManager.getDefaultSharedPreferences(caller);
String serializedReminders = settingsActivity.getString(caller.getString(R.string.reminders), "").trim();
reminders = new ArrayList<ProximityAlertReminder>();
if (serializedReminders.length() > 0) {
JSONArray jsonObjs = new JSONArray(serializedReminders);
for (int i=0; i<jsonObjs.length(); i++) {
JSONObject itemObj = jsonObjs.getJSONObject(i);
reminders.add(new ProximityAlertReminder(itemObj));
}
}
if (reminders.size() == 0) {
ProximityAlertReminder addReminder = new ProximityAlertReminder(-2, "<click to add new alert>", "Add";
reminders.add(0, addReminder);
}
}
catch (Exception ex) {
Log.e(AppSettings.DEBUG_TAG, ex.getMessage(), ex);
}
return reminders;
}
public static ArrayList<ProximityAlertReminder> add(Context caller, ProximityAlertReminder reminder) {
ArrayList<ProximityAlertReminder> reminders = load(caller);
reminders.add(reminder);
save(caller, reminders);
return reminders;
}
public static boolean save(Context caller, ArrayList<ProximityAlertReminder> reminders) {
boolean success = false;
try {
SharedPreferences.Editor prefEditor = PreferenceManager.getDefaultSharedPreferences(caller).edit();
ArrayList<JSONObject> itemObjs = new ArrayList<JSONObject>(reminders.size());
for (ProximityAlertReminder item:reminders) {
JSONObject serializedItemObj = new JSONObject(item.serialize());
itemObjs.add(serializedItemObj);
}
prefEditor.putString(caller.getString(R.string.reminders), new JSONArray(itemObjs).toString());
prefEditor.commit();
success = true;
}
catch (Exception ex) {
Log.e(AppSettings.DEBUG_TAG, ex.getMessage(), ex);
success = false;
}
return success;
}
public static boolean deleteAll(Context caller) {
boolean success = false;
try {
SharedPreferences.Editor prefEditor = PreferenceManager.getDefaultSharedPreferences(caller).edit();
prefEditor.putString(caller.getString(R.string.reminders), "");
prefEditor.commit();
success = true;
}
catch (Exception ex) {
Log.e(AppSettings.DEBUG_TAG, ex.getMessage(), ex);
success = false;
}
return success;
}
}
These represent the CRUD based methods used in common entity storage and retrieval.
Setting Proximity Alerts
Let’s discuss how we will actually set a proximity alert. We will take the approach that we will set a proximity alert based on our current physical location. First, we must have a user interface that allow us to display the list of current proximity alerts, and add new proximity alerts. Our ProximityAlertHomeActivity view will encompass the former, and, from an action initiated at the home activity, the ProximityAlertSetActivity will provide the later, the actual create and edit alert functionality. Additionally, the ProximityAlertHelper class comprises the proximity alert technical functionality that makes this all work.
A screenshot of our ProximityAlertHomeActivity Screen is as follows:
The ProximityAlertHomeActivity class is as follows:
public class ProximityAlertHomeActivity extends ListActivity {
private ArrayList<ProximityAlert> reminders = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String context = Context.LOCATION_SERVICE;
ProximityAlertHelper.getInstance().initializeLocationManager(this, (LocationManager) getSystemService(context));
loadReminders();
}
public void onListItemClick(ListView parent, View v, int position, long id)
{
if (position == 0) {
Intent intent = new Intent(this, ProximityAlertSetActivity.class);
startActivityForResult(intent, 0);
return;
}
editReminder(v, position);
}
// Creates the menu items
public boolean onCreateOptionsMenu(android.view.Menu menu) {
getMenuInflater().inflate(R.menu.home, menu);
return true;
}
// Handles item selections
public boolean onOptionsItemSelected(android.view.MenuItem item) {
switch (item.getItemId()) {
case R.id.home_menu_add:
Intent intent = new Intent(this, ProximityAlertSetActivity.class);
startActivityForResult(intent, 0);
return true;
case R.id.home_menu_delall:
DialogHelper.getInstance().displayConfirmationDialog(this, this.dialogResultHandler, "Confirmation", "Would you like to delete all alerts now?");
return true;
case R.id.home_menu_settings:
intent = new Intent(this, ProximityAlertPreferencesActivity.class);
startActivityForResult(intent, 0);
return true;
case R.id.home_menu_about:
showDialog(0);
return true;
}
return false;
}
@Override
public void finish() {
ProximityAlertHelper.getInstance().closeLocationManager(this);
super.finish();
}
@Override
public void onBackPressed() {
ProximityAlertHelper.getInstance().closeLocationManager(this);
super.onBackPressed();
}
protected Handler dialogResultHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (Boolean.parseBoolean(msg.obj.toString())) {
ProximityAlertHomeActivity.this.reminders.clear();
ProximityAlertHelper.getInstance().removeAlerts(ProximityAlertHomeActivity.this);
ProximityAlert.deleteAll(ProximityAlertHomeActivity.this);
Msg.ToastShort(ProximityAlertHomeActivity.this, "All Alerts Deleted");
loadReminders();
}
else {
Msg.ToastShort(ProximityAlertHomeActivity.this, "Canceled");
}
}
};
@Override
protected Dialog onCreateDialog(int id) {
// we have only one - defaulted to 0
return DialogHelper.getInstance().displayAboutDialog(this);
}
@Override
protected void onResume() {
super.onResume();
loadReminders();
}
protected void loadReminders() {
setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, loadFormattedAlertReminders()));
ProximityAlertHelper.getInstance().addProximityAlerts(this, this.reminders);
}
protected void editReminder(View v, int position) {
Intent intent = new Intent(v.getContext(), ProximityAlertSetActivity.class);
intent.putExtra("current", this.reminders.get(position).serialize());
startActivityForResult(intent, 0);
}
protected ArrayList<String> loadFormattedAlertReminders()
{
this.reminders = ProximityAlert.load(this);
ArrayList<String> formattedReminders = new ArrayList<String>();
for (ProximityAlert reminder:this.reminders) {
formattedReminders.add(reminder.Name);
}
return formattedReminders;
}
}
Note that much of the technical work is done by the ProximityAlertHelper class.
Of particular interest, is the fact that within the loadReminders(…) method, we both
- load the proximity alerts from persistent storage (e.g., SharedPreferences in our case) AND
- add them to the LocationManager
via the ProximityAlertHelper class.
Also of interest, within the onResume(…) method, we call the loadReminders(…) method to reload our proximity alerts upon resume.
Lastly, within the finish(…) method, we call AlertProximityHelper.getInstance(…).closeLocationManager(…) to both
- remove the proximity alerts from the LocationManager AND
- unregister our custom BroadcastReceiver called ProximityIntentReceiver (more on this later).
A screenshot of the ProximityAlertSetActivityScreen is as follows:
The following introduces our ProximityAlertSetActivity class.
public class ProximityAlertSetActivity extends Activity {
private double lat = 00;
private double lng = 00;
private boolean newReminder = true;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.alert_reminder);
this.newReminder = !(getIntent().getExtras() != null && getIntent().getExtras().containsKey("current"));
if (this.newReminder) {
String context = Context.LOCATION_SERVICE;
updateWithNewLocation(((LocationManager) getSystemService(context)).getLastKnownLocation(ProximityAlertHelper.getInstance().Provider));
}
else {
ProximityAlert current = retrieveCurrentAlertReminder();
((TextView) findViewById(R.id.TextView_reminder_location_hdr)).setText("Lat:" + current.Lat + "\nLong:" + current.Lng);
((TextView) findViewById(R.id.EditText_reminder_name)).setText(current.Name);
((TextView) findViewById(R.id.EditText_reminder_desc)).setText(current.Desc);
DatePicker picker = (DatePicker)findViewById(R.id.DatePicker_reminder_expiration);
picker.updateDate(current.Year, current.Month, current.DayOfMonth);
}
}
// Creates the menu items
public boolean onCreateOptionsMenu(android.view.Menu menu) {
getMenuInflater().inflate(R.menu.alert_reminder_add, menu);
return true;
}
// Handles item selections
public boolean onOptionsItemSelected(android.view.MenuItem item) {
switch (item.getItemId()) {
case R.id.add_menu_save:
addAlert();
return true;
case R.id.add_menu_cancel:
DialogHelper.getInstance().displayConfirmationDialog(this, this.dialogCancelResultHandler, "Confirmation", "Would you like to cancel adding this alert?");
return true;
case R.id.add_menu_del:
DialogHelper.getInstance().displayConfirmationDialog(this, this.dialogDeleteResultHandler, "Confirmation", "Would you like to delete this alert?");
return true;
case R.id.add_menu_settings:
Intent intent = new Intent(this, ProximityAlertPreferencesActivity.class);
startActivityForResult(intent, 0);
return true;
case R.id.add_menu_about:
showDialog(0);
return true;
}
return false;
}
protected ProximityAlert retrieveCurrentAlertReminder() {
ProximityAlert current = null;
try {
current = new ProximityAlert(getIntent().getExtras().getString("current"));
}
catch (JSONException ex) {
Log.e(AppSettings.DEBUG_TAG, "JSON failure in AlertReminderActivity", ex);
}
return current;
}
protected void addAlert() {
String name = ((TextView) findViewById(R.id.EditText_reminder_name)).getText().toString();
String desc = ((TextView) findViewById(R.id.EditText_reminder_desc)).getText().toString();
DatePicker picker = (DatePicker)findViewById(R.id.DatePicker_reminder_expiration);
// add proximity alert intent
if (newReminder) {
ProximityAlert reminder = new ProximityAlert(this.lat, this.lng, name, desc, picker.getMonth(), picker.getDayOfMonth(), picker.getYear());
ProximityAlertHelper.getInstance().addProximityAlerts(this, ProximityAlert.add(this, reminder));
}
finish();
}
protected Handler dialogCancelResultHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (Boolean.parseBoolean(msg.obj.toString())) {
Msg.ToastShort(ProximityAlertSetActivity.this, "Alert has been canceled!");
finish();
}
else {
Msg.ToastShort(ProximityAlertSetActivity.this, "Alert has NOT been canceled");
}
}
};
protected Handler dialogDeleteResultHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (Boolean.parseBoolean(msg.obj.toString())) {
ProximityAlert current = retrieveCurrentAlertReminder();
ProximityAlertHelper.getInstance().removeAlert(ProximityAlertSetActivity.this, current);
Msg.ToastShort(ProximityAlertSetActivity.this, "Alert has been deleted!");
finish();
}
else {
Msg.ToastShort(ProximityAlertSetActivity.this, "Alert has NOT been deleted");
}
}
};
@Override
protected Dialog onCreateDialog(int id) {
// we have only one - defaulted to 0
return DialogHelper.getInstance().displayAboutDialog(this);
}
//Register for the updates when Activity is in foreground
@Override
protected void onResume() {
super.onResume();
if (this.newReminder) {
ProximityAlertHelper.getInstance().requestLocationUpdates(this, this.locationListener);
}
}
// Stop the updates when Activity is paused
@Override
protected void onPause() {
super.onPause();
if (this.newReminder) {
String context = Context.LOCATION_SERVICE;
((LocationManager) getSystemService(context)).removeUpdates(this.locationListener);
}
}
private final LocationListener locationListener = new LocationListener() {
public void onLocationChanged(Location location) {
updateWithNewLocation(location);
}
public void onProviderDisabled(String provider) {
updateWithNewLocation(null);
}
public void onProviderEnabled(String provider) {
}
public void onStatusChanged(String provider, int status, Bundle extras) {
}
};
private void updateWithNewLocation(Location location) {
String latLongString;
TextView myLocationText = (TextView) findViewById(R.id.TextView_reminder_location_hdr);
if (location != null) {
this.lat = location.getLatitude();
this.lng = location.getLongitude();
latLongString = "Lat:" + lat + "\nLong:" + lng;
} else {
latLongString = "No location found - Please move more than " + AppSettings.MinDistance + " from current position";
}
myLocationText.setText("Your Current Position is:\n" + latLongString);
}
}
The LocationListener allows us to refresh the view (e.g., position status) as our longitude and latitude changes due to changes in our physical location. Or, more specifically, our smart phone detects changes to its physical location. Again, much of the technical lifting is done by the ProximityAlertHelper class.
And lastly, the ProximityAlertHelper class is implemented as follows:
public class ProximityAlertHelper {
public static ProximityAlertHelper getInstance() {
if (instanceOf == null) {
instanceOf = new ProximityAlertHelper();
}
return instanceOf;
}
public String Provider;
private static ProximityAlertHelper instanceOf;
private static boolean initialized = false;
private LocationListener locationListener;
private HashMap<ProximityAlert, PendingIntent> proximityIntentsMap = new HashMap<ProximityAlert, PendingIntent>();
private ProximityIntentReceiver receiver = null;
public boolean initializeLocationManager(Context context, LocationManager locationManager) {
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setAltitudeRequired(false);
criteria.setBearingRequired(false);
criteria.setCostAllowed(true);
criteria.setPowerRequirement(Criteria.POWER_LOW);
this.Provider = locationManager.getBestProvider(criteria, false);
this.receiver = new ProximityIntentReceiver();
IntentFilter filter = new IntentFilter(ProximityAlertActivity.class.getName());
context.registerReceiver(this.receiver, filter);
return initialized = true;
}
public void requestLocationUpdates(Context caller, LocationListener locationListener) {
if (initialized) {
this.locationListener = locationListener;
String context = Context.LOCATION_SERVICE;
LocationManager mgr = ((LocationManager) caller.getSystemService(context));
mgr.removeUpdates(locationListener);
mgr.removeUpdates(locationListener);
long minInc = Long.parseLong(PreferenceManager.getDefaultSharedPreferences(caller).getString("mintimeinc", "2000"));
float minDistance = Float.parseFloat(PreferenceManager.getDefaultSharedPreferences(caller).getString("mindistance", "2"));
mgr.requestLocationUpdates(this.Provider, minInc, minDistance, locationListener);
}
}
public boolean addProximityAlerts(Context caller, ArrayList<ProximityAlert> reminders) {
if (initialized) {
String context = Context.LOCATION_SERVICE;
LocationManager mgr = ((LocationManager) caller.getSystemService(context));
removeAlerts(caller);
for (int i=1; i <reminders.size(); i++) {
ProximityAlert reminder = reminders.get(i);
Intent intent = new Intent(ProximityAlertActivity.class.getName());
intent.putExtra("current", reminder.serialize());
PendingIntent proximityIntent = PendingIntent.getBroadcast(caller, i, intent, 0);
float proxRadius = Float.parseFloat(PreferenceManager.getDefaultSharedPreferences(caller).getString("proxradius", "2"));
mgr.addProximityAlert(reminder.Lat, reminder.Lng, proxRadius, -1, proximityIntent);
proximityIntentsMap.put(reminder, proximityIntent);
}
return true;
}
return false;
}
public void removeAlert(Context caller, ProximityAlert reminder) {
if (initialized) {
String context = Context.LOCATION_SERVICE;
LocationManager mgr = ((LocationManager) caller.getSystemService(context));
removeAlert(mgr, reminder);
}
}
public void removeAlerts(Context caller) {
if (initialized) {
String context = Context.LOCATION_SERVICE;
LocationManager mgr = ((LocationManager) caller.getSystemService(context));
for (ProximityAlert reminder:this.proximityIntentsMap.keySet()) {
removeAlert(mgr, reminder);
}
}
}
public void closeLocationManager(Context caller) {
if (initialized) {
try {
String context = Context.LOCATION_SERVICE;
LocationManager mgr = ((LocationManager) caller.getSystemService(context));
if (this.locationListener != null) {
mgr.removeUpdates(this.locationListener);
}
removeAlerts(caller);
caller.unregisterReceiver(this.receiver);
}
catch(Exception ex) {
Log.e(AppSettings.DEBUG_TAG, "exception thrown in closeLocationManager(...)", ex);
}
}
}
protected void removeAlert(LocationManager mgr, ProximityAlert reminder) {
if (initialized) {
if (reminder.Name.trim().length() > 0) {
mgr.removeProximityAlert(this.proximityIntentsMap.get(reminder));
}
}
}
}
As new proximity alerts are added via the LocationManager, we first remove the old ones and add all alerts again.
Note that the actual xml layout and menu files will be included in the download.
We will continue our discussion on
- Capturing Promity Alert Events and
- Setting Proximity Alert Preferences
in Part 3 of Developing Proximity Alerts for Mobile Applications using the Android Platform.
More coming soon!



I’m eagerly awaiting part 2 as I’ve had trouble understanding how to do this.
I’m writing this post after work, ad I get time. I just started part 2 (take a peek), and plan on more this week. Thanks for your interest!