Open main menu

CDOT Wiki β

Teams Winter 2011/team1/BlackBerry/Add Send Email Option

9. Adding Send Email Functionality

9.1. Add couple of private variables to the ViewStudentApp:

   private static ServiceRecord[] _mailServiceRecords;
   public MessagesViewScreen _messagesViewScreen;

9.2. Add the Send email option to the menu and implement it:

 // Send email to student
        ImageMenuItem emailItem = new ImageMenuItem("Email Student", 400, 4, MENU_EMAIL);
        emailItem.setCommand(new Command(new CommandHandler()
        {
            public void execute(ReadOnlyCommandMetadata metadata, Object context)
            {
                Student student = (Student) _keywordFilterField.getSelectedElement();

                if (student != null)
                { 
                    try
                    {
                        ComposeScreen composeScreen = new ComposeScreen(null, _store, student.getEmail());
                        pushScreen(composeScreen);
                    }
                    catch(Exception ex)
                    {
                        Dialog.alert("composing screen: " + ex.getMessage());
                    }

                }
            }
        }));
        _mainScreen.addMenuItem(emailItem);

9.3 Add four new Screen classes and a helper class to the project:

  • ComposeScreen.java - start screen where email is composed
  • MessageScreen.java - screen showing the email
  • MessagesViewScreen.java - to view the created email
  • MessageListField.java - handles the drawing of the list of messages
  • FoldersViewScreen.java - view emails in the Outbox folder

The main functionality is taken from the Sample provided with blackberry plug-in: BlackBerryMailDemo
9.4. Following is the modified code to accommodate the application:
ComposeScreen.java - pass the student e-mail as "To:" parameter:

  package cs.ecl.team1.project;

  import java.util.*;
  import net.rim.blackberry.api.mail.*;
  import net.rim.device.api.command.*;
  import net.rim.device.api.ui.component.*;
  import net.rim.device.api.ui.*;
  import net.rim.device.api.util.*;


  /**
  * The ComposeScreen is a screen which displays either a new or saved message.
  * It adds the functionality of saving and sending messages to its parent class,
  * MessageScreen.
  */
public final class ComposeScreen extends MessageScreen
{   
    private static final int FIRST = 0;
    private static final int SEND_MENU_ITEM_INDEX = 0;
   
    private Store _store;
    private MessagesViewScreen _messagesViewScreen;   

   
    /**
     * Creates a new ComposeScreen object
     * @param message A message in the process of being composed, or null if a new message is to be composed
     * @param store The message store for this application
     */ 
   
