Tuesday, March 29, 2011

Creating a reusable layout component in Android

Let us say that we want to use a common layout in several places in your user interface. Something like a box with a label and value text view vertically layed out. The application uses the same label/value TextView in 3 different places.

This is the common label/value layout component (file: layout/dashboard_item.xml):
<LinearLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  
     <TextView android:id="@+id/label"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:layout_weight="0"
         android:gravity="center_horizontal"
         style="@style/label"
     />
     <TextView android:id="@+id/value"
         android:layout_width="fill_parent"
         android:layout_height="fill_parent"
         android:layout_weight="1"
         android:gravity="center"
         style="@style/output"
     />
</LinearLayout>
We want to use it in a screen definition (file: layout/home.xml):
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:labelValue="http://schemas.android.com/apk/res/net.kazed.sailor"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      android:orientation="vertical"
      style="@style/screen"
      >

    <net.kazed.sailor.view.LabelValueItem android:id="@+id/vmg"
       android:layout_width="fill_parent"
       android:layout_height="fill_parent"
       android:layout_weight="1"
       labelValue:label="@string/vmg"
    />
    
   </LinearLayout>
</RelativeLayout>
Here we reuse the label/value custom component implemented by the LabelValueItem class. This component has one custom attribute: "labelValue:label". Notice that this attribute has a namespace that corresponds with the package name of the Android application (defined in AndroidManifest.xml). The component class retrieves the value of the custom attribute and uses it to set the text of the label TextView (file: src/net/kazed/sailor/view/LabelValueItem.java):
public class LabelValueItem extends LinearLayout {
   
   public LabelValueItem(final Context context, final AttributeSet attrs) {
      super(context, attrs);
      LayoutInflater.from(context).inflate(R.layout.dashboard_item, this, true);

      TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.labelValue, 0, 0);

      TextView label = (TextView) findViewById(R.id.label);
      String labelText = array.getString(R.styleable.labelValue_label);
      if (labelText != null) {
         label.setText(labelText);
      }

      array.recycle();
   }

}
To make it all work, the application must define the custom attribute (file: values/attr.xml):
<?xml version="1.0" encoding="utf-8"?>
<resources>
   <declare-styleable name="labelValue">
      <attr name="label" format="string" />
   </declare-styleable>
</resources>

Monday, February 21, 2011

Error messages for required fields in JSF 1.2

JSF 1.1

When you have required fields in your JSF page, you simple add the required="true" attribute to the inputField and the user will get error message when user the field the field blank.
<h:inputText id="accountnumber" value="#{accountSetting.accountNumber}"
  required="true" />
This is all great, since we don't have to do much as a developer. However, the user gets an ugly error message: "accountForm:accountnumber: Validation Error: Value is required." This message is customizable by overriding this message in your message bundle and is pretty limited in how customized you want it to be.

JSF 1.2

JSF 1.2 and later makes it easier to create custom error messages for required fields, just add the requiredMessage attribute:
<h:inputText id="accountnumber" value="#{accountSetting.accountNumber}"
  required="true" requiredMessage="Account number is required" />
When the user has forgotten to enter something in this field, the JSF framework will display the error message supplied with the requiredMessage attribute. To supply translations for different languages, you can put the message in a resource bundle. Note that you must configure this resource bundle in faces-config.xml like this:
<application>
  <!-- ... -->
  <resource-bundle>
    <base-name>com.example.messages</base-name>
  <var>bundle</var>
  </resource-bundle>
</application>
This makes it possible to use the configured variable "bundle" anywhere in your pages, including the requiredMessage:
<h:inputText id="accountnumber" value="#{accountSetting.accountNumber}"
  required="true" requiredMessage="#{bundle.error_required_accountNumber}" />
I have found that the old way of using the loadBundle JSF component does not work with requiredMessage. The configured resource-bundle in faces-config.xml is much neater anyway.

Monday, January 3, 2011

Getting started with GIT

I recently started working on a new project and decided to try out the source repository GIT. As a long time user of CVS and Subversion (SVN), these distributed repositories take a little time getting used to.
As long as you use it yourself in a one man project, a distributed repository is almost the same as CVS and SVN. You check code out, make modifications and commit. The major difference is that you then "push" your commits to another repository, usually a remote server.

Create a local repository

To get started, after installing GIT, you can create a repository with the "init" command.
cd /path/to/repository
git init

Make modifications

Just like CVS and SVN, you can add/modify/delete files and commit them.
git add readme.txt
git commit -a -m "added readme text"
Note that this commit is stored locally, it is not committed to a central server.

Copy a repository

You can create a copy of a local repository with the "clone" command. This way you can locally make a branch.
cd /path/to/workspace
git clone /path/to/repository

Shareable repository

To create a sort of "central" repository like CVS or SVN, you can create a shareable repository with the "init" command. Do this on your server.
cd /path/to/repository
git init --bare --shared

Checkout with SSH

