Friday, November 4, 2011

Irrduino Project Open Sourced!

As of today, I have found a co-conspirator in my remote control irrigation project, which I have christened, Irrduino. (irrigation + Arduino = Irrduino! Get it?)

Due to this 100% expansion in support staff for this project, I have moved my code out onto code.google.com so my new colleague can help me extend it:


http://code.google.com/p/irrduino/


This code is probably not useful to anyone except me, and possibly other folks who are already working on using Arduino to control their sprinkers (of which there are a few), but it is exciting (and a little scary) to put it out there in the world.

Friday, September 23, 2011

Calculating Dates with Java Calendar

Found a tricky problem with Java Calendars today. Essentially, I discovered that you can't reuse Calendar objects in a calling function, because they are singletons. Take this code, for example:

How NOT to do it

// give me the date X days from the day represented by cal
public static String getOffsetDate(Calendar cal, int daysOffset){
    cal.add(Calendar.DATE, daysOffset);
    return dateFormat.format(cal.getTime());
}

This function appears to work until you call it a second and third time, and find out (like I did) that the new calendar value is being retained each time, and so increasing the daysOffset incrementally results in dates that are additively further out:

Today :2011-01-15
1 days ago     : 2011-01-14
2 days ago     : 2011-01-12 
3 days ago     : 2011-01-09
4 days ago     : 2011-01-05
5 days ago     : 2010-12-31 // wait. what?!

Apparently the Calendar.getInstance() gives you a singleton. Try passing it around to other methods, expecting it to make a new instance of itself and you'll run into problems. I found this to be extremely annoying and non-intuitive, and the Java Doc does not warn you about this, either.

Solution

So what's the solution? You have to create a new instance, or clone() the instance of the calendar before making the calculation (see sample code below).

package com.test.calendar;

import java.text.SimpleDateFormat;
import java.util.Calendar;

public class CalendarTestDaysAgo {

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
 
    /**
     * @param args
     */
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance(); // today
        
        // set to January 15
        cal.set(Calendar.MONTH, Calendar.JANUARY);
        cal.set(Calendar.DAY_OF_MONTH, 15);
        
        System.out.print("Today :"+getOffsetDate(cal,0) + "\n");
        for (int d = 1; d < 32; d++){
            System.out.print(d + " days ago: " + getOffsetDate(cal, d * -1) + "\n");
        }
    }

    // give me the date X days hence from the day represented by cal
    public static String getOffsetDate(Calendar cal, int daysOffset){
                                          // this clone() call is required
                                          // otherwise subsequent changes are additive 
        Calendar newCal = (Calendar) cal.clone();
  
        newCal.add(Calendar.DATE, daysOffset);
        return dateFormat.format(newCal.getTime());
    }

}
Results in:
Today :2011-01-15
1 days ago     : 2011-01-14
2 days ago     : 2011-01-13
3 days ago     : 2011-01-12
4 days ago     : 2011-01-11
5 days ago     : 2011-01-10 // that's better.
...

Tuesday, September 20, 2011

Arduino Remote Control Irrigation Rig - Prototype 2

Last night I finished Prototype 2 of my Arduino irrigation remote control rig. Added the screw terminal headers so I can connect the irrigation control wires. The very last screw terminal on the right with the white wire is my power bus. It's wired into the COM terminals of all the relay boards.



For those of you who may be wondering, this iteration is mounted on an old IKEA cutting board. I needed the extra physical strength, because the 18 gauge wire used for the irrigation wires is stiff and heavy.

I drilled holes in the cutting board that are just big enough for 4-40 machine screws to poke through (7/32 drill bit, I think). The relay boards have long pins on the bottom, so they need be elevated off the board. I found some 1-inch spacer nuts at Fry's, and that's what the relay boards are mounted on top of. It worked so well, I will be doing the same for the Arduino ADK and Ethernet Shield.

Next step: Wire it up to my irrigation control box and try real hard not the burn the house down. The real trick will be to tap into my irrigation controller's 24-volt power supply without disabling the controller. Fun times!