    public ComposeScreen(Message message, Store store, String email)
    {
        super(message, true, email);    
        _store = store;
        _messagesViewScreen = new MessagesViewScreen();     
       
        // If a new message is to be created, indicate this in the title
        if( message == null )
        {
            setTitle("New Message");
        }

        // Create and add menu items specific to the Compose action (addTo,
        // addBcc, addCc, etc...).
        AddHeaderFieldAction addToMenuItem = new AddHeaderFieldAction(Message.RecipientType.TO, "Add To: ", "To: ");
        AddHeaderFieldAction addCcMenuItem = new AddHeaderFieldAction(Message.RecipientType.CC, "Add Cc: ", "Cc: ");
        AddHeaderFieldAction addBccMenuItem = new AddHeaderFieldAction(Message.RecipientType.BCC, "Add Bcc: ", "Bcc: ");
       

        // MenuItem to save a message
        MenuItem saveMenuItem = new MenuItem(new StringProvider("Save Message"), 0x230020, 1);
        saveMenuItem.setCommand(new Command(new CommandHandler()
        {
            /**
             * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, Object)
             */
            public void execute(ReadOnlyCommandMetadata metadata, Object context)
            {       // If the save is completed, then discard this screen           
                if( onSave() )
                {
                    close();
                }
                else
                // If the message could not be saved, alert the user
                {
                    UiApplication.getUiApplication().invokeLater(new Runnable()
                    {
                        public void run()
                        {
                            Dialog.alert("Message could not be saved");
                        }
                    });
                }
            }
        }));
       
        // MenuItem to send a message
        MenuItem sendMenuItem = new MenuItem(new StringProvider("Send Message"), 0x230010, 0);
        sendMenuItem.setCommand(new Command(new CommandHandler()
        {
            /**
             * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, Object)
             */
            public void execute(ReadOnlyCommandMetadata metadata, Object context)
            {       // If the save is completed, then discard this screen           
                try
                {
                    _message = getMessage();
                    if( _message != null )
                    {
                        // Send the message
                        Transport.send(_message);
                       
                        UiApplication.getUiApplication().pushScreen(_messagesViewScreen);
                        close();
                    }
                 }
               catch( MessagingException e )
                {               
                    ViewStudentApp.errorDialog("Transport.send(Message) threw " + e.toString());
                }
            }
        }));

        addMenuItem(sendMenuItem);
        addMenuItem(saveMenuItem);
        addMenuItem(addToMenuItem);
        addMenuItem(addCcMenuItem);
        addMenuItem(addBccMenuItem);
    }

   
    /**
     * Overrides MessageScreen.displayMessage(). The message's 'sent' properties
     * are not displayed since the message is still in the process of editing.
     */
    void displayMessage(String email)
    {
        // If the message does not exist then compose a new message
        if( _message == null )
        {
            // Add a To line
            EditField toField = new EditField("To: ", email, 40, BasicEditField.FILTER_EMAIL);           
            addTextFieldToTableAndScreen(toField, Message.RecipientType.TO);

            // Add a subject line
            EditField subjectField = new EditField("Subject: ", "");
            addTextFieldToTableAndScreen(subjectField, SUBJECT);

            // Add a separator between the body and the headers
            add(new SeparatorField());

            // Add a body field
            EditField bodyField = new EditField();
            addTextFieldToTableAndScreen(bodyField, BODY);
        }
        else
        // The message exists so display it
        {
            displayHeader();
            add(new SeparatorField());
            displayMessageBody();
        }
    }   

   
    /**
     * Gets a message for sending or saving
     * @return A new message
     */
    Message getMessage()
    {
        // Find an outbox folder and use it to construct a new message     
        Folder outbox = _store.findFolder("Outbox")[ FIRST ];       
        Message message = new Message(outbox);

        // Add all the current headers
        for( int keyNo = 0; keyNo < HEADER_KEYS.length; keyNo++ )
        {
            Vector fieldsByType = (Vector) _fieldTable.get(HEADER_KEYS[ keyNo ]);

            if( fieldsByType != null )
            {
                // Build a vector of all the addresses
                Vector addressVector = new Vector();
                int size = fieldsByType.size();
                for( int fieldNo = 0; fieldNo < size; fieldNo++ )
                {
                    TextField addressField = (TextField) fieldsByType.elementAt(fieldNo);

                    // Try to create a new address object wrapping the email
                    // address and add it to the address vector.
                    try
                    {
                        addressVector.addElement(new Address(addressField.getText(), ""));
                    }
                    catch( AddressException e ) // Invalid address
                    {
                        ViewStudentApp.errorDialog("Address(String, String) threw " + e.toString());
                    }
                }

                // Dump the vector of addresses into an array to send the message
                Address[] addresses = new Address[ addressVector.size() ];
                addressVector.copyInto(addresses);

                // Try to add the addresses to the message's list of recipients
                try
                {
                    message.addRecipients(HEADER_KEYS[ keyNo ], addresses);
                }
                catch( MessagingException e )
                {                   
                    ViewStudentApp.errorDialog("Message#addRecipients(int, Address[]) threw " + e.toString());
                }
            }
        }

        // Add the subject
        Vector subjectFields = (Vector) _fieldTable.get(SUBJECT);
        TextField subjectField = (TextField) subjectFields.elementAt(FIRST);

        if( subjectFields != null && subjectFields.size() > 0 )
        {
            message.setSubject(subjectField.getText());
        }

        // Add the body by adding all the body fields into one multipart
        Vector bodyFields = (Vector) _fieldTable.get(BODY);
        if( bodyFields != null )
        {
            int size = bodyFields.size();
            Multipart content = new Multipart();
            for( int fieldNo = 0; fieldNo < size; fieldNo++ )
            {
                TextField body = (TextField) bodyFields.elementAt(fieldNo);
                content.addBodyPart(new TextBodyPart(content, body.getText()));
            }
            try
            {
                message.setContent(content);
            }
            catch( MessagingException e )
            {               
                ViewStudentApp.errorDialog("Message#setContent(Object) threw " + e.toString());
            }
        }
        else
        {
            ViewStudentApp.errorDialog("Error: no body field available");
            return null;
        }

        // Set the date
        message.setSentDate(Calendar.getInstance().getTime());

        return message;
    }

   
    /**
     * @see net.rim.device.api.ui.Screen#onSave()
     */
    protected boolean onSave()
    {
        // Save the message to the outbox
        try
        {
            Message newMessage = getMessage();
            if( newMessage != null )
            {
                // Retrieve an outbox to save the message in
                Store store = Session.waitForDefaultSession().getStore();
                Folder[] allOutboxFolders = store.list(Folder.OUTBOX);

                Folder outbox = null;               
                for( int i = allOutboxFolders.length - 1; i >= 0 ; --i )
                {
                    if( allOutboxFolders[ i ].getParent().getName().startsWith("Mailbox") )
                    {
                        outbox = allOutboxFolders[ i ];
                        break;
                    }
                }

                // Save the new message and replace the old one if it exists
                outbox.appendMessage(newMessage);
                if( _message != null )
                {
                    outbox.deleteMessage(_message, true);
                }
                _message = newMessage;

                // Set the status to composing and flag that it has been saved
                _message.setStatus(Message.Status.TX_COMPOSING, Message.Status.TX_ERROR);
                _message.setFlag(Message.Flag.SAVED, true);

                return true;
            }

            return false;
        }
        catch( MessagingException e )
        {
            return false;
        }
    }

   
    /**
     * Make "Send" the default menu item.
     *
     * @see net.rim.device.api.ui.container.MainScreen#makeMenu(Menu,int)
     */
    protected void makeMenu(Menu menu, int instance)
    {
        super.makeMenu(menu, instance);

      //  menu.setDefault(SEND_MENU_ITEM_INDEX);
    }

   
    /**
     * This class is responsible for adding the various header fields to the
     * compose screen (To, Bcc, CC).
     */
    private final class AddHeaderFieldAction extends MenuItem
    {
        private String _fieldLabelText;
        private int _headerType;

