Learning Java in 2020 – Beginner – Simple RSS Feed GUI

Overview reference

Problem

Goal

Simply obtain a valid URL from the command line, determine if the given URL has a rss feed ( <URL>/feed). If it does, display a list of all the items and allow the user to open a post by clicking on an item.

Pseudo-code

  1. Get URL from user
  2. Make request to URL
  3. Find rss feed link
    1. Check if rss feed exists: like content is in URL/feed
    2. Else return not found
  4. Make request to feed link
  5. Create UI to display news feed
    1. UI should check for new updates from

Solution

Main class

public class RssFeedGenerator  {
 String validuri;
	JFrame frame;

}

The main class has non-static attributes which are to be used for storing references to components of the GUI. The frame is the container which will hold all the rss feed items assembled from the user provided resource url.

Get user input

private  String getUserInput() {
		// gets user input and returns the value
		System.out.print("Type in the website url and press Enter:   ");
		Scanner sc = new Scanner(System.in);
		String input = sc.nextLine();
		return input;
	}

Does the Url even exist

I wanted to make sure the user provided url actually is a valid url and exists before making real requests to the resource. I did so by using the Uniform Resource Locator class, URL. By definition, a valid url contains the protocol, host, port ( default protocol port if not specified) and path, which points to the location of resource.

public URL(String spec)
    throws MalformedURLException

The constructor I used takes in a string, in our case the user provided url, and in the process parses the given string to a URL.

Since I did not specify the protocol needed for this program to work, http and https, if another protocol scheme e.g. file://host/path is inputted by the user, no exception will be thrown in the is valid url method.

private static boolean isValidUrl(String url) {
		System.out.println("Validating url");
		try {
			new URL(url);
			return true;
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		}
		
	}

According to the documentation, if the format of the string provided by the user does fit that of a standard URI, a MalformedURLException will be thrown.

MalformedURLException - if no protocol is specified, or an unknown protocol is found, or spec is null.

How to specify required uri scheme

private static boolean isValidUrl(String url) {
		System.out.println("Validating url");
		try {
			
			URL u = new URL(url);
			String protocol = u.getProtocol();
			if(protocol.indexOf("http") >= 0) {
				return true;
			}
			System.out.println("Protocol is not valid for program: " + u.getProtocol());
			return false;
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		}
		
	}

Remove trailing slashes

Just in case the user typed in a url ending with such a character “/”, lets remove it.

private static String cleanUrl(String url) {
		String cleanedurl = url;
		if(url.charAt(url.length() - 1) == '/') {
			cleanedurl = url.substring(0, url.length() - 1);
		}
		return cleanedurl;
	}

Is XML feed in given url

Once the uri has been validated and “cleaned” or trimmed, the Rss/XML resource is fetched. Before discovering the DocumentBuilderFactor, I thought I needed to fetch the XML content first, then parse it. But luckily several lines of code have been spared.