Saturday, August 27, 2011

It Works! Remote Controlled Irrigation via Arduino

Success! I can now remotely turn on my sprinkler zones via my Arduino-powered web server.

The picture does not do justice to the amazing amount of wiring nuts I used to make this work. I think there are nearly a dozen of them holding all the wires together.



I did not manage to hook the Toro controller's 24-volt power supply into my rig. Sharp-eyed readers might be able to spot the two 9V batteries I clipped into my valve control circuits as a temporary power supply.

Anyway, it works! Awesome! Now to write the native Android controller app...

Edit: I have since torn down this setup so that I can mount my Arduino hardware on something a little more substantial (and less flammable) than cardboard. Stay tuned...

Friday, August 19, 2011

Google Authorship with Blogger

In the last couple of weeks, I have taken an interest in Google's attempt to register authorship for articles published on the web. This initiative allows web authors to essentially claim ownership of a particular piece of content that they publish.

I think this a great way to recognize folks who are serious about the content they publish on the web and a way to help folks looking for good content to find it faster. I'm all in for that. Now, how to get it working?

Caution: This process is a bit fiddly using Blogger, which is ironic 'cause it's a Google web property, right? I have sent some feedback to the Googlers who will hopefully make this easier in the future. If you are not comfortable editing raw html, I recommend you wait until the "register my authorship" option (or whatever they call it) is implemented in Blogger. Also, the solution is not suitable for a multiple-author blog. Fair warning. If you manage to get it working in that scenario, please leave a comment as to how you accomplished it.

How to Plug into Authorship with Blogger

There are a few steps you need to complete to get authorship set up:
  1. Setup a Google Profile (you don't have to be on Google+ to do this). If you are on Blogger, you probably already have one.
  2. Add your Blogger site to your Links list. Make sure you:
    • Click the This page is specifically about me option.
    • Set the visibility of the Links list to Anyone on the web.
  3. For each page of your blog, make sure there is a link to your Google Profile, and make sure the anchor text starts or ends with "+". (See specific instructions for Blogger in the next section.)
  4. Test your implementation using this page.
  5. Fill out this form for the Googlers.
  6. Wait.
  7. Wait.
  8. Wait some more.
  9. Obsessively run Google searches on your blog posts.
  10. Finally see your mug next to a search result, declare success and drink a celebratory beverage of your choice.

Add a Google+ Profile Link to Your Blogger Byline

Here's how I changed my byline to include a link to my Google+ profile. You have to manually edit your Blogger template to do this.
  1. Log into Blogger
  2. Go to your blog dashboard and click Design > Edit HTML)
  3. In the Edit HTML page, go down to the Edit Template section and make sure you click on the Expand Widget Templates option on the top right of the edit box.
  4. In the edit box, find the section containing a div with class='post-footer-line post-footer-line-1'
  5. Wrap the <data:post.author/> tag with an "a" tag containing a url to your Google Profile, followed by "?rel=author":
    ...
        <div class='post-footer'>
        <div class='post-footer-line post-footer-line-1'><span class='post-author vcard'>
            <b:if cond='data:top.showAuthor'>
              <data:top.authorLabel/>
              <span class='fn'>
                <a href='https://plus.google.com/103507786235300238642?rel=author'>
                +<data:post.author/></a>
              </span>
            </b:if>
          </span> <span class='post-timestamp'>
          ...
    
  6. Also, you need to put a "+" at the beginning or end of the anchor text for Google to accept this as a valid author reference. (No, that "+" is not a typo; It's required.)

Props to the Kevin & Amanda blog for providing the blogger signature template tips that lead to this solution.

Further Thoughts

As a writer, I think this a great initiative for content producers to get credit for their contributions to the sea of knowledge that is the interweb. I think it also has the potential to help folks looking for information to sort out the real sources of information from the increasing numbers of republishers, SEO hackers and link farmers out there.

If that describes you, then hey, I realize we all need to make a living, but ask yourselves; are you actually contributing any new value to this great interweb thing we all share? If not, then maybe it's time to rethink your strategy.

Wednesday, August 10, 2011

Arduino Remote Control Irrigation Rig Update

Last night I tried hooking up my Arduino irrigation remote control rig to all three of the SeeedStudios Relay Shield I bought a while ago. Now here's a nice hack:



I took a piece of cardboard and some unused 1/8 inch rivets, poked the non-rivet end into the cardboard and set the Relay Shield on top of them using the mounting holes. Not a good long term solution, obviously, but it kept the boards from sliding a round while I was wiring everything up.

This project is going to need a much more solid base because the wires for the irrigation system are pretty heavy. I'm thinking about getting a nice, thick sheet of clear acrylic for mounting. I will also have to go find a screw terminal block and use that in between the relay boards and the irrigation wire. Fun! And, I also need to figure out how to get an ethernet connection into my garage where the current irrigation controller lives. Heh.

Arduino Pin Use Limitations: Pin 10, 13

I have a total of 12 relays I can control with the Relay Shields, and I was hoping to use pins 2 to 13 for that purpose. Well, trying that didn't go so well. Apparently the Arduino Ethernet Shield lays claim to pin 10 and so I cannot use it for output. Also, pin 13 (which is connected to the ADK board's LED) gets flashed a lot during program uploads. So, if I connect one of my relays to pin 13, it gets opened and closed dozens of times a second while I'm uploading a new Arduino program. That's going to cause unwanted wear and tear on the relay and possibly overheat it as I noticed when connecting the relays to pin 0 and 1.