        /**
         * Constructs a menu item which adds a header of a specified type to the
         * compose screen.
         *
         * @param headerType One of the Message.RecipientType fields
         * @param menuItemText String to use for the menu item
         * @param fieldLabelText String to use for the label of this field
         */
        AddHeaderFieldAction(int headerType, String menuItemText, String fieldLabelText)
        {
            super(new StringProvider(menuItemText), 0x240010, 2);

            _fieldLabelText = fieldLabelText;
            _headerType = headerType;
            this.setCommand(new Command(new CommandHandler()
            {
                /**
                 * Adds a new header field to the message. The field is placed so that
                 * the header types are grouped together and the most recently added one
                 * is closest to the bottom.        
                 * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, Object)
                 */
                public void execute(ReadOnlyCommandMetadata metadata, Object context)
                {
                    EditField newField = new EditField(_fieldLabelText, "");

                    // Find out where the last field of this type was added to the
                    // screen.
                    Vector fieldsByType = (Vector) _fieldTable.get(_headerType);
                    int lastInsertedIndex;
                    if( fieldsByType == null )
                    {
                        // If a field of _headerType was not made yet, then create the
                        // vector which contains all of the fields of _headerType.
                        fieldsByType = new Vector();
                        _fieldTable.put(_headerType, fieldsByType);
                        lastInsertedIndex = getIndexForNewFieldType();
                    }
                    else
                    {
                        lastInsertedIndex = getIndexOfLastFieldOfType(_headerType);
                    }

                    // Add the new field to both the screen and the vector keeping track
                    // of all the fields of the same type.
                    ComposeScreen.this.insert(newField, lastInsertedIndex + 1);
                    fieldsByType.addElement(newField);
                    newField.setFocus();
                }
            }));
        }

       
        /**
         * Given the existing field type of this instance of the class,
         * determine where to place new header fields in the screen.
         *
         * @return The index at which to insert the new header field
         */
        private int getIndexForNewFieldType()
        {
            // Note: we don't handle TO here since there ALWAYS must be one TO
            // field.
            switch( _headerType )
            {
                // Find the last TO field and use the next index as the
                // insertion point.
                case Message.RecipientType.CC:
                    return getIndexOfLastFieldOfType(Message.RecipientType.TO);

                    // Try to find the last CC field and use it as the next
                    // insertion point. If no CC field exists then find the last
                    // TO field and use its index as the insertion point.
                case Message.RecipientType.BCC:
                    int index = getIndexOfLastFieldOfType(Message.RecipientType.CC);
                    if( index == -1 )
                        return getIndexOfLastFieldOfType(Message.RecipientType.TO);
                    return index;

                default:
                    throw new IllegalStateException("Mail Demo: Unrecognized recipient type");
            }
        }

       
        /**
         * Retrieves the index of the last field added of a specified type.
         *
         * @param type The type of header field to retrieve the last index of
         * @return The index of the most recently added field of the specified
         *         type, -1 if a field of the specified type has not been added
         *         yet.
         */
        private int getIndexOfLastFieldOfType(int type)
        {
            Vector fields = (Vector) _fieldTable.get(type);
            if( fields == null )
            {
                return -1;
            }

            Field field = (Field) fields.lastElement();

            return field.getIndex();
        }
    }
 }

