Everything you need to know about Android ListViews and CheckBoxes!

10:46 AM , , 2 Comments

I have recently been developing a simple Shopping list application in Android and for the list itself, I wanted to have a list of all the items i wanted to buy with a checkbox on the far right so that I could mark any item as complete - on ticking the checkbox I wanted two things to happed:

1. The item displayed becomes struck through
2. The tick is persisted (in case I navigate anway from the app and want to come back later, i dont want to re-tick things)


I searched everywhere to find a solution, and posted on StackOverflow but managed to find no solution as to how I could implement a Checkbox listener that could work out which row in the list had been ticked. Fortunately, with some help from here I have managed to solve the problem - So here is a detailed walkthrough how to do it! This covers how to do it using a custom Adapter, in this case I extended the ArrayAdapter as I like to work with JSON - but the approach should be valid for most Adapter implementations.


The first problem people encounter, that is already pretty well documented on the web is that if you put a checkbox on a listview row, then you can no longer click on the row (for the context menu for example) as all focus is on the check box - this is easily fixed! in the getView() method, simply initialise the CheckBox and call checkBox.setFocusable(false);



Now to the problem of discovering the row that has been selected:

1. First, in the getView() method whilst we are populating the row data (for example here we set the text in the textview) we will add a "Tag" to our checkbox - this tag will indicate the row position that the CheckBox belongs to:

 checkBox.setTag(new Integer(position)); //we tag every checkbox we create with the row number for later


2. Next we set the OnCheckChangedListener - this could be a seperate class, but for ease of this overview I just changed my Adapter to implement the listener and added the required onCheckChange method

 checkBox.setOnCheckedChangeListener(this); //define the listener (we will use this class but could be another)