So now I'm down to using only 10 relays of my 12. That's more than enough for my project, and if I really need them the ADK lot's more digital outputs. I was hoping to be able to control twelve relays with just the pins available on a Arduino Uno though. Maybe I can use the analog pins to control the relays?


Monday, August 8, 2011

Deleting Files from Android SDCard

Today I needed to delete a bunch of files off of my phone because some rogue test program I was running generated several hundred image files in the root of my SDCard. Here is program I wrote to get rid of them:

package com.example.android.filemanagement;

import java.io.File;
import java.io.FilenameFilter;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;


public class FileCleanupActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

       Button b = (Button) findViewById(R.id.go);
        b.setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View v) {

				// Delete those files!
		        Iterator it = listFiles().iterator();
		        while (it.hasNext()){
		        	it.next().delete();
		        }
				
			}
        });
        
        TextView tv = (TextView) findViewById(R.id.file_list);
        
        StringBuffer sb = new StringBuffer();
        Iterator it = listFiles().iterator();
        while (it.hasNext()){
        	sb.append(it.next().getName() + "\n");
        }
        
        tv.setText(sb.toString());
    }
    
    private static List listFiles(){
    	
    	File[] files = Environment.getExternalStorageDirectory()
    					.listFiles(new ExtensionFilter("jpg"));
    	
    	return Arrays.asList(files);
    }
}

class ExtensionFilter implements FilenameFilter {

	private String ext;
	
	public ExtensionFilter(String ext){
		this.ext = "."+ext;
	}
	
	@Override
	public boolean accept(File dir, String filename) {
		return filename.endsWith(ext);
	}
	
}

Friday, August 5, 2011

Uris and Intents and Cameras, Oh My! - Android Development

For the last couple of days, I've been hacking on the Android camera API functions. Specifically, I've been trying to invoke the camera with an Intent (new Intent(MediaStore.ACTION_IMAGE_CAPTURE);), rather than building out my own preview and capture function.

The main stumbling block has been how to pass file location to the Intent so that the Camera function saves images where I want them to go, rather than the default location (/mnt/sdcard/DCIM/Camera/ I believe). I've looked all over the internet for how to do this properly. After getting some expert advice, this is the best solution I found:

private static Uri getImageFileUri(){

     // Create a storage directory for the images
     // To be safe(er), you should check that the SDCard is mounted
     // using Environment.getExternalStorageState() before doing this
     
     File imagePath = new File(Environment.getExternalStoragePublicDirectory(
                                Environment.DIRECTORY_PICTURES), "CameraTest");
     if (! imagePath.exists()){
      if (! imagePath.mkdirs()){
       Log.d("CameraTestIntent", "failed to create directory");
       return null;
      }
     }
     
     // Create an image file name
     String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
     image = new File(image.getPath() + File.separator +
                        "IMG_"+ timeStamp + ".jpg");

     // Create an File Uri
     return Uri.fromFile(image);
    }

The key appears to be not to use ContentResolver (e.g.,
getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);) to generate a Uri, because this means the Camera app will just save the image in the default location with a default name.

The above solution saves the image in a shared location so other applications (like Gallery) can access it. Files saved to this location also will not be deleted when your application is uninstalled. If you want your image files to be not easily visible to the user and removed when your app is uninstalled, call your application's Context object to create the image path:

     File imagePath = context.getExternalFilesDirectory(
                         Environment.DIRECTORY_PICTURES);

You don't need the "CameraTest" bit in this case, because the imagePath will be specific to your application: /mnt/sdcard/Android/data/<app_package>/Pictures/

Monday, August 1, 2011

Web Relay Test Arduino Sketch

I wrote this Arduino sketch to test firing the relays on 2 of my Seeed Studio Relay Shields. Shield 1 is connected to digital pins 2 to 5 and Shield #2 is connected to digital pins 6 to 9.

This sketch enables all those pins for output and then turns them on and off for half a second (500 milliseconds) in sequence.

#include <SPI.h>
#include <Ethernet.h>


byte mac[] = { 0x90, 0xA2, 0xDA, 0x00, 0x50, 0xA0 };    //physical mac address
byte ip[] = { 192, 168, 1, 177 };   // ip in lan
byte gateway[] = { 192, 168, 1, 1 };   // internet access via router
byte subnet[] = { 255, 255, 255, 0 };                   //subnet mask
Server server(80);                                      //server port
byte sampledata=50;            //some sample data - outputs 2 (ascii = 50 DEC)            

String readString = String(30); //string for fetching data from address

void setup(){
//start Ethernet
Ethernet.begin(mac, ip, gateway, subnet);

//Set pins 2 through 9 to output
for (int i = 2; i <= 9; i++){
  pinMode(i, OUTPUT);  
}

//enable serial data print  
  Serial.begin(9600);
}
void loop(){
// Create a client connection
Client client = server.available();
  if (client) {
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        //read char by char HTTP request
        if (readString.length() < 30) {
          //store characters to string
          readString.concat(c);
        }  
        //output chars to serial port
        Serial.print(c);
        //if HTTP request has ended
        if (c == '\n') {
          if(readString.indexOf("test=all") > -1) {

httpReply(client);

// cycle through pins 2 to 9
for (int i = 2; i <= 9; i++){
               Serial.print("Writing pin ");
               Serial.print(i);
               Serial.println(" HIGH");
               digitalWrite(i, HIGH);    // set the LED on
               delay(500);
               Serial.print("Writing pin ");
               Serial.print(i);
               Serial.println(" LOW");
               digitalWrite(i, LOW);
               delay(500);
             }
          }
          //clearing string for next read
          readString="";
          //stopping client
          client.stop();
        }
      }
    }
  }
}

void httpReply(Client client){
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/html");
  client.println();
  client.println("{\"status\" : \"executing\" , \"cmd\" : \"test=all\"}");
  client.stop();
}
This code is loosely derived from code by Mareika Giacobbi of nerdydog.it for his Domotic Home project.

ADK + Ethernet Shield + 2 Relay Shields = Almost Working Prototype

Here is the current iteration of my Arduino Irrigation Controller rig:


I now have 2 of the 3 Relay Shields connected to the ADK with Ethernet. Did I say connected? It's a bit fragile to say the least. Don't breathe on it! Those green wires going into the middle of the Relay PCBs are basically held their by the tension of the wires wanting to straighten out.

I really wish the Seeeduino guys had put on those extra long headers and pins on these Relay Shields like on the Arduino Ethernet Shield. It's basically impossible to attach them to the Arduino board or the Ethernet Shield because these huge freakin' pins from the screw terminals extend 2mm out the bottom of the board (and cause shorts against tall components, if you're not careful).

Now I'm waiting on some parts from AdaFruit so I can connect the 3rd Relay Shield and create a more reliable connections to the first two boards. But, where am I going to put Relay Shield #3? Hmmm...

One other thing that confuses/frustrated me with the Relay Shields is that they cannot run off USB power (5v), you have to plug them into their own 9v power supply, or plug the Arduino into a 9v+ power supply and route the Arduino's 9v pin (not the 5v pin) and ground to the Relay Shields. That's a bit annoying. The important thing, though, is that it works. On to the next stage.

Pin 0 and 1: Don't Use for Output!

Oh, and one other thing: If you are thinking of using digital pin 1 and pin 2 to control the Relay Shields: Don't. I tried using them in an earlier version of this prototype and saw some worrying behavior. Namely, when uploading a sketch to the Arduino board, pins 1 and 2 flash on and off very quickly which turns on the connected relays on and off very quickly and makes them rather hot. I didn't have a catastrophic failure; the relays still work, but that'll be the last time I try using pin 1 and 2 for anything other than TX and RX.

Thursday, July 28, 2011

ADK / Ethernet Shield Problem Solved!

Fixed! A quick trip to Radio Shack for some hookup wire (22 gauge, solid) and I manually wired the ICSP pins from the Ethernet Shield and now I have a web server that's just a bit bigger than a deck of cards.



Here is the ISCP pin mapping to the ADK pins for future reference:


(MISO) 50 <-- 1 o o 2 --> +5V
(SOCK) 52 <-- 3 o o 4 --> 51 (MOSI)
RESET <-- 5 o o 6 --> GND


Only pins 1,3 and 5 need to be connected (orange wires in the picture). The Ethernet shield still works if you leave pins 2, 5 and 6 disconnected (red, yellow and green wires in picture).

Android ADK and Arduino Ethernet Shield Woes

I think I figured out why my Arduino Ethernet shield won't play nicely with my Android ADK Arduino board.

You see the black, six-pin header connector on the right side of the blue board (the Ethernet Shield)? That's the the ICSP bus in Arduino-speak. That's supposed to connect into a matching set of pins on the white Arduino board so the Ethernet shield can to its "talkin' to the interweb" thing. Do you see matching pins on the ADK board? Yeah, neither do I.



Turns out this is a bit of a problem, because the Arduino Ethernet Shield documentation states:

Arduino communicates with both the W5100 and SD card using the SPI bus (through the ICSP header).


Like I said, bit of a problem. Looks like there are exposed copper pads on the bottom of the ADK board for the ICSP connections, so I could solder in some connections if absolutely necessary. Those pads (MISO1, MOSI1, SOCK1) are clearly wired to 50,51 and 52 as described in the Arduino Mega 2560 schematics.

Time for some hand wiring...

Friday, July 22, 2011

Arduino Irrigation Controller Projects Across the Web

I've done a bunch of research on this type of project in the last few weeks. Here are some interesting links from around the web that I find useful and/or interesting:

Arduino Irrigation Projects

Seems like all these guys built custom hardware, which is cool, but I'm going to use mostly off the shelf parts (for the first iteration, anyway).

Supporting Technology Projects
  • DomoticHome project : not actually controlling irrigation, but using Ethernet shield and with a Android app client
  • RESTduino - A nice REST interface for Arduino. Very cool.

Saturday, July 16, 2011

Let the Arduino Hacking Begin!

I have jumped into Arduino hardware hacking and bought some shields to use with the ADK I got from work. The idea is to use these to remotely control my irrigation system at home.

Friday, February 11, 2011

Customized ListView Data Display in Android

ListViews are very handy user interface tools on the Android platform. We see them all the time in applications: phone number lists, contact name lists, shopping lists, all kinds of lists. The example Notepad application for Android, arguably the second app that all fledgling developers build is—you guessed it—a list.

Android provides some easy to use tools for mapping data from your application’s database into a ListView. However, you can quickly reach the limit of the basic tools as soon as you try to move past simply displaying data as it appears in your database. In this article, I discuss how to go beyond basic display of data in ListViews.

Lists, Cursors and the SimpleCursorAdapter

The SimpleCursorAdapter provides a simple way to map database fields into an entry in a List. All you have to do is provide a Cursor to your database table with the fields you want to display, map the fields to ids in your list item layout xml file and you are done:

// Get all of the items from the database and create a list
       Cursor cursor = mDbHelper.fetchAllItems();
       startManagingCursor(cursor);
       
       // Create a mapping between database columns and layout fields
       String[] from = new String[] { DbAdapter.COL_TITLE };
       int[] to = new int[] {R.id.text1};
       
       // Create a cursor adapter and set it to display using a row layout
       SimpleCursorAdapter notes = 
              new SimpleCursorAdapter(this, R.layout.list_item, cursor,
                                        from, to);

       setListAdapter(notes); // assumes this is a ListArray class

This approach works great for a basic list of text items or numbers, but what if you want to do something beyond just displaying database data as-is? Consider the following list of things you might want to do when displaying your data in a List:
  • Format the data for display
  • Optionally display data for each list item (skip display of some items)
  • Combine data from multiple data fields into a single display field
SimpleCursorAdapter does not allow for these variations, but hey, what did you expect? It is called SimpleCursorAdapter for a reason and for basic mapping it’s a perfect solution.

ComplexCursorAdapter?

So what is the next step up? No, not ComplexCursorAdapter; I made that up. The next step is to extend either the CursorAdapter or ResourceCursorAdapter classes. For this tutorial, I’ll focus on the use of ResourceCursorAdapter, which provides the ability to use XML-based layout resources for your list items.

Getting Started

This tutorial assumes you have already successfully created a few Android applications. So, if you have not done that yet, get Eclipse and the Android SDK, build Hello Android and the Notepad example applications and come back.

Create a new Android project with a ListActivity as the primary Activity for the application. If you want to follow along with my example project, you must choose Android SDK version 2.2 (8) or higher as your build target. After the Android Project Wizard creates the template Activity class, modify it to extend ListActivity (and make sure to update your imports):

public class CustomDataList extends ListActivity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

Next, modify the default main.xml layout to include a ListView. (See the example project for an example.) If you are using your own layout, make sure your ListView includes the default id for lists:

    <ListView 
              android:id="@android:id/list" ...

Create a layout xml for your list items under the ProjectName > res > layout. Make sure your layout has at least two TextView fields and make a note of their ids. If you need something to get started, check out the list_item_with_description.xml in the example project.

In your ListActivity class, create a new inner class that extends ResourceCursorAdapter. This inner class is the custom CursorAdapter we will use to manage how data is bound to a list item:

    private class MyListAdapter extends ResourceCursorAdapter {
        
        public MyListAdapter(Context context, Cursor cursor) {
            super(context, R.layout.list_item_with_description, cursor);
        }

        @Override
        public void bindView(View view, Context context, Cursor cursor) {
            // TODO Auto-generated method stub
        }
    }

The first method is the constructor, where we declare which list item layout to use. The bindView method is where the action happens. In this method, we take control over how data from our database gets populated into each list item.

Got Data?

In order to do data binding, we need data. So, use the DbAdapter class in the example project for sample data (recommended) or use a database adapter from one of your applications. If you use your own database adapter, make sure you have a method that returns a Cursor object with three or more data fields. In the example DbAdapter class, this method is called fetchListItems().

Connecting the Custom ListAdapter

Now we have a database and method to get a Cursor to our data, so it is time to connect the data pipes. In your ListActivity class, add the following to the onCreate() method:

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        DbAdapter dbHelper = new DbAdapter(this);
        dbHelper.open();
        
        // Get a Cursor for the list items
        Cursor listCursor = dbHelper.fetchListItems();
        startManagingCursor(listCursor);
        
        // set the custom list adapter
        setListAdapter(new MyListAdapter(this, listCursor));
    }