MessageScreen.java

  package cs.ecl.team1.project;

  import net.rim.device.api.ui.container.*;
  import net.rim.device.api.ui.component.*;
  import net.rim.device.api.util.IntHashtable;
  import javax.microedition.pim.Contact;
  import java.util.*;

  import net.rim.blackberry.api.mail.*;

  /**
   * The MessageScreen class allows a user to view a selected message and
   * edit the message if the screen is marked editable. It manages the different
   * TextFields using a hashtable where the type of information that is held
   * in a given TextField is the key while the value is a Vector of TextFields
  * associated with that information type. This class supports displaying
   * plain text, Mime, supported and unsupported attachments and pdap contacts.
   */
  public class MessageScreen extends MainScreen
  {
    // Constants
    public final static String NO_SUBJECT = "<No Subject>";
    public final static String UNKNOWN_NAME = "<?>";

    protected final static int SUBJECT = 0;
    protected final static int BODY = 1;
    protected final static int INFO = 2;
    protected final static int[] HEADER_KEYS = { Message.RecipientType.TO, Message.RecipientType.CC, Message.RecipientType.BCC };
    protected final static String[] HEADER_NAMES = { "To: ", "Cc: ", "Bcc: " };

    private final static int MAX_CHARS = 128;

    protected IntHashtable _fieldTable;
    protected Message _message;
    private boolean _editable;

   
    /**
     * Creates a new MessageScreen object
     * @param message The message to display
     * @param editable True is the message is editable, otherwise false
     */
    public MessageScreen(Message message, boolean editable, String email)
    {
        _fieldTable = new IntHashtable();
        _editable = editable;

        // Set the message and display its subject as the title if the
        // message exists.
        _message = message;
        if( _message != null )
            setTitle(_message.getSubject());

        displayMessage(email);
    }

   
    /**
     * Displays the message
     */
    void displayMessage(String email)
    {
        displayMessageInformation();

        add(new SeparatorField());

        displayHeader();

        add(new SeparatorField());

        displayMessageBody();
    }

   
    /**
     * Displays information about the message's send and recieve properties
     */
    protected void displayMessageInformation()
    {
        // Add a field describing the source service
        ServiceConfiguration sc = _message.getFolder().getStore().getServiceConfiguration();
        EditField service = new EditField("Service: ", sc.getName(), MAX_CHARS, EditField.READONLY | EditField.NON_FOCUSABLE);
        addTextFieldToTableAndScreen(service, INFO);

        // Add the folder field
        EditField folder = new EditField("Folder: ", _message.getFolder().getName(), MAX_CHARS, EditField.READONLY
                | EditField.NON_FOCUSABLE);
        addTextFieldToTableAndScreen(folder, INFO);

        // Add the status of the message
        String statusString = getStatusString(_message);
        EditField status = new EditField("Status: ", statusString, MAX_CHARS, EditField.READONLY | EditField.NON_FOCUSABLE);
        addTextFieldToTableAndScreen(status, INFO);
    }

    /**
     * Displays information about the destination and source of the message as
     * well as its subject.
     */
    protected void displayHeader()
    {
        // Assign the appropriate EditField style property
        long editableStyle = _editable ? EditField.EDITABLE : EditField.READONLY;

        // Display the headers (To:, Cc:, Bcc:)
        for( int key = 0; key < HEADER_KEYS.length; key++ )
        {
            try
            {
                Address[] addresses = _message.getRecipients(HEADER_KEYS[ key ]);
                for( int index = 0; index < addresses.length; index++ )
                {
                    // Retrieve the name
                    String name = addresses[ index ].getName();
                    if( name == null || name.length() == 0 )
                    {
                        name = addresses[ index ].getAddr();
                    }

                    // Create the edit field, associate the address to the field
                    // and add it to the screen and collection of fields.
                    EditField headerField = new EditField(HEADER_NAMES[ key ], name, EditField.DEFAULT_MAXCHARS, editableStyle);
                    headerField.setCookie(addresses[ index ]);

                    addTextFieldToTableAndScreen(headerField, HEADER_KEYS[ key ]);
                }
            }
            catch( MessagingException e )
            {               
                ViewStudentApp.errorDialog("Error: could not retrieve message header.");               
                close();
            }
        }

        // Display the 'Sent' date if it is available
        Date sent = _message.getSentDate();
        if( sent != null )
        {
            EditField sentDate = new EditField("Sent: ", Util.getDateAsString(sent), EditField.DEFAULT_MAXCHARS, EditField.READONLY
                    | EditField.NON_FOCUSABLE);

            // Change the label to "Saved: " if the message hasn't been sent yet
            if( _message.getStatus() == Message.Status.TX_COMPOSING )
                sentDate.setLabel("Saved: ");

            add(sentDate);
        }

                // Display the subject field
        String subject = _message.getSubject();
        if( subject == null )
            subject = NO_SUBJECT;

        EditField subjectField = new EditField("Subject: ", subject, EditField.DEFAULT_MAXCHARS, editableStyle);
        addTextFieldToTableAndScreen(subjectField, SUBJECT);
    }

    /**
     * Displays the message body
     */
    protected void displayMessageBody()
    {
        // Retrieve the parent of the message body
        Object obj = _message.getContent();
        Multipart parent = null;
        if( obj instanceof MimeBodyPart || obj instanceof TextBodyPart )
        {
            BodyPart bp = (BodyPart) obj;
            parent = bp.getParent();
        }
        else
        {
            parent = (Multipart) obj;
        }

        // Display the message body
        String mpType = parent.getContentType();
        if( mpType.equals(BodyPart.ContentType.TYPE_MULTIPART_ALTERNATIVE_STRING)
                || mpType.equals(BodyPart.ContentType.TYPE_MULTIPART_MIXED_STRING) )
        {
            displayMultipart(parent);
        }

        // Ensure there is at least one body field if nothing was displayed
        Vector bodyVector = (Vector) _fieldTable.get(BODY);
        if( bodyVector == null || bodyVector.size() == 0 )
        {
            if( _editable )
            {
                addTextFieldToTableAndScreen(new EditField("", ""), BODY);
            }
            else
            {
                addTextFieldToTableAndScreen(new RichTextField(""), BODY);
            }
        }
    }

   
    /**
     * Processes a multi-part message by displaying its body parts. Text body
     * parts are displayed before attachments and if a multi body part is
     * encountered, then it is processed through recursion by calling this method
     * on it.
     *
     * @param multipart The multi-part to display
     * @param editable True if this multi-part is editable
     */
    protected void displayMultipart(Multipart multipart)
    {
        // This vector stores fields which are to be displayed only after all
        // of the body fields are displayed. (Attachments and Contacts).
        Vector delayedFields = new Vector();

        // Process each part of the multi-part, taking the appropriate action
        // depending on the part's type. This loop should: display text and
        // html body parts, recursively display multi-parts and store
        // attachments and contacts to display later.
        for( int index = 0; index < multipart.getCount(); index++ )
        {
            BodyPart bodyPart = multipart.getBodyPart(index);

            // If this body part is text then display all of it
            if( bodyPart instanceof TextBodyPart )
            {
                TextBodyPart textBodyPart = (TextBodyPart) bodyPart;

                // If there are missing parts of the text, try to retrieve the
                // rest of it.
                if( textBodyPart.hasMore() )
                {
                    try
                    {
                        Transport.more(textBodyPart, true);
                    }
                    catch( Exception e )
                    {
                        ViewStudentApp.errorDialog("Transport.more(BodyPart, boolean) threw " + e.toString());
                    }
                }
                String plainText = (String) textBodyPart.getContent();

                // Display the plain text, using an EditField if the message is
                // editable or a RichTextField if it is not editable. Note: this
                // does not add any empty fields.
                if( plainText.length() != 0 )
                {
                    if( _editable )
                    {
                        addTextFieldToTableAndScreen(new EditField("", plainText), BODY);
                    }
                    else
                    {
                        addTextFieldToTableAndScreen(new RichTextField(plainText), BODY);
                    }
                }
            }
            else if( bodyPart instanceof MimeBodyPart )
            {
                MimeBodyPart mimeBodyPart = (MimeBodyPart) bodyPart;

                // If the content is text then display it
                String contentType = mimeBodyPart.getContentType();
                if( contentType.startsWith(BodyPart.ContentType.TYPE_TEXT_HTML_STRING) )
                {
                    Object obj = mimeBodyPart.getContent();
                    if( obj != null )
                    {
                        String htmlText = new String((byte[]) obj);
                        addTextFieldToTableAndScreen(new RichTextField(htmlText), BODY);
                    }
                }
                else if( contentType.equals(BodyPart.ContentType.TYPE_MULTIPART_ALTERNATIVE_STRING) )
                {
                    // If the body part is a multi-part and it has the the
                    // content type of TYPE_MULTIPART_ALTERNATIVE_STRING, then
                    // recursively display the multi-part.
                    Object obj = mimeBodyPart.getContent();
                    if( obj instanceof Multipart )
                    {
                        Multipart childMultipart = (Multipart) obj;
                        String childMultipartType = childMultipart.getContentType();
                        if( childMultipartType.equals(BodyPart.ContentType.TYPE_MULTIPART_ALTERNATIVE_STRING) )
                        {
                            displayMultipart(childMultipart);
                        }
                    }
                }
            }
            else if( bodyPart instanceof SupportedAttachmentPart || bodyPart instanceof UnsupportedAttachmentPart )
            {
                // Extract the content type and name from the attachments
                String contentType = bodyPart.getContentType();
                String name;
                if( bodyPart instanceof UnsupportedAttachmentPart )
                {
                    UnsupportedAttachmentPart uap = (UnsupportedAttachmentPart) bodyPart;
                    name = uap.getName();
                }
                else // The bodyPart is a SupportedAttachmentPart
                {
                    SupportedAttachmentPart sap = (SupportedAttachmentPart) bodyPart;
                    name = sap.getName();
                }

                // Format the content type and name to display and store
                // the field.
                StringBuffer sb = new StringBuffer(contentType.length() + name.length() + 2);
                sb.append(contentType);
                sb.append('[');
                sb.append(name);
                sb.append(']');

                delayedFields.addElement(new RichTextField(sb.toString()));
            }
            else if( bodyPart instanceof PDAPContactAttachmentPart )
            {
                Contact contact = (Contact) bodyPart.getContent();

                // Build the contact name
                StringBuffer sb = new StringBuffer("Contact: ");
                if( contact.countValues(Contact.NAME) > 0 )
                {
                    String[] name = contact.getStringArray(Contact.NAME, 0);

                    if( name[ Contact.NAME_PREFIX ] != null )
                    {
                        sb.append(name[ Contact.NAME_PREFIX ]);
                        sb.append(' ');
                    }

                    if( name[ Contact.NAME_GIVEN ] != null )
                    {
                        sb.append(name[ Contact.NAME_GIVEN ]);
                        sb.append(' ');
                    }

                    if( name[ Contact.NAME_FAMILY ] != null )
                    {
                        sb.append(name[ Contact.NAME_FAMILY ]);
                    }

                    // Trim the last space of the name if it exists
                    int lastChar = sb.length() - 1;
                    if( sb.charAt(lastChar) == ' ' )
                        sb.deleteCharAt(lastChar);
                }
                else
                {
                    sb.append(UNKNOWN_NAME);
                }

                // Create the contact attachment field and store it
                RichTextField contactAttachment = new RichTextField(sb.toString());
                contactAttachment.setCookie(contact);
                delayedFields.addElement(contactAttachment);
            }
        }

        // Now that the body parts have been displayed, display the queued
        // fields while separating them by inserting a separator field.
        for( int index = 0; index < delayedFields.size(); index++ )
        {
            add(new SeparatorField());
            addTextFieldToTableAndScreen((TextField) delayedFields.elementAt(index), BODY);
        }
    }

    /**
     * Compiles the status of a message into a readable string.
     *
     * @param message The message whose status is to be compiled into a string
     * @return The string displaying the status of the message
     */
    public static String getStatusString(Message message)
    {  
        StringBuffer statusStrBuffer = new StringBuffer();

        // Add any errors to the status string if it applies
        int status = message.getStatus();
        if( status == Message.Status.RX_ERROR )
        {
            statusStrBuffer.append("RX ERROR, ");
        }

        if( status == Message.Status.TX_GENERAL_FAILURE )
        {
            statusStrBuffer.append("RX ERROR, ");
        }

        if( status == Message.Status.TX_ERROR )
        {
            statusStrBuffer.append("TX ERROR, ");
        }

        // Use the flags to add any message statuses
        int flags = message.getFlags();
        if( 0 != (flags & Message.Flag.OPENED) )
        {
            statusStrBuffer.append("Opened, ");
        }

        if( 0 != (flags & Message.Flag.SAVED) )
        {
            statusStrBuffer.append("Saved, ");
        }

        if( 0 != (flags & Message.Flag.FILED) )
        {
            statusStrBuffer.append("Filed, ");
        }

        // Check if the message has a high or low priority
        byte messagePriority = message.getPriority();
        if( messagePriority == Message.Priority.HIGH )
        {
            statusStrBuffer.append("High Priority, ");
        }
        else if( messagePriority == Message.Priority.LOW )
        {
            statusStrBuffer.append("Low Priority, ");
        }

        // If there are any characters in the status string then delete the last
        // two characters if there are any characters to delete. Should be
        // either ", " or "  ".
        statusStrBuffer.delete(statusStrBuffer.length() - 2, statusStrBuffer.length());

        return statusStrBuffer.toString();
    }

    /**
     * Add a new field to the hashtable of TextFields and to the screen
     *
     * @param field The field to add
     * @param type The type of field to add
     */
    protected void addTextFieldToTableAndScreen(TextField field, int type)
    {
        Vector fieldsByType = (Vector) _fieldTable.get(type);

        // If the vector of fields associated with the type is not made yet,
        // initialize one and put it into the fields collection.
        if( fieldsByType == null )
        {
            fieldsByType = new Vector(1);
            _fieldTable.put(type, fieldsByType);
        }

        fieldsByType.addElement(field);
        add(field);
    }

    /**
     * @see net.rim.device.api.ui.Screen#onClose()
     */
    public boolean onClose()
    {
        // If the message status is "received", mark it "read"
        if( _message != null && _message.getStatus() == Message.Status.RX_RECEIVED )
        {
            _message.setStatus(Message.Status.TX_READ, Message.Status.TX_ERROR);
            _message.setFlag(Message.Flag.OPENED, true);
        }

        return super.onClose();
    }
 }

Other classes stay unchanged.
9.5. Run the application, select a user from the list, and select Email Student from the menu:
 
9.6. Type in the subject and some text in the body, and select Send Email from the menu:
   
9.7. The messageList screen is displayed showing the created email. Select Folder View from the menu to see the Outbox folder: