GenericListCellRenderer – Revisiting the Twitter “Infinite List” demo

It appears the previous posting was pretty popular, so I thought I’d continue on the subject a little further.  For this posting, I’ll revisit the Twitter client introduced in that post, and make it just a little prettier. I’ll be demonstrating how to utilize the GenericListCellRenderer to display the tweets, and how to display the Twitter avatars on our infinite list using Codename One’s ImageDownloadService.

Updating the Infinite List Demo

It might look like a lot of work involved, but with CodenameOne, this stuff really is a piece of cake.  Assuming you followed the instructions in the previous post to create the “infinite list” project, continue on with the following instructions to get the results you see in the above video.

Modifications to the GUI resource file

  1.  First, double click on your resource file to open up the GUI builder.
  2. Click the ‘Add A New GUI Component’ button, set the name to ‘TweetRenderer’ and set the template to ‘BlankContainer’
  3. Set your containers layout to BorderLayout.
  4. From the palette, add a Label, and name it ‘avatar’, set the text to ‘Avatar’, and the TextPosition to Bottom.  Set the LayoutConstraint to West.
  5. Now add a Container, and set it’s layout to BoxLayoutY, and set it’s LayoutConstraint to Center.
  6. Add another Container inside the one from step 5, leave it’s layout as FlowLayout.
  7. Add 3 labels to the container from step 6.  Name the first one ‘from_user’, and set the text to  ‘Tweeter’.  Name the second one ‘fixed’ and set the text to ‘@’.  Name the third one ‘created_at’ and set the text to ‘Date/Time’.
  8. Finally, add a TextArea component to the container created in step 5.  Name it ‘text’, set the text value to ‘Tweet’, make sure that ‘GrowByContent’ is selected, and ‘Editable’ is unselected.  Set the maxsize to 140.
  9. Save your resource file.

At this point, your TweetRenderer component should look something like this:

Renderer for Infinite List items

That’s all for the resource file, now to update the Java code.

Modifications to the java code

  1. First, we want to install our new component based list renderer that we just created.  To do this, replace your beforeMainForm() method with the following:
    protected void beforeMainForm(Form f) {
       super.beforeMainForm(f);
       final List list = findList(f);
       Component selected = createContainer(fetchResourceFile(), "TweetRenderer");
       Component unselected = createContainer(fetchResourceFile(), "TweetRenderer");
       list.setRenderer(new GenericListCellRenderer(selected, unselected) {
          public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected) {
             if ((index + 1) >= list.size()) {
                fetchMore(list);
             }
             return super.getListCellRendererComponent(list, value, index, isSelected);
          }
       });
    }

    NOTE: See the API docs to see why we are instantiating the renderer component twice.

  2. Now, we want to convert the twitter profile URLs to images and pass them to the renderer under our own key of ‘avatar’.  CodenameOne’s ImageDownloadService makes this super easy.  Replace your fetchMore() method with the following:
    private void fetchMore(final List list) {
       System.out.println("Fetching more...");
       TwitterSearchService twitter = new TwitterSearchService("android") {
          protected void readResponse(InputStream input) throws IOException {
             JSONParser json = new JSONParser();
             Vector tweets = (Vector)json.parse(new InputStreamReader(input)).get("results");
             int count = list.size();
             for (int i = 0; i < tweets.size(); i++) {
                Hashtable tweet = (Hashtable)tweets.elementAt(i);
                list.addItem(tweet);
                addAvatar(list, tweet, count + i);
             }
          }
          private void addAvatar(List list, Hashtable tweet, int i) {
             String url = (String)tweet.get("profile_image_url");
             String user = (String)tweet.get("from_user");
             if (url == null || url.startsWith("http:") == false) {
                // ImageDownloadService doesn't support HTTPS at moment
                return;
             }
             ImageDownloadService ids = new ImageDownloadService(url, list, i, "avatar");
             ids.setDuplicateSupported(true);
             NetworkManager.getInstance().addToQueue(ids);
             ImageDownloadService.createImageToStorage(url, list, i, "avatar", user + "-avatar",
                  new Dimension(48, 48));
          }
       };
       twitter.setResultsPerPage(pageSize);
       twitter.setPage((list.size() / (pageSize + 1)) + 1);
       NetworkManager.getInstance().addToQueue(twitter);
    }

And that’s all there is to it!  Fire up the simulator from your IDE, and you should see the same results as in the video above.

UPDATE: Shai made a wise suggestion in that we could use ImageDownloadService.createImageToStorage() to cache the avatar images, reducing bandwidth wasted downloading them multiple times.  The code with the strikethrough above is the original code.

Want more?

In your renderer container that you created in the GUI Builder, you can add components to display any of the information Twitter provides us in the tweet object.  The trick is, you set the name of the components on your renderer to the key you want to select from the tweet.  The GenericListCellRender has the smarts to assign that value to most any component you choose from the palette. If you want to see what key/values you are receiving from Twitter, please see this previous posting for monitoring traffic to and from the webservice.

If you like this post, be sure to click the Like button, and feel free to leave a comment here or on the CodenameOne discussion forum.

ImageDownloadService.createImageToStorage(url, list, i, “avatar”, user + “-avatar”, new Dimension(48, 48));