These statements create a connection to your database, get a Cursor to your list data and then set the customized ListAdapter, which tells the ListView how to render your data. Actually, we have not defined how the data is rendered for each list item yet; That is our next step.

Note: If you are getting a “method setListAdapter() is undefined” error, make sure the enclosing class extends ListActivity. This method is not available in a regular Activity class.

Binding it Your Way

With the implementation so far, no data is shown in our list! Now it is time to fix that problem by defining a data binding in the bindView() method of our custom ResourceCursorAdapter inner class. Here is how we update bindView() to render the data from the example database:

@Override
public void bindView(View view, Context context, Cursor cursor) {

       TextView title = (TextView) view.findViewById(R.id.item_title);
       title.setText(cursor.getString(
                           cursor.getColumnIndex(DbAdapter.COL_TITLE)));
       
       TextView details = (TextView) view.findViewById(
                                            R.id.item_details);
       StringBuffer detailsText = new StringBuffer();
       
       int price = cursor.getInt(cursor.getColumnIndex(DbAdapter.COL_PRICE));

       if (price > 0){
              detailsText.append("$"+price+".00");
       } else {
              detailsText.append("Price Unavailable");
       }
       String description = cursor.getString(cursor.getColumnIndex(
                                             DbAdapter.COL_DESCRIPTION));
       if (description != null && description.length() > 0){
              detailsText.append(", "+description);
       }
       details.setText(detailsText.toString());
}

