2011-02-09

Application released!

I have finally finished the app, called Hole In One GPS, a combined range finder and scorecard for golf. So if you are a golfer you should check it out!

You can find it on android market, or follow this link to android web market:
Hole In One GPS on Android market

2011-01-22

Fixed header in a TableLayout

As the previous post this will also cover TableLayout. As with borders, there are no attribute for defining a TableRow as a header row. With header row I mean a row that will not scroll with the rest of the data in the TableLayout (providing you have declared the table to be inside a ScrollView).

Well, there is actually a way to work around this using a separate TableLayout for the header row. If you know that the header columns will be wider than the rest of the data in the table it's pretty easy. Some key notes to make this work:


  • Set all Views in the table to android:layout_weight="1"
  • You must NOT set android:stretchColumns="*" as this will override the weight attribute.
  • You must know which row is widest, and use a copy of this as a "dummy" row.
In the layout xml add the header row:
<TableLayout
  android:id="@+id/header"
  android:layout_width="fill_parent"
  android:layout_marginRight="2dp"
  android:layout_marginLeft="2dp"
  android:layout_height="wrap_content"
  android:background="@android:color/black">
  <TableRow>
   <TextView
    android:textColor="@android:color/black"
    android:layout_margin="1dp"
    android:gravity="center_horizontal"
    android:layout_weight="1"
    android:background="#ffcccccc"
    android:text="Col1" />
   <TextView
    android:textColor="@android:color/black"
    android:gravity="center_horizontal"
    android:layout_weight="1"
    android:layout_margin="1dp"
    android:background="@android:color/white"
    android:text="Col2" />
   <TextView
    android:textColor="@android:color/black"
    android:gravity="center_horizontal"
    android:layout_weight="1"
    android:layout_margin="1dp"
    android:background="@android:color/white"
    android:text="Col3" />
   <TextView
    android:textColor="@android:color/black"
    android:gravity="center_horizontal"
    android:layout_weight="1"
    android:layout_margin="1dp"
    android:background="@android:color/white"
    android:text="Col4" />
   <TextView
    android:textColor="@android:color/black"
    android:gravity="center_horizontal"
    android:layout_weight="1"
    android:layout_margin="1dp"
    android:background="@android:color/white"
    android:text="Col5" />
  </TableRow>
 </TableLayout>
Then add the main table inside a ScrollView containing a copy of the header row as a dummy row with a height set to "0dp" making it invisible:
<ScrollView
  android:id="@+id/table_scroll"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:layout_marginRight="2dp"
  android:layout_marginLeft="2dp">
  <TableLayout
   android:id="@+id/maintable"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   android:background="@android:color/black">
   <TableRow>
    <TextView
     android:layout_height="0dp"
     android:layout_weight="1"
     android:layout_marginRight="1dp"
     android:layout_marginLeft="1dp"
     android:text="Col1" />
    <TextView
     android:layout_height="0dp"
     android:layout_weight="1"
     android:layout_marginLeft="1dp"
     android:layout_marginRight="1dp"
     android:text="Col2" />
    <TextView
     android:layout_height="0dp"
     android:layout_weight="1"
     android:layout_marginLeft="1dp"
     android:layout_marginRight="1dp"
     android:text="Col3" />
    <TextView
     android:layout_height="0dp"
     android:layout_weight="1"
     android:layout_marginLeft="1dp"
     android:layout_marginRight="1dp"
     android:text="Col4" />
    <TextView
     android:layout_height="0dp"
     android:layout_weight="1"
     android:layout_marginLeft="1dp"
     android:layout_marginRight="1dp"
     android:text="Col5" />
   </TableRow>
  </TableLayout>
 </ScrollView>
This is how it is displayed:


Note that this will only work if you know that the header columns is wider than the rest of the data. If any of the rows in the "main" table is wider than the header row the columns will no longer be aligned. However, there is a way to solve this as well. If you can identify the widest row in the "main" table, then just do the same procedure as above, but this time add this as a dummy row to the header table.

2011-01-06

Border in a TableLayout

As specified in the documentation for TableLayout, borders are not supported (there is no attribute android:border or something like that). So you have to be a little creative to accomplish this.

In the root layout, specify a background color (this will be the border color), for instance:
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@android:color/black">
Then for each cell in the table specify another background color, and set a margin:
<TextView
    android:id="@+id/tv_col2"
    android:textColor="@android:color/black"
    android:gravity="center_horizontal"
    android:layout_margin="1dp"
    android:background="@android:color/white"
    android:text="Col2" />
This will produce the following layout, depending on the number of rows and columns of course:

2011-01-02

RelativeLayout alignment trick

Have you ever tried to place two buttons next to each other with each button taking up half the screen? Are you using RelativeLayout as well? Finding it hard? There is actually a pretty neat trick for this, using a View layout as a helper object. This will not be visible since the size is 0 pixels, and after centering the View horizontally, the buttons can be aligned to the left/right:
<View
 android:id="@+id/buttons_helper"
 android:layout_width="0dp"
 android:layout_height="0dp"
 android:layout_centerHorizontal="true" />
<ImageButton
 android:id="@+id/previous"
 android:src="@drawable/previous_btn"
 android:layout_toLeftOf="@id/buttons_helper"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 android:layout_margin="3dp"
 android:layout_alignParentBottom="true" />
<ImageButton
 android:id="@+id/next"
 android:src="@drawable/next_btn"
 android:layout_toRightOf="@id/buttons_helper"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 android:layout_margin="3dp"
 android:layout_alignParentBottom="true" />
Note that I'm using ImageButton above. You will have to reference a valid picture in android:src="@drawable/...", or simply use a Button object instead. This will result in the following layout:


This will be cheaper than having a LinearLayout with weight attributes set. If you know any other way to accomplish this with RelativeLayout, please let me know.

2010-12-18

Cascade delete and Triggers

If you are using a database in your android application you are probably going to delete data at some point. When dealing with one to many relationships, the children should also be deleted when the master is deleted.

For example, a table Album contains many songs stored in another table Songs. The table Songs has a foreign key album_id referencing the primary key id in table Album.

Sqlite3 doesn't support cascade delete until version 3.6.19 (available on Android 2.2), so if you want this to work on a device running android prior to version 2.2 it can be solved using Triggers:

In your database helper class, create a String containg the trigger command:
private static final String CREATE_TRIGGER_DELETE_ALBUM =
     "CREATE TRIGGER fkd_songs_album_id "
       +"BEFORE DELETE ON Album "
       +"FOR EACH ROW BEGIN "
       +"DELETE FROM Songs WHERE album_id = OLD._id; " 
       +"END;";
and add this after you have created the tables:
@Override
  public void onCreate(SQLiteDatabase db) {
       //Create tables
        .
        .
        db.execSQL(CREATE_TRIGGER_DELETE_ALBUM);
  }
Now, when deleting an Album all of it's songs will also be deleted.

2010-12-15

Optimizing code

Since mobile devices are pretty limited when it comes to hardware specs, there are a few things you should think of as a developer to optimize the code. This will make the app run faster and smoother, and the user will more likely keep using your app.

1. One thing I noticed when debugging my app, was a lot of lines in Logcat that looked like: "Gc freed x objects / y bytes in z ms". This is the garbage collector doing it's work and it's of course impossible to write an app that has no GC at some point. But if you start seeing these lines frequently and when the app is idle, you should be able to optimize the code.

The most probable cause for this is that you create too many objects. Look for code like:
public void onClick(View view) {
  String aString = object.getString();
  Integer anInteger = object.getInteger();
}
Change this by declaring the variables as class member variables instead, then you will create only one object instead of one object/onclick action. Note that you shouldn't use this as a general rule though. A member (global) variable's life cycle is longer compared to local variables, so it will allocate memory for a longer time.

In a GPS listener method executing once every second you should use member variables.

In a method that will be executed a few times only, local variables are preferred.

2. Analyze the layout. If you start nesting several LinearLayout you should consider switching to RelativeLayout instead. The deeper the layout tree becomes, the more expensive it is. One way to analyze this is to use the tool Hierarchy Viewer (included in the SDK). Run your application and click on <Focused Window> and then hit Ctrl + L to load the current activity. You should aim for a wide layout instead of deep layout with many levels.

3. Avoid boxing and Un-boxing of objects:
public void stupid() {
  Integer anInteger = Integer.valueOf(new Float(String.valueOf(1.1f)).toString());
}
This will create many objects. Use the primitives (i.e. float, int) instead of their wrapper classes (i.e. Float, Integer) when possible.

4. Access the member variables directly instead of using a getter method. It will always be faster.

5. Reuse views. For example, instead of creating a new TextView object for each TextView in a layout, you should consider reusing only one TextView.

6. This is more of a power consumption optimization, but a good one. If you are using GPS listener for example, you should consider turning it off when the phone is idle or when the user exits the activity where GPS is used, by implementing the onResume and onPause methods:
@Override
protected void onResume() {
  super.onResume();
  
  // Acquire a reference to the system Location Manager
  locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
  // Define a listener that responds to location updates
  locationListener = new LocationListener() {
   @Override
   public void onLocationChanged(Location location) {
  .
  .
  .
  }
 // Register the listener with the Location Manager to receive location
 locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener);
}
@Override
 protected void onPause() {
  super.onPause();
  locationManager.removeUpdates(locationListener);
  locationManager = null;
 }

2010-12-01

SyntaxHighlighter

The first problem I ran into was of course how to include code snippets in my blog posts. So I stumbled upon SyntaxHighlighter and it works perfectly.

To use it on your own blog page, simply follow these steps.

1. On your blogger page, go to "Design/Template" -> "Edit HTML" and add these lines above the </head> tag:
<link href='http://alexgorbatchev.com/pub/sh/current/styles/shCore.css' rel='stylesheet' type='text/css'/>
<link href='http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css' rel='stylesheet' type='text/css'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js' type='text/javascript'/>

2. Add support for the languages needed, a full list of supported languages can be found here. For example continue to add these lines:
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCss.js' type='text/javascript'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJava.js' type='text/javascript'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js' type='text/javascript'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushSql.js' type='text/javascript'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js' type='text/javascript'/>

3. Lastly add this line
<script language='javascript'> SyntaxHighlighter.config.bloggerMode = true;
 SyntaxHighlighter.config.clipboardSwf =
 'http://alexgorbatchev.com/pub/sh/current/scripts/clipboard.swf';
 SyntaxHighlighter.all(); </script>

Click on Save to complete the implementation. Try it out by creating a new post and in the HTML section type:


<pre class="brush: java">
private void test() {
System.out.println("Testing code snippets");
}
</pre>