Wednesday 16 February 2011

Real estate application for Android(RSS) part 2

Continuing with the RSS android application, we are going to take a closer look at LazyAdapter class which has the most relevant code of the application.
To begin with, notice that the class implements OnScrollListener. The reason why is that we need some scrolling behaviour on the list view and the most reasonable way to do that is making this class an implementation of that interface. That is why this class acts as an adaper and as a scroll listener to the list view.
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;
    }
More in particular the method getView(mandatory because it is extending android.widget.Adapter) is called every time a ListView's item will be shown. Here is where you must return a view of the element. On our case, a thumbnail and a brief description of the property.
@Override
 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
  if (totalItemCount - lastViewedPosition <= 3) {
   readRss(10);
  }
 }
On the other hand, we have onScroll method which is mandatory since we implemented OnScrollListener interface. This method will be called every time the user tries to view an element that is not on the screen at that moment in particular. This is the appropriate moment to check if we have enough elements to continue scrolling. That way, we can manage list view scrolling on the fly.
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);
  }
    }
Nevertheless, the issue now is how can we read a resource from Internet on demand. There is a solution to this too. We can read the xml by creating an instance of XmlPullParserFactory. Through this parser you can get elements from the xml, one by one, to show as many elements as you need. Moreover, you can get the instance of the parser and read a set of elements again to get into the view. The parser's instance will preserve pointing where you are, so you will always can get it and read another set of elements til the end of the document. Then we have ImageLoader class that is just an in-memory cache implementation. The idea of this is show a default image while the final one is bring from the internet. This allows us to continue adding elements to the list view independently of the images completition. Lets go back to the MainActivity to see what happens when we click on an element. As we saw in the last post, a click listener was created in order to attend this event.
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);
The selected property is set and the activity that show it is created. Lets show the code for the method showActivity.
private void showActivity() {
     Intent imageView = new Intent(this, ImageFromUrl.class);
     
 String propertyDescription = selectedProperty.getTitle() + "\n" +
              "Bedrooms " + selectedProperty.getBedrooms()  + "\n" +  
               "Bathrooms " + selectedProperty.getBathrooms();
     
     Bundle bundle = new Bundle();
     bundle.putString("imageUrl", selectedProperty.getImage());
     bundle.putString("propertyDescription", propertyDescription);
     imageView.putExtras(bundle);
     
     startActivity(imageView);
    }
As you can see, an Intent is created based on the ImageFromUrl class. This class will take care of how a property should be shown. The idea is that the Activity can be shown even if the image haven't been loaded yet.
public class ImageFromUrl extends Activity {
    
    private String url = null;
    private String description = null;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.image_viewer);
        
        Bundle extras = getIntent().getExtras();
        if (extras != null) {
         url = extras.getString("imageUrl");
         description = extras.getString("propertyDescription");
        }

        ImageView imgView = (ImageView) findViewById(R.id.ImageView);
        TextView txtView = (TextView) findViewById(R.id.DescriptionView);
        
        Drawable drawable = LoadImageFromWebOperations(url);
        imgView.setImageDrawable(drawable);
        txtView.setText(description);
    }

   private Drawable LoadImageFromWebOperations(String url) {
 try {
     InputStream is = (InputStream) new URL(url).getContent();
     Drawable d = Drawable.createFromStream(is, "src name");
     return d;
  
 }catch (Exception e) {
     return null;
 }
   }
}
Lastly I want to add something I forgot to mention. The application was based on the real estate site http://www.suburbview.com and I use in particular I used the housing rental from Melbourne RSS. This information was written down into a resource file named strings.xml.


    House Rental RSS
 http://www.suburbview.com/files/vic/3000/rent/melbourne.rss

Because we are not going to see the entire application here, I will give you this link for you to download the eclipse project so you can play with it as much as you want.

HouseRentalRSS.zip

And if you want to try it here you have the application.
I may forget some other things so please, don't hesitate asking questions.

Have fun!!

3 comments:

  1. i need help with your code can you plz reply :)

    ReplyDelete
    Replies
    1. Sorry for the delay.
      How can I help you singh? It's been a long time since I posted this test project, but I will help you as far as I can.

      Delete
  2. There is a problem with it... when scrolling it loads data ok. but after a few scrolls it just crashes...

    can you help me fix it.

    ReplyDelete