As you can see from this example, while we can still do a simple mapping of an item title data to a TextView, we also have the opportunity to do apply any rendering logic we want to the data retrieved from our database. A word of caution, however, is in order. Since the bindView() code is executed fairly frequently, it is a good idea to keep your rendering logic to a minimum, otherwise your users may experience significant pauses as your list renders.

Code Download


Here is the example project for this tutorial. You need Eclipse and the Android SDK with platform version 2.2 (8) to be able to run this project.

CustomDataList_CodeProject.zip

Conclusion

I hope you have found this article to be a useful next step in the evolution of your understanding of working with ListViews, Cursors and databases in Android. Please add your comments and questions below. Happy programming!

Wednesday, February 9, 2011

Managing Date and Time Data in Android Apps

The Android platform offers many well-defined options for managing various types of data in your applications. However, date and time values, such as in January 1, 2011 at 12:00pm, are an example of a data type on Android where you have many options, but the best path for storing and displaying them is not clear. This article discusses my investigation into managing date and time values for one of my Android applications, my conclusions and the approach I finally implemented.

Date and Times in SQLite

SQLite, the embedded database library in the Android platform, does not have a native date and time storage type (see SQLite docs), and instead suggests the use of TEXT, REAL or INTEGER data types. The library also has functions for working with dates and times, but I prefer to do my date and time calculations in Java, so these offerings were not of much use to me and did not figure into my decision about how to store this type of data.

Text or Numbers?

So, what data type to use? Text strings or number values? Text strings are convenient for display, but they are a pain when it comes time to do calculations (is this date more recent than that date?). In addition, if I ever want to do localized date display (European Day, Month Year format versus US Month / Day / Year), having dates preformatted for one locale means quite a bit of work to transform it into other locales. So, text strings get tossed out as a storage type!

That leaves storing dates and times as numbers. The big question there, is how precise do these dates and times need be? The SQLite INTEGER type can handle values up to 8 bytes (64 bits) in size. Using Java’s long value for time in milliseconds from January 1, 1970 might be convenient. However, I tossed out this idea because of concerns about the size of the numbers generated by this method and the fact that they are completely unreadable as dates without programming assistance. My application only needed precision down to seconds and I wanted the numbers to be as human-readable as possible without actually being formatted strings.