private static Document parseXMLContent(String url) {
		System.out.println("Parsing XML @ " + url);
		DocumentBuilderFactory  dbBuilderfactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder dbBuilder;
		try {
			dbBuilder = dbBuilderfactory.newDocumentBuilder();
			Document doc;
			try {
				doc = dbBuilder.parse(url);
				doc.getDocumentElement().normalize();
				return doc;
			} catch (SAXException | IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return null;
			}
			
		} catch (ParserConfigurationException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
		return null;
		
	}

DocumentBuilderFactory

With this module, xml trees can be parsed into DOM node objects, allowing for easier reading and handling of the content. Although the constructor of this class is protected, the class can be instantiated by calling the public static method called newInstance.

DocumentBuilder

This class helps us acquire the document of an xml tree. I created an instance by calling the newDocumentBuilder method of the DocumentBuilderFactory instance.

public Document parse(String uri)
               throws SAXException,
                      IOException

Its parse method returns the DOM document from the uri provided as an argument.

Document

According to the documentation, this represents the root HTML or XML document. From this object, one could access all the leaves, attributes of leaves in the document. The method returns either a value of type DOM document or null incase of an exception.

SAXException

An example of such an exception is the SAXException. This exception is usually in the context of XML parsing.

ParserConfigurationException

public class ParserConfigurationException
extends Exception
Indicates a serious configuration error.

Only setup GUI if any rss item was found

Once the resource XML has been parsed, the coast is clear to setup the graphical user interface. Initially I never knew about the FlowLayout class and mounted the buttons and texts without the layout. It looked like all the layers were built on top of one another, instead of next to another. So yes, we need it. There are other layouting techniques, but I chose the first one I saw which solved my problem. Read more here.

private void setupGUI() {
		 this.frame = new JFrame("My First GUI");
	       this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	       this.frame.setSize(300,300);
	       FlowLayout fl = new FlowLayout();
			 this.frame.setLayout(fl);
	}

Extract only needed attributes/element text values

In order to get the recent blog posts from the xml document, we need to select all elements of type “item”. Read here. Each item contains children nodes called title, description and link, which is what I need exactly. Nothing more or less. The getElementsByTagName allows us to select such items specifically along with their children elements/nodes.

private static NodeList getItemsInRssDocument(Document doc) {
		NodeList nodes = doc.getElementsByTagName("item");
		return nodes.getLength() > 0 ? nodes: null;
	}

Inner class in java

Things get a bit messy.

The GUI that I expect is a simple list of items, each containing a text and a button ( post link action). However if the main class implements the ActionListener, how does my actionPerformed method know which of the several buttons was clicked and which link to open?. So I opted for an inner class which will eventually implement the ActionListener interface. Each instance of this inner class, RssItem, will have their own title, link, description and actionPerformed properties/methods. The buttons will listen to their individual rssItem instance.

class RssItem implements ActionListener{
			String title;
			String link;
			String description;
			RssItem(String title, String link, String description){
				this.title = title;
				this.link = link;
				this.description = description;
			}
			@Override
			public void actionPerformed(ActionEvent e) {
				// TODO Auto-generated method stub
				if(Desktop.isDesktopSupported()) {
					try {
						Desktop.getDesktop().browse(new URI(this.link));
						
					} catch (IOException | URISyntaxException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}finally{
						System.exit(0);
					}
				}
			}
			
			
		}

Once a button has been clicked, the default browser opens the RssItem instance’s link. Voila.

Transform nodes

But we only have a NodeList though. Yes, all we need to do is create a list which will hold instances of the RssItem inner class, loop over the nodelist, extract the title, link and description of each node, create an RssItem instance and add it to the list.

private  ArrayList<RssItem> getRssItemsFromDoc(NodeList nodes){
		ArrayList<RssItem> rssitems = new ArrayList();
		for(int i = 0; i < nodes.getLength(); i++) {
			Node node = nodes.item(i);
			if(node.getNodeType() == Node.ELEMENT_NODE) {
				Element element = (Element) node;
				String title = element.getElementsByTagName("title").item(0).getTextContent();
				String link = element.getElementsByTagName("link").item(0).getTextContent();
				String description = element.getElementsByTagName("description").item(0).getTextContent();
				RssItem r = new RssFeedGenerator.RssItem(title, link ,description);
				rssitems.add(r);
			}
		}
		return rssitems;
	}

Generate GUI for Rss Feed Items

Once the list of RssItem instances have been created, we need to update the GUI. For each RssItem instance, mount a sub-container ( panel) holding an uneditable text and a button. The button is bound to the RssItem for the click action to work.

private  void generateRssFeedUI(ArrayList<RssItem> rssitems) {
		 
	      for(int i=0; i < rssitems.size(); i++) {
	    	  Panel pn = new Panel();
	    	  JLabel l = new JLabel(rssitems.get(i).title);
	    	  JButton b = new JButton("Read More");
	    	  pn.add(l);
	    	  pn.add(b);
	    	  this.frame.add(pn);
	    	  b.addActionListener(rssitems.get(i));
	    	 
	      }
	       this.frame.setVisible(true);
	}

If all the panels are mounted, the main container is made visible to the user.

Run Program

public static void main(String[] args) {
		// TODO Auto-generated method stub
			new RssFeedGenerator();
	}
RSSFeedGenerator program result
RSSFeedGenerator program result

Full Code

Here.

Author Notes

This is most likely not the cleanest implementation, just a heads up.

Links

  1. Insight into RSS Feeds: https://de.wikipedia.org/wiki/RSS_(Web-Feed)
  2. Python Implementation ( slightly advanced) : https://alvinalexander.com/python/python-script-read-rss-feeds-database
  3. https://www.xul.fr/en-xml-rss.html
  4. https://towardsdatascience.com/collecting-news-articles-through-rss-atom-feeds-using-python-7d9a65b06f70
  5. Validate URL : https://stackoverflow.com/questions/2230676/how-to-check-for-a-valid-url-in-java
  6. Making HTTP Requests : https://www.baeldung.com/java-http-request
  7. Making HTTP Requests (2): http://zetcode.com/java/getpostrequest/
  8. Making HTTP Request (3): https://stackabuse.com/how-to-send-http-requests-in-java/
  9. Getting input from resource : https://www.codejava.net/java-se/networking/how-to-use-java-urlconnection-and-httpurlconnection
  10. BufferReader : https://www.baeldung.com/java-buffered-reader
  11. XML Parsing : https://mkyong.com/java/how-to-read-xml-file-in-java-dom-parser/
  12. Nodes in XML : https://www.tutorialspoint.com/java_xml/java_dom_parse_document.htm
  13. RSS XML Format – Required Fields : https://validator.w3.org/feed/docs/rss2.html
  14. Hashmaps : https://www.w3schools.com/java/java_hashmap.asp
  15. ArrayList: https://www.w3schools.com/java/java_arraylist.asp
  16. ArrayList size : https://www.tutorialspoint.com/java/util/arraylist_size.htm
  17. Java UI : https://www.guru99.com/java-swing-gui.html
  18. Array List loop :https://beginnersbook.com/2013/12/how-to-loop-arraylist-in-java/
  19. Java GUI Layouting : https://docs.oracle.com/javase/tutorial/uiswing/layout/flow.html
  20. Java GUI button action : https://docs.oracle.com/javase/tutorial/uiswing/events/actionlistener.html
  21. Java open browser : https://stackoverflow.com/questions/17581455/opening-a-url-in-the-default-browser