This example implements a Todo Management Midlet.

 

     



 /*--------------------------------------------------
* TodoMIDlet.java
*
* The main class for todo list MIDlet
*
* Example from the book:     Core J2ME Technology
* Copyright John W. Muchow   http://www.CoreJ2ME.com
* You may use/modify for any non-commercial purpose
*-------------------------------------------------*/
import java.io.*;
import java.util.*;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.rms.*;

public class TodoMIDlet extends MIDlet implements ItemStateListener, CommandListener
{
  private Display display;      // Our display
  private Form fmMain;         // Main Form
  private FormAdd fmAdd;       // Form to add todo item
  private Command cmAdd,       // Command to add todo
                  cmPrefs,     // Command to set preferences
                  cmExit;      // Command to exit
  private Vector vecTodo;       // The "master" list of todo items
  private ChoiceGroup cgTodo;  // Todo items (read from vecTodo)
  protected DisplayManager displayMgr;  // Class to help manage screens

  //-------------------------------------------------
  // Record Stores. One for todo, one for preferences
  //-------------------------------------------------
  private RecordStore rsTodo;
  private RecordStore rsPref;
  private static final String REC_STORE_TODO = "TodoList";
  private static final String REC_STORE_PREF = "TodoPrefs";
  private boolean flagSortByPriority = false, // Sort by priority?
                  flagShowPriority = true;    // Show priorities ?

  //-------------------------------------------------
  // Re-use these input streams throughout the MIDlet
  //-------------------------------------------------
  // Read from a specified byte array
  private ByteArrayInputStream istrmBytes = null;
  // Read Java data types from the above byte array
  private DataInputStream istrmDataType = null;
  // If you change length of a todo entry, bump thisbyte[] recData = new byte[25];

  //-------------------------------------------------
  // Re-use these output streams throughout the MIDlet
  //-------------------------------------------------
  // Write data into an internal byte array
  ByteArrayOutputStream ostrmBytes = null;
  // Write Java data types into the above byte array
  DataOutputStream ostrmDataType = null;

  //-------------------------------------------------
  // Record Enumerator and compare class
  //-------------------------------------------------
  private RecordEnumeration e = null;
  private ComparatorInt comp = null;

  /*--------------------------------------------------
  * MIDlet constructor.
  *-------------------------------------------------*/
  public TodoMIDlet()
  {
    display = Display.getDisplay(this);

    // Create 'main' and 'add todo item' forms
    // Form for setting prefs is in commandAction()
    fmMain = new Form("Todo List");
    fmAdd = new FormAdd("Add Todo", this);

    // Todo list and vector
    cgTodo = new ChoiceGroup("", Choice.MULTIPLE);
    vecTodo = new Vector();

    // Commands
    cmAdd = new Command("Add Todo", Command.SCREEN, 2);
    cmPrefs = new Command("Preferences", Command.SCREEN, 3);
    cmExit = new Command("Exit", Command.EXIT, 1);

    // Add all to form and listen for events
    fmMain.addCommand(cmAdd);
    fmMain.addCommand(cmPrefs);
    fmMain.addCommand(cmExit);
    fmMain.append(cgTodo);
    fmMain.setCommandListener(this);
    fmMain.setItemStateListener(this);

    // Create a display manager object
    displayMgr = new DisplayManager(display, fmMain);

    // Open/create the record stores
    rsTodo = openRecStore(REC_STORE_TODO);
    rsPref = openRecStore(REC_STORE_PREF);

    // Initialize the streams
    initInputStreams();
    initOutputStreams();

    // Read preferences from rms
    refreshPreferences();

    // Initialize the enumeration for todo rms
    initEnumeration();

   // Read rms into vector
    writeRMS2Vector();

    // Build the todo list (choice group)
    rebuildTodoList();
  }

  /*--------------------------------------------------
  * Show the main Form
  *-------------------------------------------------*/
  public void startApp ()
  {
    display.setCurrent(fmMain);
  }

  /*--------------------------------------------------
  * Shutting down. Cleanup all we created
  *-------------------------------------------------*/
  public void destroyApp (boolean unconditional)
  {
    // Cleanup for enumerator
    if (comp != null)
      comp.compareIntClose();
    if (e != null)
      e.destroy();

    // Cleanup streams
    try
    {
      if (istrmDataType != null)
        istrmDataType.close();

      if (istrmBytes != null)
        istrmBytes.close();

      if (ostrmDataType != null)
        ostrmDataType.close();

      if (ostrmBytes != null)
        ostrmBytes.close();
    }
    catch (Exception e)
    {
      db(e.toString());
    }

    // Cleanup rms
    closeRecStore(rsTodo);
    closeRecStore(rsPref);
  }

  /*--------------------------------------------------
  * No pause code necessary
  *-------------------------------------------------*/
  public void pauseApp ()
  { }

  /*--------------------------------------------------
  * Open input streams
  *-------------------------------------------------*/
  private void initInputStreams()
  {
    istrmBytes = new ByteArrayInputStream(recData);
    istrmDataType = new DataInputStream(istrmBytes);
  }

  /*--------------------------------------------------
  * Open output streams
  *-------------------------------------------------*/
  private void initOutputStreams()
  {
    ostrmBytes = new ByteArrayOutputStream();
    ostrmDataType = new DataOutputStream(ostrmBytes);
  }

  /*--------------------------------------------------
  * Initialize enumeration for todo rms
  *-------------------------------------------------*/
  private void initEnumeration()
  {
    // Are we to bother with sorting?
    if (flagSortByPriority)
      comp = new ComparatorInt();
    else
      // We must set this to null to clear out
      // any previous setting
      comp = null;

    try
    {
      e = rsTodo.enumerateRecords(null, comp, false);
    }
    catch (Exception e)
    {
      db(e.toString());
    }
  }

  /*--------------------------------------------------
  * Open a record store
  *-------------------------------------------------*/
  private RecordStore openRecStore(String name)
  {
    try
    {
      // Open the Record Store, creating it if necessary
      return RecordStore.openRecordStore(name, true);
    }
    catch (Exception e)
    {
      db(e.toString());
      return null;
    }
  }

  /*--------------------------------------------------
  * Close a record store
  *-------------------------------------------------*/
  private void closeRecStore(RecordStore rs)
  {
    try
    {
      rs.closeRecordStore();
    }
    catch (Exception e)
    {
      db(e.toString());
    }
  }

  /*--------------------------------------------------
  * Delete a record store
  *-------------------------------------------------*/
  private void deleteRecStore(String name)
  {
    try
    {
      RecordStore.deleteRecordStore(name);
    }
    catch (Exception e)
    {
      db(e.toString());
    }
  }

  /*--------------------------------------------------
  * Write new todo item
  *   - Write a new record into the rms
  *   - Write a new item into the vector
  *   - Recreate the vector from the rms (which will
  *     use the proper sort (using rms enumeration)
  *   - Rebuild todo list from vector
  *-------------------------------------------------*/
  protected void addTodoItem(int priority, String text)
  {
    try
    {
      // Toss any data in the internal array so writes
      // starts at beginning (of the internal array)
      ostrmBytes.reset();

      // Write priority and todo text
      ostrmDataType.writeInt(priority);
      ostrmDataType.writeUTF(text);

      // Clear any buffered data
      ostrmDataType.flush();

      // Get stream data into byte array and write record
      byte[] record = ostrmBytes.toByteArray();
      int recordId = rsTodo.addRecord(record, 0, record.length);

      // Create a new Todo item and insert it into our Vector
      TodoItem item = new TodoItem(priority, text, recordId);
      vecTodo.addElement(item);
    }
    catch (Exception e)
    {
      db(e.toString());
    }

    // Read rms into vector
    writeRMS2Vector();

    // Rebuild todo list
    rebuildTodoList();
  }

  /*--------------------------------------------------
  * Save preferences to record store
  *-------------------------------------------------*/
  protected void savePreferences(boolean sort, boolean showSort)
  {
    // No changes we made
    if (sort == flagSortByPriority && showSort == flagShowPriority)
      return;

    // Save the current sort status
    boolean previouslySorted = flagSortByPriority;
    boolean previouslyShowPriority = flagShowPriority;

    try
    {
      // Update prefs in private variables
      flagSortByPriority = sort;
      flagShowPriority = showSort;

      // Toss any data in the internal array so writes
      // starts at beginning (of the internal array)
      ostrmBytes.reset();

      // Write the sort order and keep completed flags
      ostrmDataType.writeBoolean(flagSortByPriority);
      ostrmDataType.writeBoolean(flagShowPriority);

      // Clear any buffered data
      ostrmDataType.flush();

      // Get stream data into byte array and write record
      byte[] record = ostrmBytes.toByteArray();

      // Always write preferences at first record
      // We cannot request to set the first record unless
      // the record store has contents.
      // If empty => add a record
      // If not   => overwrite the first record
      if (rsPref.getNumRecords() == 0)
        rsPref.addRecord(record, 0, record.length);
      else
        rsPref.setRecord(1, record, 0, record.length);
    }
    catch (Exception e)
    {
      db(e.toString());
    }

    // If the sort order was changed, rebuild enumeration
    if (previouslySorted != flagSortByPriority)
      initEnumeration();

    // If we are going from non-sorted to sorted
    // or changing whether or not to show priority 
    // then we must update what's currently displayed
    if ((!previouslySorted && flagSortByPriority) ||
        (previouslyShowPriority != flagShowPriority))
    {
      // Read rms into vector
      writeRMS2Vector();
      // Rebuild todo list
      rebuildTodoList();
    }
  }

  /*--------------------------------------------------
  * Read preferences from record store
  *-------------------------------------------------*/
  private void refreshPreferences()
  {
    try
    {
      // Record store is empty
      if (rsPref.getNumRecords() == 0)
      {
        // Write into the store the default preferences
        savePreferences(flagSortByPriority, flagShowPriority);
        return;
      }

      // Reset input back to the beginning
      istrmBytes.reset();

      // Read configuration data stored in the first record
      rsPref.getRecord(1, recData, 0);
      flagSortByPriority = istrmDataType.readBoolean();
      flagShowPriority = istrmDataType.readBoolean();

      System.out.println("Sort: " + flagSortByPriority);
      System.out.println("Show: " + flagShowPriority);
    }
    catch (Exception e)
    {
      db(e.toString());
    }
  }

  /*--------------------------------------------------
  * Create the vector from record store contents
  *-------------------------------------------------*/
  private void writeRMS2Vector()
  {
    // Cleanout the vector
    vecTodo.removeAllElements();

    try
    {
      // Rebuild enumeration for any changes
      e.rebuild();

      while (e.hasNextElement())
      {
        // Reset input back to the beginning
        istrmBytes.reset();

        // Get data into the byte array
        int id = e.nextRecordId();
        rsTodo.getRecord(id, recData, 0);

        // Create a new Todo item and insert it into our Vector
        TodoItem item = new TodoItem(istrmDataType.readInt(), istrmDataType.readUTF(), id);
        vecTodo.addElement(item);
      }
    }
    catch (Exception e)
    {
      db(e.toString());
    }
  }

  /*--------------------------------------------------
  * Store the current vector contents to the rms
  *-------------------------------------------------*/
  private void writeVector2RMS()
  {
    try
    {
      byte[] record;

      for (int i = 0; i < vecTodo.size(); i++)
      {
        TodoItem item = (TodoItem) vecTodo.elementAt(i);

        int priority = item.getPriority();
        String text = item.getText();

        // Toss any data in the internal array so writes
        // starts at beginning (of the internal array)
        ostrmBytes.reset();

        // Write priority and todo text
        ostrmDataType.writeInt(priority);
        ostrmDataType.writeUTF(text);

        // Clear any buffered data
        ostrmDataType.flush();

        // Get stream data into byte array and write record
        record = ostrmBytes.toByteArray();
        rsTodo.addRecord(record, 0, record.length);
      }
    }
    catch (Exception e)
    {
      db(e.toString());
    }
  }

