[TUT] Show YouTube Feed for a Users Videos in a ListView ( XML )

Hi guys, this is a Tutorial Request to show a list of videos that a user has uploaded to their youtube channel using atom XML.

YouTube has some great API‘s for using the data from their website and you can retrieve this data in a few formats including XML and JSON. This tutorial will retrieve the data using XML. This gives you the information in as an atom feed in an XML format, this is a style of RSS feed looking like this: example.

However this XML feed is NOT omptimized for mobile, we get a load of data we don’t need. That’s users downloading extra data on their bill. Also it means a lot of parsing for us giving extra overhead and longer parse times. If I ever had to do this in production I would always always use the JSON-C YouTube feed: https://blog.blundellapps.co.uk/show-youtube-user-videos-in-a-listview/

So the plan is, talk to YouTube ask them for one users uploaded videos. YouTube will then send back an atom (XML) object that we will retrieve the videos from and shove them into a list, sound easy huh.

What we’re going to do:

  • Create an Activity that has a button to initiate the search and a listview to display the results
  • Create a new thread that will talk to youtube and retrieve the XML
  • Parse the XML into nice java objects
  • Pass these objects to our ListView and display on the screen

The below paragraph does NOT apply. I cannot see a thumbnail url in the XML that YouTube are sending, therefore this is not shown in the ListView see the JSON tutorial for this.
One thing to note is, within our ListView we will be showing a thumbnail of the youtube video and the title, the atom XML that YouTube sends back only contains a URL to the thumbnail and not the image itself. Therefore I am going to use code from one of my previous tutorials (UrlImageView) to load the image in an asynchronous manner, this means I’ll just pass the url to the imageview and it’ll go start it’s own thread to load it from the internet and display it when it has, yay.

Ok Here … we .. go.

Lets mix it up a bit and start off with the Manifest, you’ll need to add the Internet permission yes!

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.blundell.tut"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".ui.phone.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Lets go ahead and create the MainActivity also, the layout is simple, it contains our Button to go search YouTube and the ListView to display the user uploaded videos when they are returned to us. The ListView is a custom ListView, this makes sense so we can customise it more without the Activity having to care what is going on. Now instead of having to worry about the adapter for the list within your activity we can do it all in our own ListView and just have the Activity call one method to initialise it. It’ll also help later on if you want to save some state when you change orientation.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <Button
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="getUserYouTubeFeed"
        android:text="Get YouTube Feed for &apos;blundellp&apos;" />

    <com.blundell.tut.ui.widget.VideosListView
        android:id="@+id/videosListView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

</LinearLayout>

Next comes the MainActivity class file. Here you want to get a reference to your ListView so you can populate it, you also react to your button click here. When the button is clicked we create a new task and start it running. This task runs on another thread and not the main one. You do not want to do blocking operations on the main thread i.e. calls to internet services. Doing heavy lifting on the main thread is the number one reason for those ‘Activity Not Responding’ errors, bad. We also create a handler here and this is used to receive our data once the task has finished.

MainActivity.java

package com.blundell.tut.ui.phone;

import com.blundell.tut.R;
import com.blundell.tut.domain.Library;
import com.blundell.tut.service.task.GetYouTubeUserVideosTask;
import com.blundell.tut.ui.widget.VideosListView;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;

/**
 * The Activity can retrieve Videos for a specific username from YouTube</br>
 * It then displays them into a list including the Thumbnail preview and the title</br>
 * There is a reference to each video on YouTube as well but this isn't used in this tutorial</br>
 * </br>
 * <b>Note<b/> orientation change isn't covered in this tutorial, you will want to override
 * onSaveInstanceState() and onRestoreInstanceState() when you come to this
 * </br>
 * @author paul.blundell
 */
public class MainActivity extends Activity {
    // A reference to our list that will hold the video details
	private VideosListView listView;

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