What’s Your Number?

With all this in mind, I decided to define a date and time number format that was closely related to printed dates and times, but that could be easily sorted into more recent and less recent. The format I defined starts with a numbered year, then month, then day of month, hours in 24 hour-format, minutes and then seconds.

Example Date/Time:   10/29/2007, 12:06am, 21 seconds
Date/Time Number:    20071029000621

Dates and times stored in this format can be interpreted by humans with a little instruction and the values will sort nicely, with higher values being the most recent.

Note: If you need to do length calculations in a specific unit (how many days between date X and date Y) this format isn’t much help, but unless you save your date and time values in that specific unit (e.g., days) doing those types of calculations is always going to be a bit of a pain.

The Implementation

With this design in mind, I created the following functions to help me convert between the Calendar objects and the long values I was going to retrieve out of my SQLite database:

    public static final String DATE_FORMAT = "yyyyMMddHHmmss";
    private static final SimpleDateFormat dateFormat = new
       SimpleDateFormat(DATE_FORMAT);
    
    public static long formatDateAsLong(Calendar cal){
       return Long.parseLong(dateFormat.format(cal.getTime()));
    }
    
    public static Calendar getCalendarFromFormattedLong(long l){
       try {
                     Calendar c = Calendar.getInstance();
                     c.setTime(dateFormat.parse(String.valueOf(l)));
                     return c;
                     
              } catch (ParseException e) {
                     return null;
              }
    }

This implementation uses Java’s SimpleDateFormat object to convert Calendar objects formatted long dates and formatted long dates back to Calendar objects.

Note: For those of you who might be wondering why I’m using a Calendar object as the primary Java data type for my date/times (rather than the Date object), it is because the implementation of Android’s DatePickerDialog is more closely aligned with the Calendar object.

In the database adapter for my application, the create and insert functions that include date and time information take a Calendar object and convert to a long just prior to inserting it into the SQLite database:

    public long updateDateRecord(long id, Calendar calendar){
        ContentValues values = new ContentValues();

        // Here is where we apply the Data DateFormat:
        values.put(COL_DATE, DateDataFormat
           .formatDateAsLong(calendar));
        
        return mDb.update(TABLE_DATES, values, COL_ID + "=" + id, null);
    }

On the user interface side, I retrieve the date and time value as a long, convert it to a Calendar object and then format the date into a string for display:

    private void populateDate(){
       String date = null;
       if (mListCursor != null && mListCursor.getCount() > 0){
              
              dateId = mListCursor.getLong(
                    mListCursor.getColumnIndex(DbAdapter.COL_ID));
              long d = mListCursor.getLong(
                    mListCursor.getColumnIndex(DbAdapter.COL_DATE));

              if (d > 0){
                  mDate = DateDataFormat
                      .getCalendarFromFormattedLong(d);
                           
                     date = DateDisplayFormat.getFormattedDate(mDate);
              }             
       }
       if (date != null){
              mDateText.setText(date);
       }
    }

Code Download

To review a complete, example implementation of this approach in an Android project, please download and import the following Android Eclipse project:

DateData_CodeProject.zip

You must have Eclipse and the Android SDK including platform version 1.5 (3) installed to compile and run this project.

Roads not Taken

Here are some things that were not considered in this approach, but might be relevant to your application:

  • Time Zones – All the date and times I was recording for my application were in relation to one user in a single time zone, so they did not figure into my implementation. Folks working with dates and times that need to be coordinated across time zones would need to enhance my approach using an implementation of Coordinated Universal Time (UTC).
  • High-Precision Times – All the dates and times for my application would differ by at least a second or more, so there was no reason for me to include millisecond precision.

Conclusion

This example implementation is not meant to be the ultimate solution for saving and retrieving date and time data in Android. As noted above, your application may have different needs which are not considered by this methodology, which you should seriously consider before adopting this approach.

I hope this article helps you more easily navigate the decisions of date and time storage on the Android platform and gets you another step further in completing your app. Happy programming!