3. Now we need to implement the listener method to use this information:

  public void onCheckedChanged(CompoundButton cb, boolean isChecked) {
  //first we need to work out the row selected - we can do this as we have already tagged the CheckBox
  Integer posSelected = (Integer)cb.getTag();
  
  //We can now get the associated JSONObject (we might want to persist some data here..)
  JSONObject checked = getItem(posSelected);
  //persistChangeToDb(checked);
  
  //now we will retrieve the current row selected so we can change the appearance
  View row = (View) cb.getParent();
  
  //once we have the row, we are going to change the TextView field as strikethrough (or not) if it is checked
  TextView descrip = (TextView)row.findViewById(R.id.item_description);
  if (isChecked){
   descrip.setPaintFlags(descrip.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
  }
  else{
   descrip.setPaintFlags(Constants.CLEAR_FORMATTING_TEXTVIEW); 
  }
 }

As you can see, as we have the CheckBox in this listener, we can get its tag to discover the row position clicked - this allows us to access the JSONObject associated with the row and use that data as we please (here I persist the change to the checkbox so it is remembered).

We can also get the entire row, this allows us to change the text/formatting/background etc - you will see that I get the TextView (this is the textView that has my item description) and then strike it through (or remove formatting) if it is checked.




Below is the entire adapter class:

public class ShoppingListItemsAdapter extends ArrayAdapter implements OnCheckedChangeListener {
 
 public ShoppingListItemsAdapter(Activity activity, List shoppingLists) {
  super(activity, 0, shoppingLists);
 }
 
 
 @Override
 public View getView(int position, View convertView, ViewGroup parent) {

  Activity activity = (Activity) getContext();
  LayoutInflater inflater = activity.getLayoutInflater();

  // Inflate the views from XML
  View rowView = inflater.inflate(R.layout.shopping_list_item_view, null);
  JSONObject jsonCurrentRow = getItem(position);


  //////////////////////////////////////////////////////////////////////////////////////////////////////
  //The next section we update at runtime the text - as provided by the JSON being passed in
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  
  // Set the text on the TextView
  TextView textView = (TextView) rowView.findViewById(R.id.item_description);
  
  //initialise the checkbox
  CheckBox checkBox = (CheckBox)rowView.findViewById(R.id.boughtCheck);
  checkBox.setFocusable(false);  //this is so the onLongClick on the row still works!
  checkBox.setTag(new Integer(position)); //we tag every checkbox we create with the row number for later
  checkBox.setOnCheckedChangeListener(this); //define the listener (we will use this class but could be another)
  
  
  try {
   //populating the row with data from the JSONObject
   String title = (String)jsonCurrentRow.get("title");
   textView.setText(title);
  } catch (JSONException e) {
   textView.setText("JSON Exception");
  }
  return rowView;
 }


 
 /**
  * This method is our checkbox change listener - this is where
  * we handle the changes in the checkbox state
  */
 @Override
 public void onCheckedChanged(CompoundButton cb, boolean isChecked) {
  //first we need to work out the row selected - we can do this as we have already tagged the CheckBox
  Integer posSelected = (Integer)cb.getTag();
  
  //We can now get the associated JSONObject (we might want to persist some data here..)
  JSONObject checked = getItem(posSelected);
  //persistChangeToDb(checked);
  
  //now we will retrieve the current row selected so we can change the appearance
  View row = (View) cb.getParent();
  
  //once we have the row, we are going to change the TextView field as strikethrough (or not) if it is checked
  TextView descrip = (TextView)row.findViewById(R.id.item_description);
  if (isChecked){
   descrip.setPaintFlags(descrip.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
  }
  else{
   descrip.setPaintFlags(Constants.CLEAR_FORMATTING_TEXTVIEW); //this is a bit of a hack! to remove strike through the normal setting is 257 so I created a constant for this, you could just pass the int literal 257 in here to clear formatting
  }
 }
}

2 comments:

Two new Android Apps..

7:29 PM , , 0 Comments



I have finally published my first two mobile apps to the Android market.


You cannot actually search the market officialy online, as Google only permit it via an Android mobile device, but you can find them here


Its been a long time coming, but there is another one on its way shortly.

0 comments:

Android RSS Reader Tutorial

1:33 PM , , 37 Comments

Well, its now 2013, and I have updated a version of this post - a long with a complete working application on GitHub - you can find that here it covers Android 3.0+ so includes fragments and parsing the RSS in an AsyncTask. Of course, you can still read this post and access the code for the pre-3.0 version!

There are two core classes that I used in my RSS parsing project - The RssHandler (extendes the SAX DefaultHandler) and the Article object (I use this to store all the information about an article/item in an RSS stream.


Here I will go through the SAX Handler implementation.


First we declare global variables we will use, most of these are self-explanatory: currentArticle stores all the information about the current RSS item being processed; articleList stores a list of all items processed so far; the two counters then count the number of RSS items processed and the limit (you will want to set this to the number of articles you want to fetch, as the stream could be very big); the characters StringBuffer, we use this to accumulate the text in each simple element:

// Feed and Article objects to use for temporary storage
 private Article currentArticle = new Article();
 private List
articleList = new ArrayList
(); // Number of articles added so far private int articlesAdded = 0; // Number of articles to download private static final int ARTICLES_LIMIT = 15; //Current characters being accumulated StringBuffer chars = new StringBuffer();



When implementing the SAX DefaultHandler you need three core methods: startElement(), endElement(), characters().

This is the startElement() method, this is called on every opening XML node (such as <item>). In our case all we want to do is reset our chars StringBuffer to be sure that the text we retrieve is always only from our current simple element
public void startElement(String uri, String localName, String qName, Attributes atts) {
  chars = new StringBuffer();
 }



Next we have characters() method - this is called whilst reading the text stored in a simple element - however, this is not just called once at the end of the element, but can be called several times, so we must be careful to be sure we dont process the text here as it maybe incomplete - so for now we just accumulate the text in our String Buffer, and we will process it later, when we ar sure we have all the text:
public void characters(char ch[], int start, int length) {
  chars.append(new String(ch, start, length));
 }


Finally we have the endElement() method - this is called when any closing XML marker is found (for example, </item>). At this point we check which element we are in and decide if we should process the contents. For example, if we have found </title> then we know we are closing the <title> simple element - we know our string buffer was reset in the startElement for <title>, and we know our characters() method has been called and collected all the text insde this element, so we can now safely use this information to set the title on our currentArticle object:
public void endElement(String uri, String localName, String qName) throws SAXException {

  if (localName.equalsIgnoreCase("title"))
  {
   Log.d("LOGGING RSS XML", "Setting article title: " + chars.toString());
   currentArticle.setTitle(chars.toString());

  }
  else if (localName.equalsIgnoreCase("description"))
  {
   Log.d("LOGGING RSS XML", "Setting article description: " + chars.toString());
   currentArticle.setDescription(chars.toString());
  }
  else if (localName.equalsIgnoreCase("pubDate"))
  {
   Log.d("LOGGING RSS XML", "Setting article published date: " + chars.toString());
   currentArticle.setPubDate(chars.toString());
  }
  else if (localName.equalsIgnoreCase("encoded"))
  {
   Log.d("LOGGING RSS XML", "Setting article content: " + chars.toString());
   currentArticle.setEncodedContent(chars.toString());
  }
  else if (localName.equalsIgnoreCase("item"))
  {

  }
  else if (localName.equalsIgnoreCase("link"))
  {
   try {
    Log.d("LOGGING RSS XML", "Setting article link url: " + chars.toString());
    currentArticle.setUrl(new URL(chars.toString()));
   } catch (MalformedURLException e) {
    Log.e("RSA Error", e.getMessage());
   }

  }




  // Check if looking for article, and if article is complete
  if (localName.equalsIgnoreCase("item")) {

   articleList.add(currentArticle);
   
   currentArticle = new Article();

   // Lets check if we've hit our limit on number of articles
   articlesAdded++;
   if (articlesAdded >= ARTICLES_LIMIT)
   {
    throw new SAXException();
   }
  }


This Handler will allow us to parse an RSS stream and create a list of Article objects for later processing. As mentioned earlier, the entire Android project can be downloaded here (its an eclipse project) where you can see the entire of this class and the rest of the code plugged together as a very simple RSS reader

37 comments:

Android and RSS

12:58 PM , , 5 Comments

Recently, I was working on a simple Android application for an upcoming independent singer - the concept was simple, it was going to be an app with three different pages:

  1. Latest News - this was going to just be an RSS feed of the artists site to get all the latest info
  2. Watch Videos - this was going to be the artists youtube channel embeded
  3. Latest Tweets - this was going to be the artist's tweets (like any good upcoming artist they were making full use of social media channels)

Embedding youtube into android was going to be straight forward (both Google), and from my experience creating Zippy, my Twitter application, the tweets list was going to be easy, so the only work really was to create the RSS feed. Easy I thought..


A quick Google for Java RSS libraries threw up ROME - so I went about constructing all the necessary parts so ROME could plugin and feed me the info i needed to create a JSON list for my list view. However, on plugging it all together and trying to get ROME to work on my Android emulator it just wasn't playing nicely, so again back on Google and I found this discussion on StackOverflow - it turns out that ROME along with some other RSS parsers don't work on Android on account of incompatible packages on the Dalik JVM.


Being fairly frustrated at this point I decided to go back to basics and just implement my own RSS parser based on SAX - I found a tutorial here on a complete RSS parser Android application, so I was able to use some of that for the boiler plate stuff, but there were some issues with the parser not working correctly (not correctly handling the "characters" values in endElement()) but I have managed to get it working and creating a simple Android RSS reader - you can get the complete code here: (also on my source code example list to the left)

The code is an Eclipse project, you can just import this into your workspace and (as long as Android is correctly set up) run the application and you will see a simple RSS stream from my blog!

5 comments:

Welcome!

11:41 AM 0 Comments

I decided my usual blog, Geek-Chic, was getting a little overcrowded with random musings and posting about gadgets, music, and well i suppose style (i use the term very loosely!) to continue posting my deeper, darker tech rants there - Im sure people viewing the blog and reading about the new Cee-Lo video, or hip hop samples didn't neccessarily want to be updated with a random Android tutorial, and vice versa, so here we are, at a new home.


I would have liked nicer seperation of categories, or pages in Blogger to allow a single site with different tab views of the different content, but it seems pages on blogger are only for static text, so i have created a new blog and cunningly put a link on the top navigation bar, so you can (almost) seamlessly navigate between the two different blogs as though they were linked.

Anyway, thats enough of a welcome, i hope this blog becomes useful!

0 comments: