POSIT:Plugin Architecture

From Notes

Jump to: navigation, search

Contents

Quick overview

The overall idea

The general idea is that: POSIT is good at finding things, keeping records of things, and tracking where they are. The problem is that the kinds of things it can track is limited. We want to be able to easily extend POSIT to track many kinds of things! i.e. bird watching.. registering people for stuff.. victim tracking.. disaster 'incident' tracking... GEOCACHING?!... I don't know, there's a million things.

We want to write the architecture in such a way that it will be easy to extend and so you don't have to write a ton of a code to do it--but we want it to remain very customizable.

File:Plugindiagram.png

As the figure shows, there are two types of plugins. A Find Plugin is one that extends or overrides the basic POSIT Find table. It can extend the Find table by adding new fields or it can override the existing table with a completely new table. There can be only one such plugin per POSIT instance.

A Function Plugin is one that adds some functionality to POSIT but does not change the data that is associated with a Find. There can be multiple Function plugins per POSIT instance.

Parsing Plugin Specs

Plugins are presented to POSIT in raw/plugin_preferences.xml. Details of a plugin spec will be given below. Raw XML files are not parsed by Android. Therefore the app must do its own parsing. This is handled by initFromResource() method in the FindPluginManager class. For each Plugin specified an object of type FunctionPlugin or FindPlugin is created and stored in a list maintained by FindPluginManager:

for(int k = 0; k < plugin_nodes.getLength(); ++k){
  if (plugin_nodes.item(k).getAttributes().getNamedItem("active").getTextContent().compareTo("true") == 0)  {
    Plugin p = null;
    if (plugin_nodes.item(k).getAttributes().getNamedItem("type").getTextContent().equals("function") ) {
      p = new FunctionPlugin(mMainActivity, plugin_nodes.item(k));
      plugins.add(p);
    } else {
      p = new FindPlugin(mMainActivity, plugin_nodes.item(k));
      mFindPlugin = (FindPlugin) p;
      plugins.add(mFindPlugin);						
    }
  }
}

POSIT then accesses Plugins through the PluginManager.

What you must do... Function Plugin

Function plugins attach to POSIT through extension points that can occur just about anywhere in the code. For example, there can be menu extension points or button extension points or extension points that attach to the onCreate method of some POSIT class.

To create a function plugin you must create a Plugin entry in raw/plugin_preferences.xml. The entry must specify its type attribute as function and it must provide a name for its extensionPoint. For example, here's the spec for a menu plugin that attaches to an extension point in ListFindsActivity:

		<Plugin active="true" 
			type="function" 
			extensionPoint="listMenu"
			name="log" 
			menuIcon="ic_menu_savetolog" menuTitle="Log Finds"
			menuActivity="org.hfoss.posit.android.experimental.functionplugins.LogFindsActivity" />

In this example, a drawable resource, ic_menu_savetolog, is used. This must be placed in the res/drawable directory amd will be used like any other Android resource. Most Function plugins will start some kind of activity, whose full path name must be specified.

Attaching a Function Plugin to an Extension Point

The values for the attributes specified in the Plugin spec are stored in the FunctionPlugin object, which can be accessed through FindPluginManager. This is done at an extension point. For example, here is how Log Finds plugin is implemented in ListFindsActivity:

  
  // Declare an extension point for 1 or more menu plugins
  private ArrayList<FunctionPlugin> mListMenuPlugins = null;  

  // Attach the Plugins to the extension point
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    mListMenuPlugins = FindPluginManager.getFunctionPlugins(FindPluginManager.LIST_MENU_EXTENSION); 
    Log.i(TAG, "# of List menu plugins = " + mListMenuPlugins.size());
  }

  // Add menu plugins to the Options Menu
  public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    if (mListMenuPlugins.size() > 0) {
      for (FunctionPlugin plugin: mListMenuPlugins) {
        MenuItem item = menu.add(plugin.getmMenuTitle());
        item.setIcon(android.R.drawable.ic_menu_mapmode);				
      }
    }
    inflater.inflate(R.menu.list_finds_menu, menu);
    return true;
  }

  // Handle the menu selection
  public boolean onMenuItemSelected(int featureId, MenuItem item) {
    Log.i(TAG, "onMenuitemSelected()");

    Intent intent;
    switch (item.getItemId()) {
    //...
    default:
      if (mListMenuPlugins.size() > 0){
        for (FunctionPlugin plugin: mListMenuPlugins) {
          if (item.getTitle().equals(plugin.getmMenuTitle()))
            startActivity(new Intent(this, plugin.getmMenuActivity()));
	}
      }
      break;
    }
    return true;
  } // onMenuItemSelected

Generality and Independence

The goal in designing a Function plugin is to make it as general and independent as possible. The idea is to have all information needed by the plugin available through the FunctionPlugin object that is attached to the extension point.

The LoginActivity plugin illustrates some of the issues:

  <Plugin active="true" 
	type="function" 
	extensionPoint="mainLogin"
	name="login"
	activity="org.hfoss.posit.android.experimental.functionplugins.LoginActivity" 
	activity_returns_result="true"
	activity_result_action="0"

As the spec indicates, the LoginActivity returns a result -- i.e., were the user's credentials successfully authenticated. The activity_returns_result and activity_result_action tags help implement this activity. Notice how the code in PositMain class determines whether the Plugin Activity should be started as ForResult or not. And notice how it retrieves the ResultAction value from the Plugin object.

The extensionPoint is the onCreate() method in PositMain:

// Declare the extension point	
private FunctionPlugin mMainLoginPlugin = null;

// Attach the plugin in onCreate()
 ...
 mMainLoginPlugin = FindPluginManager.getFunctionPlugin(FindPluginManager.MAIN_LOGIN_EXTENSION);
 ...

// Run the plugin if it is installed
  if (mMainLoginPlugin != null) {
    Intent intent = new Intent();
    Class<Activity> loginActivity = mMainLoginPlugin.getActivity();
    intent.setClass(this, loginActivity);
    intent.putExtra(User.USER_TYPE_STRING, User.UserType.USER.ordinal());
    Log.i(TAG, "Starting login activity for result");
    if (mMainLoginPlugin.getActivityReturnsResult()) 
	this.startActivityForResult(intent, mMainLoginPlugin.getActivityResultAction());
    else
	this.startActivity(intent);
    }

// Handle the result in onActivityResult
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  Log.i(TAG, "onActivityResult resultcode = " + resultCode);
		
  // Login Extension Point result
  if (mMainLoginPlugin != null && requestCode == mMainLoginPlugin.getActivityResultAction()) {
      if (resultCode == Activity.RESULT_OK) {
	Toast.makeText(this, getString(R.string.toast_thankyou), Toast.LENGTH_SHORT).show();
      } else {
	finish();				
      }
   }
...
}

The LoginActivity plugin uses other classes that are currently part of POSIT's API, including the User class and the AppControlManager class. As we continue to build out additional Plugins, we will continue to decide what additional classes are needed in the API to support Plugins.

What you must do... Find Plugin

Because they require changes to POSIT's mobile database, Find plugins are more involved than Function plugins. To this end, this is what you have to do (right now) to write a Find Plugin:

  • Add an entry to raw/plugin_preferences.xml and comment out all the other ones (yeah this could be made prettier..)
	<Plugin active="true" name="OutsideInFind"
			package="org.hfoss.posit.android.experimental.plugin.outsidein"
			find_class = "org.hfoss.posit.android.experimental.plugin.outsidein.OutsideInFind"
			find_factory="org.hfoss.posit.android.experimental.api.FindFactory"
			find_activity_class="org.hfoss.posit.android.experimental.plugin.outsidein.OutsideInFindActivity" 
			extra_activity_class=""
			extra_activity_class2=""
			list_finds_activity_class="org.hfoss.posit.android.experimental.plugin.outsidein.OutsideInListFindsActivity"
 			login_activity_class="org.hfoss.posit.android.experimental.api.activity.LoginActivity"
			find_data_manager="org.hfoss.posit.android.experimental.api.database.DbManager" 
			preferences_xml="posit_preferences" 
 		        add_find_layout="outsidein_add_find"
 		        list_find_layout="outsidein_list_row"
			main_list_button_label="viewFinds"
			main_add_button_label="addFind"
			main_extra_button_label=""
			main_extra_button_label2=""
			main_icon="posit" />
    • Find class: "Basic find" class that extends Find and adds whatever new fields you want for your customized POSIT--z.B. if you were doing bird watching POSIT, you'd write a "BirdSighting" class that would add fields like "type" for the type of bird, etc.
    • Find factory: I don't think this is really being used yet, so just leave it as the api.FindFactory.
    • Find activity class: The activity you use to add new finds. Probably you will want to write your own class that extends FindActivity and put it here.
    • Extra activity class 1 and 2: If you put activities here, a new button will be added to the main page that opens these activities. Basically for extra activities that don't fit into the framework.
    • List finds activity class: The class you use to list your finds. You will probably want to extend this and write a custom ListAdapter that displays information relevant to your find.
    • Login activity class: Activity for logging into your app. You can extend this if you want or leave it as default for basic login functionality.
    • Preferences xml:Name of your preferences xml file for custom settings/preferences. Located in res/xml.
    • Add find layout:Name of the layout for your custom FindActivity without '.xml' at the end.
    • List find layout:Name of the layout file for your custom ListFindsActivity without '.xml' at the end.
    • Main list button label:Name of the string (from strings.xml) that you want displayed on the "view finds" button.
    • Main add button label:Name of the string (from strings.xml) that you want displayed on the "add finds" button.
    • Main extra button labels 1 and 2:Name of the string (from stings.xml) that you want displayed on the buttons for your extra activities (specified in extra_activity_class and extra_activity_class2.
    • Main icon:Name of the drawable for the icon on the main screen.
  • Choose the classes from the API that you want to extend/customize and write new layout files where needed e.g. for FindActivity (the activity that you use to add a new find) you will probably want to add new text boxes/other input things for your new type of Find.

Architecture... no this isn't the whole thing.

File:plugin-architecture.jpg
Example of the relationships between some the classes and activities and example implementation of a plugin. The plugin here is called OutsideIn--its for tracking the participants in a needle exchange program.

  • OutsideInFind contains some extra fields that this plugin must track: the number of syringes the person received and the number he/she exchanged, along with whether or not they're a new registrant.
  • FindActivity is the activity you use to add new finds. Its just an input form with a save button. OutsideInFindActivity extends this by extending methods to retrieve/display information in the view.
  • ListFindsActivity and OutsideInListFindsActivity share the same sort of relationship as this.

OrmLite

  • Ormlite is an ORM (object relational mapper) for android. Basically it lets you map Java classes directly to database tables so you don't have to write a lot of messy sql!
  • In order for it to work, activities have to subclass OrmLiteBaseActivity (and its variants) and use this.getHelper() to get a database helper object through which you can access the database! This class is called DbManager. Inserting and some basic lookups work now but a more general/useful framework for this will be established soon. If you want, you can try extending DbManager for your own stuff.

What to ignore/not ignore

This is still VERY much a work in progress so stuff might be a little ugly/messy.

Stuff that doesn't work

  • The entire org.hfoss.posit.android.experimental.plugin.acdivoca package. :) And basically ANYTHING with the word 'Acdivoca' in it. This is from a separate project and we plan to turn this into a plugin eventually.
  • SmsService.java
  • SearchFindsActivity.java
  • FilePickerActivity.java (I don't know why this is even there right now)

Stuff that doesn't really do anything (but will do something soon)

  • FindProvider/FindProviderInterface.java
  • FindFactory.java
  • Plugin.java (eventually gonna use this for implementing multiple plugins at once)

Stuff that is probably gonna be removed and so you should ignore it

  • 90% of the layout files.
Personal tools
NSF K-12