  /*--------------------------------------------------
  * Rebuild todo list (ChoiceGroup) from the Vector
  *-------------------------------------------------*/
  protected void rebuildTodoList()
  {
    // Clear out the ChoiceGroup.
    for (int i = cgTodo.size(); i > 0; i--)
      cgTodo.delete(i - 1);

    TodoItem item;
    int priority;
    String text;
    StringBuffer strb;

    for (int i = 0; i < vecTodo.size(); i++)
    {
      // Get a todo item from vector
      item = (TodoItem) vecTodo.elementAt(i);

      // Read values from todoitem class
      priority = item.getPriority();
      text = item.getText();

      // Are we are to show priority as part of the text?
      strb = new StringBuffer((flagShowPriority ? (Integer.toString(priority) + "-"): ""));
      strb.append(text);

      // Append to todo choicegroup
      cgTodo.append(strb.toString(), null);
    }
  }

  /*--------------------------------------------------
  * This method is called when a todo item has been
  * selected in the choicegroup. We treat this as
  * a request to delete the item
  *-------------------------------------------------*/
  public void itemStateChanged(Item item)
  {
    ChoiceGroup cg;

    // Cast the item to a ChoiceGroup type
    cg = (ChoiceGroup) item;

    // Create an array that mirrors the ChoiceGroup
    // and populate with those items selected
    boolean selected[] = new boolean[cg.size()];
    cg.getSelectedFlags(selected);

    // Unfortunately, there is no (easy) way to determine
    // which item in the choiceGroup was "clicked" to
    // initiate this event. The best we can do is look at
    // each entry and determine its current selection state

    // For each element, see if it is selected
    // If so, delete it. Once we have found a selected
    // item, we can exit - there will never be more than
    // one item selected at any time
    for (int i = 0; i < cg.size(); i++)
    {
      if (selected[i])
      {
        // Get the record id from the todoItem 
        // and delete record from rms
        TodoItem todoitem = (TodoItem) vecTodo.elementAt(i);
        try
        {
          rsTodo.deleteRecord(todoitem.getRecordId());
        }
        catch (Exception e)
        {
          db(e.toString());
        }
        break;
      }
    }
    // Read rms into vector
    writeRMS2Vector();
    // Rebuild todo list
    rebuildTodoList();
  }

  /*--------------------------------------------------
  * Process events for the main form
  *-------------------------------------------------*/
  public void commandAction(Command c, Displayable d)
  {
    if (c == cmExit)
    {
      destroyApp(false);
      notifyDestroyed();
    }
    else
    {
      if (c == cmAdd)
      {
        // Reset the textfield and choicegroup
        // on the 'add todo' form
        fmAdd.tfTodo.setString("");
        fmAdd.cgPriority.setSelectedIndex(0, true);

        // Push current displayable and activate 'add todo' form
        displayMgr.pushDisplayable(fmAdd);
      }
      else if (c == cmPrefs)
      {
        boolean flags[] = {flagSortByPriority, flagShowPriority};

        // Push current displayable and show preferences form 
        // passing in current preference settings        
        displayMgr.pushDisplayable(new FormPrefs("Preferences", this, flags));
      }
    }
  }

  /*--------------------------------------------------
  * Simple message to console for debug/errors
  *-------------------------------------------------*/
  private void db(String str)
  {
    System.err.println("Msg: " + str);
  }
}


/*--------------------------------------------------
* Use a stack to push and pop displayable objects
*
* public void pushDisplayable(Displayable)
* public void popDisplayable()
* public void home()  
*
* Example from the book:     Core J2ME Technology
* Copyright John W. Muchow   http://www.CoreJ2ME.com
* You may use/modify for any non-commercial purpose
*-------------------------------------------------*/

class DisplayManager extends Stack
{
  private Display display;      // Reference to Display object
  private Displayable mainDisplayable; // Main displayable for MIDlet
  private Alert alStackError;  // Alert for error conditions

  /*--------------------------------------------------
  * Display manager constructor
  *-------------------------------------------------*/
  public DisplayManager(Display display, Displayable mainDisplayable)
  {
    // Only one display object per midlet, this is it
    this.display = display;
    this.mainDisplayable = mainDisplayable;
    // Create an alert displayed when an error occurs
    alStackError = new Alert("Displayable Stack Error");
    alStackError.setTimeout(Alert.FOREVER);  // Modal
  }

  /*--------------------------------------------------
  * Push the current displayable onto stack and set
  * the passed in displayable as active
  *-------------------------------------------------*/
  public void pushDisplayable(Displayable newDisplayable)
  {
    push(display.getCurrent());
    display.setCurrent(newDisplayable);
  }

  /*--------------------------------------------------
  * Return to the main displayable object of MIDlet
  *-------------------------------------------------*/
  public void home()
  {
    while (elementCount > 1)
      pop();
    display.setCurrent(mainDisplayable);
  }
  /*--------------------------------------------------
  * Pop displayable from stack and set as active
  *-------------------------------------------------*/
  public void popDisplayable()
  {
    // If the stack is not empty, pop next displayable
    if (empty() == false)
      display.setCurrent((Displayable) pop());
    else
      // On error show an alert
      // Once acknowldeged, set 'mainDisplayable' as active
      display.setCurrent(alStackError, mainDisplayable);
  }
}

/*--------------------------------------------------
* FormAdd.java
*
* Form for adding new todoitems
* Supporting class for TodoMIDlet
*
* Example from the book:     Core J2ME Technology
* Copyright John W. Muchow   http://www.CoreJ2ME.com
* You may use/modify for any non-commercial purpose
*-------------------------------------------------*/
class FormAdd extends Form implements CommandListener
{
  private Command cmBack,
                  cmSave;
  protected TextField tfTodo;
  protected ChoiceGroup cgPriority;
  private TodoMIDlet midlet;

  public FormAdd(String title, TodoMIDlet midlet)
  {
    // Call the Form constructor
    super(title);

    // Save reference to MIDlet so we can access
    // the display manager class and rms
    this.midlet = midlet;

    // Commands
    cmSave = new Command("Save", Command.SCREEN, 1);
    cmBack = new Command("Back", Command.BACK, 2);

    // Create textfield for entering todo items
    tfTodo = new TextField("Todo", null, 15, TextField.ANY);

    // Create choicegroup and append options (no images)
    cgPriority = new ChoiceGroup("Priority", Choice.EXCLUSIVE);
    cgPriority.append("Today", null);
    cgPriority.append("Tomorrow", null);
    cgPriority.append("This Week", null);
    cgPriority.append("This Month", null);
    cgPriority.append("Someday", null);

    // Add stuff to form and listen for events
    addCommand(cmSave);
    addCommand(cmBack);
    append(tfTodo);
    append(cgPriority);
    setCommandListener(this);
  }

  public void commandAction(Command c, Displayable s)
  {
    if (c == cmSave)
    {
      // Add a new todo item
      // Notice we bump priority by 1. This is because the 
      // choicegroup entries start at zero. We would like 
      // the records in the rms to store priorities starting
      // at 1. Thus, if a user requests to display priorities 
      // on the todo list, the highest priority is 1 (not zero)
      midlet.addTodoItem(cgPriority.getSelectedIndex() + 1,
                     tfTodo.getString());
    }

    // Any other event and we go back to the main form...
    // Pop the last displayable off the stack
    midlet.displayMgr.popDisplayable();
  }
}

/*--------------------------------------------------
* ComparatorInt.java
*
* Sorts rms records based on todo item priority
* Provides compare() method for RecordEnumerator
* Supporting class for TodoMIDlet
*
* Example from the book:     Core J2ME Technology
* Copyright John W. Muchow   http://www.CoreJ2ME.com
* You may use/modify for any non-commercial purpose
*-------------------------------------------------*/

