Monday 7 February 2011

Real estate application for Android(RSS) part 1

Android is an open-source operating system for smartphones. Android was the name of the company that began the challenge of making this operating system for smartphones and Google bought the company to develop the project on their own. The first Android come to light only a few month after the IPhone, back in 2007. By those days the event was announced that way: "Google Android, a new software platform designed to provide open access to mobile phones for application developers." Google's idea was always the same, bring people closer to content on the Web in a easy and organised way. Anyway, this is just history.

I will try to do a little piece of work that you can find useful to learn and enough challenging to keep reading.

I will assume that you have a working environment base on eclipse. If not, you can go here and follow the recipe.

To create an Android application, you will need to create a corresponding Android
project. The project will hold all of your source code, resources, third-party JARs, and related materials.

The application we will develop is very simple. As I plan to go to Australia on holiday, I thought it will be great to use this material to make a useful application.

I've been using suburbview as part of my investigation and they have a great service of RSS that we can use as an excuse to make some piece of work.

The RSS publishes the properties in rent or for sale. We are going to use this to pull properties and make a lazy list showing them. The idea is to use Layouts, Widgets, and Internet access to make a useful application that allows us to look rentals.

I will suppose that you have a clean project with an Activity named MainActivity, and a layout named main.xml. Here is the first problem for newbies. There is no a good tool to make layouts and you probably going to deal with xmls.



 


This is the main layout and basically holds the application on a LinearLayout. There inside, its adds a ListView in which the properties will be shown.

Each item of the ListView needs its own Layout that we named item.xml:



  
  


The next stage to define is the click event on the ListView. An Activity named ImageFromUrl will be responsible. Let's see the layout:



 
  
 
 
  
 


This layout is base on a TableLayout. It's pretty similar the the HTML tag so there is not much to tell. The idea is display the details of the property in a more extended way.

Now that we have the GUI, we have to start programming. An Android program starts by executing on an Activity. The start-up Activity needs to be specifically set in the AndroidManifest.xml along with other things that we won't explain here. Let's see the code:



 
  
   
    
    
   
  
 
 
 
  
 


As you can see above, there are two activities inside the application tag. The one that has the intent-filter inside is that main activity. We are telling this to Android by adding the action and category tags.


    
    
   

I'm not going to give a detailed explanation, but you can ask for answers.

Let's move forward and see some java code. We are going to start by coding the main activity. All custom activities has to extend from Activity android class. Then you have to override onCreate method and there you have to do your staff. That means that you have to create your application(mainly graphically) because Android will invoke this method:

@Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  
  list = (ListView) findViewById(R.id.list);
  adapter = new LazyAdapter(this, getString(R.string.rental_feed), properties);
  
  list.setOnItemClickListener(new OnItemClickListener() {
   public void onItemClick(AdapterView _av, View _v, int _index, long arg3) {
    selectedProperty = properties.get(_index);
    showActivity();
   }
  });
  list.setOnScrollListener(adapter);
  list.setAdapter(adapter);
 }

First two lines are almost default. We are going to focus on the rest of the method. The third line gets the list in which the process will add the properties. As you may see, there's a static class that Android auto-generate and has all the resources constants. In this case we use the one that represents the list we define in the layout "R.id.list".

Then we create a class LazyAdapter. This class has the behavioural responsibility of the list.

Here is where the first challenge appear. The main goal of the application is to get properties from a RSS. We don't know how much properties this file is going to have. If the file is really big we should spend a lot of resources and time trying to read it entirely before showing the user some information. That is why we have to get a more efficient approach to give the user a better experience. There's a way of doing this and consist in reading the file as necessary.

To do this we need the logic that allows us to parse the file as needed and we can do this, implementing OnScrollListener interface. That will force us to implement two methods, onScroll and onScrollStateChanged. In this case, I made both things in the same class, meaning extends BaseAdapter and implement OnScrollListener.

Because we are extending from BaseAdapter we need to implement some more methods: getCount(), getItem(), getItemId(), getView().

Let's see the class.

public class LazyAdapter extends BaseAdapter implements OnScrollListener {
 
 private int lastViewedPosition = 0;
 
 private XmlPullParserFactory factory = null;
 private XmlPullParser xpp = null;
 
 private Activity activity;
 private ArrayList data;
 
 private static LayoutInflater inflater = null;
 public ImageLoader imageLoader;
 private String feedString;
 
 public LazyAdapter(Activity a, String feedString, ArrayList d) {
  this.feedString = feedString;
  activity = a;
  data = d;
  inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  imageLoader = new ImageLoader(activity.getApplicationContext());
 }

 public int getCount() {
  return data.size();
 }

 public Object getItem(int position) {
  return position;
 }

 public long getItemId(int position) {
  return position;
 }
 
 public static class ViewHolder{
  public TextView text;
  public ImageView image;
 }

 public View getView(int position, View convertView, ViewGroup parent) {
  View vi = convertView;
  ViewHolder holder;
  
  if (convertView == null) {
   vi = inflater.inflate(R.layout.item, null);
   holder = new ViewHolder();
   holder.text = (TextView) vi.findViewById(R.id.text);;
   holder.image = (ImageView) vi.findViewById(R.id.image);
   vi.setTag(holder);
  } else {
   holder = (ViewHolder) vi.getTag();
  }
  
  if (data.size() <= position) {
   readRss(10);
  }
  
  lastViewedPosition = position;
  
  holder.text.setText(data.get(position).getTitle());
  holder.image.setTag(data.get(position).getImage());
  imageLoader.DisplayImage(data.get(position).getImage(), activity, holder.image);
  
  return vi;
 }
 
 private boolean connect() {
  try {
   if (xpp == null) {
    URL url = new URL(feedString);
    URLConnection connection = url.openConnection();
    
    HttpURLConnection httpConnection = (HttpURLConnection)connection; 
    int responseCode = httpConnection.getResponseCode(); 
    
    if (responseCode == HttpURLConnection.HTTP_OK) {
     InputStream in = httpConnection.getInputStream();
     
     factory = XmlPullParserFactory.newInstance();
     factory.setNamespaceAware(true);
     xpp = factory.newPullParser();
     
     xpp.setInput(in, "ISO-8859-1");
    }
   }
   
   return true;
   
  } catch (XmlPullParserException xe) {
   Log.e("RssReader", "Connection", xe);
   return false;
   
  } catch (IOException eio) {
   Log.e("RssReader", "Connection", eio);
   return false;
   
  }
 }
 
 private void readRss(int itemsToRead) {
  boolean insideItem = false;
  boolean insideTitle = false;
  boolean insidePubDate = false;
  boolean insideDescription = false;
  Property property = null;
  
  try {
   if (connect()) {
    int eventType = xpp.getEventType();
    
    while (eventType != XmlPullParser.END_DOCUMENT && itemsToRead >= 0) {
     if(eventType == XmlPullParser.START_TAG) {
      if (xpp.getName().equalsIgnoreCase("item")) {
       insideItem = true;
       property = new Property();
      } else if (insideItem && xpp.getName().equalsIgnoreCase("title")) {
       insideTitle = true;
      } else if (insideItem && xpp.getName().equalsIgnoreCase("pubDate")) {
       insidePubDate = true;
      } else if (insideItem && xpp.getName().equalsIgnoreCase("description")) {
       insideDescription = true;
      }
      
     } else if(eventType == XmlPullParser.END_TAG) {
      if (xpp.getName().equalsIgnoreCase("item")) {
       insideItem = false;
       data.add(property);
       --itemsToRead;
      }
      
     } else if(eventType == XmlPullParser.TEXT && insideItem) {
      if (xpp.getText() != null) {
       if (insideTitle) {
        property.setTitle(xpp.getText());
        insideTitle = false;
       } else if (insidePubDate) {
        property.setDate(xpp.getText());
        insidePubDate = false;
       } else if (insideDescription) {
        getDetails(property, xpp.getText());
        insideDescription = false;
       }
      }
      
     }
     
     eventType = xpp.next();
    }
   }
   
  } catch (XmlPullParserException e) {
   Log.e("RssReader", "Connection", e);
  } catch (IOException e) {
   Log.e("RssReader", "Connection", e);
  }
 }
 
 private void getDetails(Property property, String description) {
  int start = description.indexOf("");
  String imageURL = description.substring(start, end);
  property.setImage(imageURL);
  
  String desc = description.replaceAll("\\<.*?\\>", "");
  String[] parts = desc.split("\n");
  
  for (String line : parts) {
   for (Filter filter : Filter.values()) {
    if ((line.toUpperCase()).startsWith(filter.name())) {
     switch (filter) {
      case CATEGORY:
       property.setCategory((line.substring(filter.name().length() + 1)).trim());
       break;
       
      case BEDROOMS:
       property.setBedrooms(Integer.valueOf((line.substring(filter.name().length() + 1)).trim()));
       break;
       
      case BATHROOMS:
       property.setBathrooms(Integer.valueOf((line.substring(filter.name().length() + 1)).trim()));
       break;
     }
    }
   }
  }
 }

 @Override
 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
  if (totalItemCount - lastViewedPosition <= 3) {
   readRss(10);
  }
 }

 @Override
 public void onScrollStateChanged(AbsListView view, int scrollState) {
 }

}
To hold the property we need to create a class. We called it Property(yes, terrible creative) and here we are going to save the information parsed from the rss.
public class Property {
 
 private String title;
 private String date;
 private String category = null;
 private int bedrooms = 0;
 private int bathrooms = 0;
 private String image = null;
 
 public String getTitle() {
  return title;
 }
 
 public void setTitle(String title) {
  this.title = title;
 }
 
 public String getDate() {
  return date;
 }
 
 public void setDate(String date) {
  this.date = date;
 }
 
 public String getCategory() {
  return category;
 }
 
 public void setCategory(String category) {
  this.category = category;
 }
 
 public int getBedrooms() {
  return bedrooms;
 }
 
 public void setBedrooms(int bedrooms) {
  this.bedrooms = bedrooms;
 }
 
 public int getBathrooms() {
  return bathrooms;
 }
 
 public void setBathrooms(int bathrooms) {
  this.bathrooms = bathrooms;
 }
 
 public String getImage() {
  return image;
 }
 
 public void setImage(String image) {
  this.image = image;
 }
 
 @Override
 public String toString() {
  return title;
 }

}

I will give you this to start thinking on and I will be back with the rest as soon as a can!!

Continue...

No comments:

Post a Comment