I use SSH to "checkout" from the remote repository, do this on your client.
cd /path/to/workspace
git clone ssh://myserver/path/to/repository
This makes a copy of the remote repository to my local workspace. Here I can make modifications and commit them. GIT will store your commit locally until you "push" this to the central repository.
git add readme.txt
git commit -a -m "added readme text"
git push origin master
This last command will update the remote repository with your commits.

See also

Friday, November 26, 2010

Flexible layout with RelativeLayout in Android

I often create a layout for my Android applications with a list and horizontal row of buttons on the bottom. Considering that Android devices come in very different sizes, you want to make this layout flexible so that the list takes up the entire area above the row of buttons on the bottom.

You could do this with a LinearLayout and use layout_weight. This often does not behave the way I want to because the LinearLayout is quite limited. I found an easier way to use a RelativeLayout, see the example: 
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      android:orientation="vertical">
    
     <LinearLayout android:id="@+id/button_bar"
          android:orientation="horizontal"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:layout_alignParentBottom="true"
          >

        <Button android:id="@+id/add_task_context_button"
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:layout_weight="1"
             android:text="@string/task_context_add_button_title"
             />
        <Button android:id="@+id/cancel_button"
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:layout_weight="1"
             android:text="@string/cancel_button_title"
             />
        <Button android:id="@+id/ok_button"
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:layout_weight="1"
             android:text="@string/ok_button_title"
             />
     </LinearLayout>

    <ListView android:id="@+id/android:list"
              android:layout_width="fill_parent" 
              android:layout_height="fill_parent"
              android:layout_alignParentTop="true"
              android:layout_above="@id/button_bar"
              android:drawSelectorOnTop="false"
              style="@style/list"  
              />

    <TextView android:id="@+id/android:empty"
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:layout_alignParentTop="true"
              android:text="@string/no_task_contexts"
              style="@style/label"                
              android:padding="10px"
              />
              

</RelativeLayout>

When you use a RelativeLayout, pay attention to the order of components. Because you specify where components are positioned relative to the parent's borders and other components.

To make my layout work, I first specify the horizontal row of buttons first (button_bar), contained in a LinearLayout with android:layout_alignParentBottom="true". When you position the list on the top with android:layout_alignParentTop="true", you also attach the bottom of the list with the top of the button row with android:layout_above="@id/button_bar". This way, the RelativeLayout will stretch the list between the top of the parent and the top of the button_bar.

Tuesday, March 2, 2010

Handling portrait/landscape switch in Android

When you develop an Android application, you will notice that when the user changes the orientation of the device, the UI framework will recreate the current screen layout with the corresponding UI objects. The framework calls these methods of the current activity: onPause, onStop, onDestroy, and then onCreate, onStart, onResume to display the activity again. The onCreate method of the activity retrieves the data it displays from a database and populates the input fields with the private populateFields method.
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  /* UI setup here */
          
  Uri itemUri = getIntent().getData();
  if (newRecord) {
    customer = new Customer();
  } else {
    customer = retrieveCustomer(itemUri);
  }

  populateFields(customer);
}
When the user switches orientation, the Android framework will destroy this activity and call onCreate again, which will retrieve the data and populate the input fields. The result is that the user will lose entered data, because this input fields will contain what the activity retrieved from the database. A solution is to store the entered data in the database in the onPause method:
protected void onResume() {
  copyFromInput(customer);
  if (newRecord) {
    saveCustomer(customer);
  } else {
    updateCustomer(customer);
  }

  super.onResume();
}
This solution mostly works and also takes care of automatic saving the entered data when the user uses the "home" button. This may also surprise the user, since the data is saved without the user explicitly asking to save it. A better solution is to use the savedInstanceState parameter that the Android framework passes into the onCreate method. This parameter is null when the user navigates to the activity. When the user changes orientation of the Android device, the framework destroys the activity after saving the contents of the input fields in a Bundle object. When the framework recreates the activity after an orientation change, the savedInstanceState parameter is not null and contains all the data that your application would want save during this change. The Android framework already automatically saves and restores the contents of the input fields, so your application does not need to do this. The thing that your application needs to be aware of is that it should only set the contents of the input fields when the savedInstanceState parameter is null.
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  /* UI setup here */
          
  Uri itemUri = getIntent().getData();
  if (newRecord) {
    customer = new Customer();
  } else {
    customer = retrieveCustomer(itemUri);
  }
  
  if (savedInstanceState == null) {
    populateFields(fragment);
  }
}

Friday, January 29, 2010

ID design

We often take the IDs that we use in databases and applications for granted. As long as we can identify a data record with a unique number, everything is great. Recently, I ran into an interesting problem in an existing application. The ID that was used has this format:
ABBBCCC

A = last digit of year
BBB = day number of the year
CCC = sequence number
For example, you could get on 2 february 2009 this ID: 9033012. This would be the twelveth data record of the day.

More digits

Sometimes, the application would generate more than 1000 data records on a day and run out of numbers. In that case the application just add digits to get something like this:
ABBBCCCDDD

A = last digit of year
BBB = day number of the year
CCCDDD = sequence number with additional digits
Example: 9003123456

Perhaps you can sense some trouble here...

Tuesday, December 15, 2009

Using a Syslog appender in log4j

The log4j framework has several appenders and one of them is the SyslogAppender. This appender lets you append log messages to syslog, a popular logging service on Unix and Linux. To use this appender, just add this to your log4j.xml configuration file:
<appender name="syslog" class="org.apache.log4j.net.SyslogAppender">
  <param name="Facility" value="USER"/>
  <param name="SyslogHost" value="localhost"/>
  <param name="Threshold" value="WARN"/>
  <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%d{MMM dd HH:mm:ss} MYAPP: %-5p %m%n"/>
  </layout>
</appender>
You can use the usual log4j API to log messages:
public class AppWithStandardLogger {
  private static final Logger logger = Logger.getLogger(AppWithStandardLogger.class);

  public void someMethod() {
    try {
       // ...
    } catch (ConnectionException e) {
       logger.fatal("#Failed to connect to database", e);
    }
  }
}

Severity level mapping

If you use logger.fatal(), the standard log4j syslog appender will log this message with the highest syslog severity, which is level 0, "emerg". This level is usually reserved for the most urgent operating system related messages and on most Unix systems will print the message on the terminal session of every logged in user. For most applications, this is probably not what you want. If an application encounters a fatal error, it should log the message as a lower level like "critical", level 2. You can do this by implementing an appender which overrides the "append" method:
import org.apache.log4j.Level;
import org.apache.log4j.spi.LoggingEvent;

/**
 * Appender that fixes level mapping to syslog.
 */
public class SyslogAppender extends org.apache.log4j.net.SyslogAppender {
 @Override
 public void append(LoggingEvent event) {
  Level log4jLevel = event.getLevel();
  Level newLevel = null;
  if (log4jLevel.equals(Level.FATAL)) {
   newLevel = SyslogLevel.FATAL;
  } else {
   newLevel = event.getLevel();
  }
  LoggingEvent newLoggingEvent = new LoggingEvent(
    event.getFQNOfLoggerClass(), event.getLogger(), event.getTimeStamp(), newLevel,
    event.getMessage(), event.getThreadName(), event.getThrowableInformation(),
    event.getNDC(), event.getLocationInformation(), event.getProperties());
  super.append(newLoggingEvent);
 }

 /**
  * Fix for level mapping - log4j "fatal" is mapped to syslog "critical".
  */
 public static class SyslogLevel extends Level {
  private static final long serialVersionUID = 1L;
  public static final Level FATAL = new SyslogLevel(FATAL_INT, "FATAL", 2);
  
  protected SyslogLevel(int level, String levelStr, int syslogEquivalent) {
   super(level, levelStr, syslogEquivalent);
  }
 }
}
This custom appender will map the log4j "fatal" messages to syslog "critical" (level 2) severity.

Stack traces

With log4j you can also log the stack trace when you pass the exception as extra parameter. In my work the syslog system will pass the messages through to Tivoli and this monitoring system should not receive stack traces. With a custom log4j PatternLayout, we can filter out stack traces by overriding the ignoresThrowable() method:
import org.apache.log4j.PatternLayout;

/**
 * Layout that prevents stack traces to be logged.
 */
public class NoStackTracePatternLayout extends PatternLayout {
 @Override
 public boolean ignoresThrowable() {
  return false;
 }
}

Putting it together

To use both the custom Appender and Layout, use this configuration:
    <appender name="syslog" class="net.kazed.log4j.SyslogAppender">
     <param name="Facility" value="USER"/>
     <param name="SyslogHost" value="localhost"/>
     <param name="Threshold" value="WARN"/>
     <layout class="net.kazed.log4j.NoStackTracePatternLayout">
       <param name="ConversionPattern" value="%d{MMM dd HH:mm:ss} MYAPP: %-5p %m%n"/>
     </layout>
    </appender>
The code is unchanged, we only changed the configuration.

Using a named syslog logger

If you want to have control over what and how the application logs to the log file and syslog, you could configure a separate logger with a name:
    <logger name="monitoring">
      <level value="WARN" />
      <appender-ref ref="syslog" />
    </logger>
In the code, you refer to the "monitoring" logger and you can log a different message to the monitoring system:
public class AppWithNamedLogger {
 private static final Logger logger = Logger.getLogger(AppWithNamedLogger.class);
 private static final Logger monitorLogger = Logger.getLogger("monitoring");
 
    public void someMethod() {
     try {
   throw new ConnectionException();
   // ...
  } catch (ConnectionException e) {
   logger.fatal("Failed to connect to database", e);
   monitorLogger.error("Failure to connect to database");
  }
 }
}
As you can see, we must change the code and add a second logger and use this to log explicitly to the monitoring system.

Conclusion

When logging to a monitoring system you can avoid changes to the source code by changing the configuration and add the monitoring logger. The standard syslog appender implementation maps the log4j fatal to syslog emerg, which level is too high for most Unix/Linux installations. A custom appender can fix this mapping. To avoid logging stack traces, you can implement custom Layout. With a named logger you can log special messages explicitly to the monitoring system. This requires changing the code.