class ComparatorInt implements RecordComparator
{
  private byte[] record = new byte[10];

  // Read from a specified byte array
  private ByteArrayInputStream strmBytes = null;

  // Read Java data types from the above byte array
  private DataInputStream strmDataType = null;

  public void compareIntClose()
  {
    try
    {
      if (strmBytes != null)
        strmBytes.close();
      if (strmDataType != null)
        strmDataType.close();
    }
    catch (Exception e)
    {}
  }

  public int compare(byte[] rec1, byte[] rec2)
  {
    int x1, x2;

    try
    {
      // If either record is larger than our buffer, reallocate
      int maxsize = Math.max(rec1.length, rec2.length);
      if (maxsize > record.length)
        record = new byte[maxsize];

      // Read record #1
      // We want the priority which is first "field"
      strmBytes = new ByteArrayInputStream(rec1);
      strmDataType = new DataInputStream(strmBytes);
      x1 = strmDataType.readInt();

      // Read record #2
      strmBytes = new ByteArrayInputStream(rec2);
      strmDataType = new DataInputStream(strmBytes);
      x2 = strmDataType.readInt();

      // Compare record #1 and #2
      if (x1 == x2)
        return RecordComparator.EQUIVALENT;
      else if (x1 < x2)
        return RecordComparator.PRECEDES;
      else
        return RecordComparator.FOLLOWS;

    }
    catch (Exception e)
    {
      return RecordComparator.EQUIVALENT;
    }
  }
}  

/*--------------------------------------------------
* TodoItem.java
*
* Holds data/methods for a single todo item
* Supporting class for todoMIDlet
*
* Example from the book:     Core J2ME Technology
* Copyright John W. Muchow   http://www.CoreJ2ME.com
* You may use/modify for any non-commercial purpose
*-------------------------------------------------*/
class TodoItem
{
  private int priority;
  private String text;
  private int recordId;

  public TodoItem(int priority, String text, int recordId)
  {
    this.priority = priority;
    this.text = text;
    this.recordId = recordId;
  }

  public int getPriority()
  {
    return priority;
  }

  public void setPriority(int priority)
  {
    this.priority = priority;
  }

  public String getText()
  {
    return text;
  }

  public void setText(String text)
  {
    this.text = text;
  }
  public int getRecordId()
  {
    return recordId;
  }
}  

/*--------------------------------------------------
* FormPrefs.java
*
* Form for specifying user preferences
* Supporting class for TodoMIDlet
*
* Example from the book:     Core J2ME Technology
* Copyright John W. Muchow   http://www.CoreJ2ME.com
* You may use/modify for any non-commercial purpose
*-------------------------------------------------*/
class FormPrefs extends Form implements CommandListener
{
  private Command cmBack,
                  cmSave;
  private TodoMIDlet midlet;
  private ChoiceGroup cgPrefs;

  public FormPrefs(String title, TodoMIDlet midlet, boolean flags[])

  {
    // Call the Form constructor
    super(title);

    // Save reference to MIDlet so we can access
    // the display manager class and rms
    this.midlet = midlet;

    // Commands
    cmSave = new Command("Save", Command.SCREEN, 1);
    cmBack = new Command("Back", Command.BACK, 2);

    // Choicegroup for sort order and showing priority
    cgPrefs = new ChoiceGroup("Preferences", Choice.MULTIPLE);
    cgPrefs.append("Sort by Priority", null);
    cgPrefs.append("Show Priority", null);

    // Set the current status of each entry
    cgPrefs.setSelectedFlags(flags);

    // Add to form and listen for events
    append(cgPrefs);
    addCommand(cmBack);
    addCommand(cmSave);
    setCommandListener(this);
  }

  public void commandAction(Command c, Displayable s)
  {
    if (c == cmSave)
    {
      // Save the preferences
      midlet.savePreferences(cgPrefs.isSelected(0),
                             cgPrefs.isSelected(1));
    }

    // Any other event and we go back to the main form...
    // Pop the last displayable off the stack
    midlet.displayMgr.popDisplayable();
  }
}