        listView = (VideosListView) findViewById(R.id.videosListView);
    }

    // This is the XML onClick listener to retreive a users video feed
    public void getUserYouTubeFeed(View v){
    	// We start a new task that does its work on its own thread
    	// We pass in a handler that will be called when the task has finished
    	// We also pass in the name of the user we are searching YouTube for
    	new Thread(new GetYouTubeUserVideosTask(responseHandler, "blundellp")).start();
    }

    // This is the handler that receives the response when the YouTube task has finished
	Handler responseHandler = new Handler() {
		public void handleMessage(Message msg) {
			populateListWithVideos(msg);
		};
	};

	/**
	 * This method retrieves the Library of videos from the task and passes them to our ListView
	 * @param msg
	 */
	private void populateListWithVideos(Message msg) {
		// Retreive the videos are task found from the data bundle sent back
		Library lib = (Library) msg.getData().get(GetYouTubeUserVideosTask.LIBRARY);
		// Because we have created a custom ListView we don't have to worry about setting the adapter in the activity
		// we can just call our custom method with the list of items we want to display
		listView.setVideos(lib.getVideos());
	}

	@Override
	protected void onStop() {
		// Make sure we null our handler when the activity has stopped
		// because who cares if we get a callback once the activity has stopped? not me!
		responseHandler = null;
		super.onStop();
	}
}

You then want to create your task, the task takes care of talking to YouTube and making sense of what YouTube sends you back i.e. the atom XML response. I’ve commented this heavily as it does some interesting things. The one thing I’d note is I do not do any error checking in this tutorial. Yes it catches the errors so you won’t get a forceclose but if you start this task from your activity then sit there waiting for it to finish and reply, if it gets an error .. it won’t and your activity will need to react to that. Thats just one thing to note if you want to expand on this code.

GetYouTubeUserVideosTask.java

package com.blundell.tut.service.task;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.DefaultHttpClient;

import com.blundell.tut.domain.Library;
import com.blundell.tut.domain.Video;
import com.blundell.tut.parser.AtomParser;
import com.blundell.tut.parser.AtomParser.ParseException;
import com.blundell.tut.parser.AtomPullParser;
import com.blundell.tutorial.util.Log;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;

/**
 * This is the task that will ask YouTube for a list of videos for a specified user</br>
 * This class implements Runnable meaning it will be ran on its own Thread</br>
 * Because it runs on it's own thread we need to pass in an object that is notified when it has finished
 *
 * @author paul.blundell
 */
public class GetYouTubeUserVideosTask implements Runnable {
	// A reference to retrieve the data when this task finishes
	public static final String LIBRARY = "Library";
	// A handler that will be notified when the task is finished
	private final Handler replyTo;
	// The user we are querying on YouTube for videos
	private final String username;

	/**
	 * Don't forget to call run(); to start this task
	 * @param replyTo - the handler you want to receive the response when this task has finished
	 * @param username - the username of who on YouTube you are browsing
	 */
	public GetYouTubeUserVideosTask(Handler replyTo, String username) {
		this.replyTo = replyTo;
		this.username = username;
	}

	@Override
	public void run() {
		try {
			// Get a httpclient to talk to the internet
			HttpClient client = new DefaultHttpClient();
			// Perform a GET request to YouTube for a JSON list of all the videos by a specific user
			HttpUriRequest request = new HttpGet("https://gdata.youtube.com/feeds/api/videos?author="+username+"&v=2&alt=atom");
			// Get the response that YouTube sends back
			HttpResponse response = client.execute(request);
			// Convert this response into an inputstream for the parser to use
			InputStream atomInputStream = response.getEntity().getContent();

			// Load up the parser with the atom xml retrieved from youtube
			AtomParser parser = new AtomPullParser(atomInputStream); // or use AtomSaxParser(atomInputStream);

			// load  the video objects into our list
			List<Video> videos = parser.getVideos();

			// Create a library to hold our videos
			Library lib = new Library(username, videos);
			// Pack the Library into the bundle to send back to the Activity
			Bundle data = new Bundle();
			data.putSerializable(LIBRARY, lib);

			// Send the Bundle of data (our Library) back to the handler (our Activity)
			Message msg = Message.obtain();
			msg.setData(data);
			replyTo.sendMessage(msg);

		// We don't do any error catching, just nothing will happen if this task falls over
		// an idea would be to reply to the handler with a different message so your Activity can act accordingly
		} catch (ClientProtocolException e) {
			Log.e("Feck", e);
		} catch (ParseException e) {
			Log.e("Feck", e);
		} catch (IOException e) {
			Log.e("Feck", e);
		}
	}
}

When the task is complete your handler is notified and the message the handler gets has a bundle attached (just like when you attach extras to an Intent). From this bundle we can retrieve the list of videos that YouTube has told us the user we searched for uploaded, in this case ‘blundellp’ me!
We’re storing each YouTube video in an object of our own called Video, this has a title, url, and thumbnail url. If you notice I like to make my objects immutable, this means you set all the fields on them in the constructor and once that has happened the fields cannot change and you can only retrieve what they are. This helps when your stepping through code and you then know crazy things can’t happen after instantiation. This isn’t always possible in practice, but it is here!

This tutorial uses two parsers. A SAX parser or a XmlPullParser they both SUCK, it is not the parsers fault but the fact that we are using Atom XML, YouTube aren’t aiming Atom XML at the mobile space and therefore the XML we get sent is very very long, so when these parsers are used our ListView takes a longtime to load (it has to read a lot of nodes). Each of our parsers implements a ‘Parser’ interface so that we can swap them in and out just for this tutorial.

AtomParser.java

package com.blundell.tut.parser;

import java.util.List;

import com.blundell.tut.domain.Video;

public interface AtomParser {

	List<Video> getVideos();

	public class ParseException extends Exception {
		public ParseException(Exception origin) {
			super(origin);
		}
	}
}

So for the tutorial you can switch in a SAX parser or an XmlPullParser the choice is up to you, as I’ve said in this scenario I won’t recommend either as you should use the JSON feed which is much faster and lighter for mobile.

Here’s the Pull Parser:

AtomPullParser.java

package com.blundell.tut.parser;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import com.blundell.tut.domain.Video;

/**
 * A pull parser will only read elements that we want, therefore theoretically faster than a sax parser
 * This is still SO SLOW, there is too much XML that we just don't need (but still have to parse)
 * If your talking to YouTube I definitely recommend using JSON-C
 * like in this tutorial: https://blog.blundellapps.co.uk/show-youtube-user-videos-in-a-listview/
 * @author paul.blundell
 */
public class AtomPullParser implements AtomParser {
	// Can't retrieve a thumbnail from the XML feed so using a static url - if you were to just use the same image you
	// should just put that image in your /drawable/ folder and use it from there
	private static final String YOUTUBE_LOGO = "https://www.linenfields.co.uk/catalog/view/theme/default/image/youtube-logo.png";

	// Create a list to store are videos in
	private final List<Video> videos;
	private final XmlPullParser parser;

	private boolean inVideoEntry;
	private boolean inTitle;
	private boolean inVideoUrl;

	private String title;
	private String url;

	public AtomPullParser(InputStream atomInputStream) throws ParseException {
		this.videos = new ArrayList<Video>();
		try {
			this.parser = XmlPullParserFactory.newInstance().newPullParser();
			parser.setInput(atomInputStream, "UTF-8");
			parse();
		} catch (XmlPullParserException e) {
			// We catch the specific parsers exception and throw our own exception so that we can control the errors
			throw new ParseException(e);
		} catch (IOException e) {
			// We catch the specific parsers exception and throw our own exception so that we can control the errors
			throw new ParseException(e);
		}
	}

	private void parse() throws XmlPullParserException, IOException {
		int event = 0;
		do{
			event = parser.next();
			switch(event){
			case XmlPullParser.START_TAG:
				debug("<"+parser.getName()+">", false);

				if("entry".equals(parser.getName())){
					inVideoEntry = true;
				}
				if("media:title".equals(parser.getName())){
					inTitle = true;
				}

				break;
			case XmlPullParser.TEXT:
				debug(parser.getText(), false);

				if(inTitle){
					title = parser.getText();
				} else
				if(inVideoUrl){
					url = parser.getText();
				}

				break;
			case XmlPullParser.END_TAG:
				debug("<"+parser.getName()+"/>", true);

				if(inVideoEntry){
					if("media:title".equals(parser.getName())){
						inTitle = false;
					} else
					if("yt:videoid".equals(parser.getName())){
						inVideoUrl = false;
					} else
					if("entry".equals(parser.getName())){
						inVideoEntry = false;
						// Loop round our JSON list of videos creating Video objects to use within our app
						videos.add(new Video(title, "https://www.youtube.com/watch?v="+url, YOUTUBE_LOGO));
					}
				}
				break;
			default:
				break;
			}


		} while ( event != XmlPullParser.END_DOCUMENT );
	}

	@Override
	public List<Video> getVideos() {
		return videos;
	}

	// A debug method to view the XML - you would remove this after you have completed the code
	// (I've set the constant to false, change to true to see the XML in your LogCat)
	private static final boolean PRINT_XML = true;
	private void debug(String var, boolean newLine){
		if(PRINT_XML){
			if(newLine)
				System.out.println(var);
			else
				System.out.print(var);
		}
	}
}

And here is the SAX parser:

AtomSaxParser.java

package com.blundell.tut.parser;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.blundell.tut.domain.Video;

import android.util.Xml;

/**
 * A SAX parser to read through the XML reading each node.
 * This is SO SLOW, there is too much XML that we just don't need (but still have to parse)
 * If your talking to YouTube I definitely recommend using JSON-C
 * like in this tutorial: https://blog.blundellapps.co.uk/show-youtube-user-videos-in-a-listview/
 * @author paul.blundell
 */
public class AtomSaxParser extends DefaultHandler implements AtomParser {
	// Can't retrieve a thumbnail from the XML feed so using a static url - if you were to just use the same image you
	// should just put that image in your /drawable/ folder and use it from there
	private static final String YOUTUBE_LOGO = "https://www.linenfields.co.uk/catalog/view/theme/default/image/youtube-logo.png";

	// Create a list to store are videos in
	private final List<Video> videos;
	private StringBuilder nodeValue;
	// These flags are used so we know where abouts we are in the XML we are parsing
	private boolean inVideoEntry;
	private boolean inTitle;
	private boolean inDetails;
	private boolean inVideoUrl;
	// These are the fields that we get out of the XML and save to our Video object
	private String title;
	private String url;

	public AtomSaxParser(InputStream atomInputStream) throws ParseException {
		// instantiate a list of videos to send back
		this.videos = new ArrayList<Video>();
		// use a string-builder to store the text from each xml field
		this.nodeValue = new StringBuilder("");
		// Start parsing the XML
		try {
			Xml.parse(atomInputStream, Xml.Encoding.UTF_8, this);
		} catch (SAXException e) {
			// We catch the specific parsers exceptions and throw our own exception so that we can control the errors
			throw new ParseException(e);
		} catch (IOException e) {
			// We catch the specific parsers exceptions and throw our own exception so that we can control the errors
			throw new ParseException(e);
		}
	}

	@Override
	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
		super.startElement(uri, localName, qName, attributes);
		debug("<"+localName+">", false);
		// use flags to keep track of where we are in the XML
		if("entry".equals(localName)){
			inVideoEntry = true;
		} else
		if("group".equals(localName)){
			inDetails = true;
		}

		if(inVideoEntry && inDetails){
			if("title".equals(localName)){
				inTitle = true;
			}
			if("videoId".equals(localName)){
				inVideoUrl = true;
			}
		}
	}

	@Override
	public void characters(char[] chars, int start, int length) throws SAXException {
		super.characters(chars, start, length);
		// only save the text of the XML attributes we want
		if(inTitle || inVideoUrl){
			nodeValue.append(new String(chars, start, length));
		}
	}

	@Override
	public void endElement(String uri, String localName, String qName) throws SAXException {
		super.endElement(uri, localName, qName);
		debug(nodeValue.toString(), false);
		debug("<"+localName+"/>", true);

		// When we get to the end of an XML node we want save the string-builder as a field
		if(inVideoEntry){
			if(inDetails){
				if(inTitle){
					title = nodeValue.toString();
					inTitle = false;
					nodeValue.setLength(0);
				} else
				if(inVideoUrl){
					url = nodeValue.toString();
					inVideoUrl = false;
					nodeValue.setLength(0);
				}
			}
		}

		if("group".equals(localName)){
			inDetails = false;
		} else
		// When we get to the end of the entry tag </entry> we know we have all the fields for our YouTube Video
		if("entry".equals(localName)){
			addVideo(title, "https://www.youtube.com/watch?v="+url, YOUTUBE_LOGO);
			// Reset for the next video
			inVideoEntry = false;
			title = "";
			url = "";
		}
	}

	private void addVideo(String title, String url, String thumbUrl) {
		// Create the videos object and add it to our list
		videos.add(new Video(title, url, thumbUrl));
	}

	public List<Video> getVideos() {
		return videos;
	}

	// A debug method to view the XML - you would remove this after you have completed the code
	// (I've set the constant to false, change to true to see the XML in your LogCat)
	private static final boolean PRINT_XML = false;
	private void debug(String var, boolean newLine){
		if(PRINT_XML){
			if(newLine)
				System.out.println(var);
			else
				System.out.print(var);
		}
	}
}

These parses create a list of Video objects like below:

Video.java

package com.blundell.tut.domain;

import java.io.Serializable;

/**
 * This is a representation of a users video off YouTube
 * @author paul.blundell
 */
public class Video implements Serializable {
	// The title of the video
	private String title;
	// A link to the video on youtube
	private String url;
	// A link to a still image of the youtube video
	private String thumbUrl;
	
	public Video(String title, String url, String thumbUrl) {
		super();
		this.title = title;
		this.url = url;
		this.thumbUrl = thumbUrl;
	}

	/**
	 * @return the title of the video
	 */
	public String getTitle(){
		return title;
	}

	/**
	 * @return the url to this video on youtube
	 */
	public String getUrl() {
		return url;
	}

	/**
	 * @return the thumbUrl of a still image representation of this video
	 */
	public String getThumbUrl() {
		return thumbUrl;
	}
}

The videos are then stored in an ArrayList, which we want to pass back to our activity in the bundle. I know you remembered the List interface doesn’t implement serialisable in Java so we then wrap that list in another object which we are calling a Library, for good measure we’ll store the username we searched for in the Library as well. Again this object is immutable.

Library.java

package com.blundell.tut.domain;

import java.io.Serializable;
import java.util.List;

/**
 * This is the 'library' of all the users videos
 * 
 * @author paul.blundell
 */
public class Library implements Serializable{
	// The username of the owner of the library
	private String user;
	// A list of videos that the user owns
	private List<Video> videos;
	
	public Library(String user, List<Video> videos) {
		this.user = user;
		this.videos = videos;
	}

	/**
	 * @return the user name
	 */
	public String getUser() {
		return user;
	}

	/**
	 * @return the videos
	 */
	public List<Video> getVideos() {
		return videos;
	}
}

Finally in the Activity when this Library is retrieved from the task it is passed to our custom listview that uses a custom adapter to read the list, take the YouTube video title and thumbnail url and populate one list row for each video.

VideoListView.java

package com.blundell.tut.ui.widget;

import java.util.List;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ListAdapter;
import android.widget.ListView;

import com.blundell.tut.domain.Video;
import com.blundell.tut.ui.adapter.VideosAdapter;

/**
 * A custom ListView that takes a list of videos to display</br>
 * As you can see you don't call setAdapter you should call setVideos and the rest is done for you.</br>
 * </br>
 * Although this is a simple custom view it is good practice to always use custom views when you can
 * it allows you to encapsulate your work and keep your activity as a delegate whenever possible</br>
 * This list could be further customised without any hard graft, whereas if you had put this into the activity</br>
 * it would have been a real pain to pull out further down the road.</br>
 * </br>
 * One example is we could switch out the adapter we are using, to something that displays scrolling images or whatever,
 * and our activity never need know!</br>
 * 
 * @author paul.blundell
 */
public class VideosListView extends ListView {

	public VideosListView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	public VideosListView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public VideosListView(Context context) {
		super(context);
	}

	public void setVideos(List<Video> videos){
		VideosAdapter adapter = new VideosAdapter(getContext(), videos);
		setAdapter(adapter);
	}
	
	@Override
	public void setAdapter(ListAdapter adapter) {
		super.setAdapter(adapter);
	}
}

The VideoAdapter uses the UrlImageView to load the thumbnails. Apart from that it is a simple view adapter.

VideoAdaper.java

package com.blundell.tut.ui.adapter;

import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.blundell.tut.R;
import com.blundell.tut.domain.Video;
import com.blundell.tut.ui.widget.UrlImageView;

/**
 * This adapter is used to show our Video objects in a ListView
 * It hasn't got many memory optimisations, if your list is getting bigger or more complex
 * you may want to look at better using your view resources: https://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/List14.html
 * @author paul.blundell
 */
public class VideosAdapter extends BaseAdapter {
	// The list of videos to display
	List<Video> videos;
	// An inflator to use when creating rows
	private LayoutInflater mInflater;
	
	/**
	 * @param context this is the context that the list will be shown in - used to create new list rows
	 * @param videos this is a list of videos to display
	 */
	public VideosAdapter(Context context, List<Video> videos) {
		this.videos = videos;
		this.mInflater = LayoutInflater.from(context);
	}

	@Override
	public int getCount() {
		return videos.size();
	}

	@Override
	public Object getItem(int position) {
		return videos.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		// If convertView wasn't null it means we have already set it to our list_item_user_video so no need to do it again
		if(convertView == null){
			// This is the layout we are using for each row in our list
			// anything you declare in this layout can then be referenced below
			convertView = mInflater.inflate(R.layout.list_item_user_video, null);
		}
		// We are using a custom imageview so that we can load images using urls
		// For further explanation see: https://blog.blundellapps.co.uk/imageview-with-loading-spinner/
		UrlImageView thumb = (UrlImageView) convertView.findViewById(R.id.userVideoThumbImageView);
		
		TextView title = (TextView) convertView.findViewById(R.id.userVideoTitleTextView); 
		// Get a single video from our list
		Video video = videos.get(position);
		// Set the image for the list item
		thumb.setImageDrawable(video.getThumbUrl());
		// Set the title for the list item
		title.setText(video.getTitle());
		
		return convertView;
	}
}

That’s it! The last two files here are just for completeness, I have a custom Log class so I can control the log message that I output. Also a utility class to convert the InputStream that is retrieved from talking to YouTube into a string (because it’s actually just atom XML).

Log.java

package com.blundell.tutorial.util;

import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.os.Message;

/**
 * A simple Log wrapper so that we have more control</br>
 * Here we can turn off logging for a live build or do other custom logging things
 * @author paul.blundell
 */
public class Log {

	private static final boolean live = false;
	
	private static final String TAG = "UserFeedYouTubeTut";

	public static void d(String msg){
		d(msg, null);
	}
	
	public static void d(String msg, Throwable e){
		if(!live)
			android.util.Log.d(TAG, Thread.currentThread().getName() +"| "+ msg, e);
	}
	
	public static void i(String msg){
		i(msg, null);
	}
	
	public static void i(String msg, Throwable e){
		if(!live)
			android.util.Log.i(TAG, Thread.currentThread().getName() +"| "+ msg, e);
	}
	
	public static void e(String msg){
		e(msg, null);
	}
	
	public static void e(String msg, Throwable e){
		if(!live)
			android.util.Log.e(TAG, Thread.currentThread().getName() +"| "+ msg, e);
	}

	public static String identifyMessage(Resources res, Message msg) {
		try{ 
			return res.getResourceEntryName(msg.what); 
		} 
		catch(NotFoundException ignore){ 
			return "not found";
		}
	}	
}

That’s Definitely it! You now should be able to query YouTube to retrieve an atom XML object, parse this XML object into a domain object of your own, pass a list of domain objects to a view and have it display the user uploaded videos on your phone screen, magic!

Here’s the Search User Feed YouTube Atom XML Tutorial Project Eclipse Source Files <--- It is also mirrored on GitHub, feel free to make changes: https://github.com/blundell/YouTubeUserFeedAtomXml

I really really really recommend following a similar package structure to what is outlines in this project, it makes your code so much more maintainable in the long run and if you follow any of my other tutorials you’ll recognise the pattern and learn that, that much quicker 😉

P.s. that is a video on my YouTube channel of me attempting to grow a moustache! It was for charity, so if you could up vote it that’d be a great help.

12 thoughts on “[TUT] Show YouTube Feed for a Users Videos in a ListView ( XML )

  1. Hi there, I enjoy reading all of your article post. I wanted to write a little comment to support
    you.

Comments are closed.