Create an RSS Reader on Android

From WikiHTP
Revision as of 23:46, 1 July 2019 by Admin (talk | contribs) (Create Custom Items Layout)

Are you looking to create an RSS reader, to include content from a website in your Android application?

Do you need ideas to create an RSS feed app like Feedly, Flipboard or Flyne?

Well, in this tutorial you will see how to feed a list of elements with the news from the website forbes.com from your feed with RSS format through the Volley and Simple Framework XML technologies.

What Is A Feed?

The first thing you must understand before starting this tutorial is the meaning of feed. A feed is a source (source) of diffusion for web content.

They provide a summary and continuous updates on the content that are broadcast regularly. This in order that other information platforms can access it and present it.

On the other hand are the formats of retransmission, which are a set of formal definitions in plain text, which contain the hierarchy of content in a feed.

I suppose you've already heard that there are currently two popular formats for spreading content: RSS and Atom.

RSS ( Really Simple Syndication ) is an XML-based retransmission format to structure the most important data of a web source. Atom is exactly the same, simply that it uses other types of conventions in its structure.

The version RSS 2.0 and Atom 1.0 are currently used. The advantages of using each one are not relevant in this article, so I will not take them into account.

XML Structure Of the RSS 2.0 Format

In order to convert a flow of XML information to Java objects, it is essential that you understand the hierarchy and syntax used by RSS 2.0.

For example, the Rss file in the Forbes feed looks like this:

<rss xmlns:atom = "http://www.w3.org/2005/Atom"  
     xmlns:dc = "http://purl.org/dc/elements/1.1/" 
     xmlns:media = "http://search.yahoo.com/mrss/"
     xmlns:content = "http://purl.org/rss/1.0/modules/content/"
     version = "2.0">
    <channel><channel>
        <link> http://www.forbes.com/most-popular/ </link>
        <atom:link href = "http://www.forbes.com/most-popular/feed" rel = "self" type = "application/rss+xml"/> 
        <title> Forbes.com: Most popular stories </ title>
        <description> Most popular stories from Forbes . com </description>
        <item> ... </ item><item> ... </ item>
        <item> ... </ item><item> ... </ item>
        <item> ... </ item><item> ... </ item>
        <item> ... </ item><item> ... </ item>
        <item> ... </ item><item> ... </ item>
        <item> ... </ item><item> ... </ item>
        <item> ... </ item><item> ... </ item>
        <item> ... </ item><item> ... </ item>
        <item> ... </ item><item> ... </ item>
        <item> ... </ item><item> ... </ item>
    </ channel></ channel>
</ rss></ rss>

The root label is called <rss>. Within it is included all the content necessary to structure the content. By obligation must carry the attribute version, which represents the RSS version, which commonly will be "2.0".

The label <channel> represents an individual section of the feed in case the web content is divided into categories. Some of its children elements are:

  • <title>: Is the name of the feed. In my case, I chose the Most popular stories channel.
  • <link>: Contains the URL of the channel section.
  • <atom: link>: Contains the URL of the feed.
  • <description>: It's a short description of the feed.

Inside you will also find the labels <item>. These are the ones that interest us most and also the ones that give us the most work when dealing with information.

Let's see some of the child labels <item> that you will often find:

  • <title>: Represents the title of the article or news.
  • <description>: This is an introductory summary of the item usually represented by the meta tag HTML description.
  • <link>: It is the original URL of the item treated.
  • <pubDate>: Date on which the article was published.
  • <guid>: A unique identifier of the item. In the example is the same URL.
  • <enclosure>: Represents a multimedia element included in the item.

However, there will be Rss definitions that implement namespaces to support special modules that complement the characteristics of an element.

For example, "http://search.yahoo.com/mrss/" represents the RSS Media module that is similar to the tag <enclosure>, but it has many more features that you can indicate in a multimedia element.

Even if you see, the namespace is used atom to access the convention of the Atom format elements.

Reader Requirements RSS

Before the development let's see a little about the characteristics that the application should have:

  • As a Feedky user, I want the application to have a list of articles composed of the title, description and a thumbnail that goes with it.
  • As a user of Feedky, I want to see in detail the article that I selected in the list.

The solution to the first behaviour has already worked before. You know that for the list we can use the ListView or RecyclerView class and for the detail.

On the other hand, the visualization of the content of the article without leaving our application requires a new layout called WebView , which we will see in the development phase.

Wireframing Of The Android Application

Analyzing the scope of the application, we note that there are only two activities. The first is the main activity where we will see a list of articles and the second has the detail of the selected item.

Only one user touch interaction is enough to travel from one activity to another:

Creating UI For The Android Application

The next step is to build the XML definitions of the layouts for our interface. So far you can perceive three layouts: The main activity, the design of the items in the list and the detail activity.

Design Layout of the Main Activity

The main activity requires the use of a list through a ListView. Then go to the layout of your main activity (for me it is activity_main.xml ) and add as a root node a tag <ListView>:

activity_main.xml

<ListView xmlns:android = "http://schemas.android.com/apk/res/"
    android:layout_width = "match_parent"
    android:layout_height = "match_parent"
    android:id = "@+id/list"
    android:divider = "@null"
    android:dividerHeight = "0dp"
    android:background = "#F1F5F8"
    android:Padding = "6dp" />

there was a design of cards for the items, so our ListView should not contain dividing lines between them. To eliminate them, set @null to the divisible drawable with android: divider and reduce the height to 0dp with android: dividerHeight.

Create Layout of the Activity Detail

The detail activity simply represents the web content of the article that has been selected in the main activity.

This feature is well covered by a WebView. A special type of layout that renders web pages under the open source WebKit engine technology.

To implement its XML definition, the <WebView> tag is used as follows:

activity_detail.xml

    <WebView xmlns:android = "http://schemas.android.com/apk/res/"
             android:layout_width = "match_parent"
             android:layout_height = "match_parent" 
             android:id = "@+id/webview" />

Create Custom Items Layout

At the top, we will add the Forbes icon next to the word "Forbes". In the middle section, we will locate the description of the entry. And in the lower part, we will put the article's thumbnail next to the title of this one. The dividing line is optional, but if you are sophisticated you can leave it.

The idea is to use as a root a Card View with a Relative Layout inside for the distribution of the elements. Remember to include the dependence of the cards.

item_layout.xml

<?xml version = "1.0" encoding = "utf-8"?>
    <android.support.v7.widget.cardView 
        xmlns: android = "http://schemas.android.com/apk/res/android"
        android: layout_width = "match_parent"
        android: layout_height = "match_parent"
        card_view: cardCornerRadius = "2dp"
        card_view: cardElevation = "2dp"
        card_view: cardUseCompatPadding = "true">

    <RelativeLayout
        Android: layout_width = "match_parent"
        Android: layout_height = "match_parent"
        android: padding = "16dp">

        <! - MINIATURE ->
        
    <com.android.volley.toolbox.NetworkImageView
            android:layout_width = "80dp"
            android:layout_height = "80dp"
            android:id = "@+id/image"
            android:scaleType = "centerCrop"
            Android:layout_alignParentStart = "true"
            android: layout_below = "@+id/line"
            Android: layout_marginTop = "16dp" /> 

        <! - TITLE ->

        <TextView
            android:layout_width = "wrap_content"
            android:layout_height = "wrap_content"
            android:textAppearance = "?android:attr/textAppearanceSmall"
            android:text = "Title"
            android:id = "@+id/title"
            android:layout_marginBottom = "10dp"
            android:layout_toEndOf = "@+id/image"
            android:layout_alignTop = "@+id/image"
            android: layout_marginStart = "16dp" />

        <! - DESCRIPTION ->

        <TextView
            android:layout_width = "wrap_content"
            android:layout_height = "wrap_content"
            android:textAppearance = "?android:attr/textAppearanceSmall"
            android:text = "Description"
            android:id = "@+id/description"
            android:layout_marginBottom = "16dp"
            android:layout_below = "@+id/icon"
            android:layout_marginTop = "16dp" />

        <! - DIVIDING LINE ->

        <View
            android:layout_width = "wrap_content"
            android:layout_height = "1dp"
            android:id = "@+id/line"
            android:background = "#ffe9e9e9"
            android:layout_below = "@+id/description" />

        <! - FORBES ICON ->

        <ImageView
            android:layout_width = "48dp"
            android:layout_height = "48dp"
            android:id = "@+id/icon"
            android:layout_alignParentTop = "true"
            android:layout_alignParentLeft = "true"
            android:layout_alignParentStart = "true"
            android:src = "@drawable/forbes" />

        <! - MARK FORBES ->

        <TextView
            android:layout_width = "wrap_content"
            android:layout_height = "wrap_content"
            android:textAppearance = "?android:attr/textAppearanceSmall"
            android:text = "Forbes"
            android:id = "@+id/publisher"
            android:layout_toEndOf = "@+id/icon"
            android:textStyle = "bold"
            android:layout_marginStart = "16dp" />

    </ RelativeLayout>
    </android.support.v7.widget.CardView>

Android Application Architecture

Before coding I created a sketch about the components that we must coordinate so that our application works with a good design pattern. Because our application must make an HTTP request to the Forbes server to obtain xml resources and then present that information to the user, you can consider a Model View Network Controller design . The diagram shows how from the Home or main activity we make a request with Volley to the web, which will send a response that will be stored in SQLite. After that, the view is updated. Model Vista Network Controller in Android

Additionally from Home the event handler will be pending to show the detail of each element in the Detail activity .

It would be ideal to use restrictions of the RESTful style to handle the requests from the model, but so far we have not talked about the necessary topics for it.

It is important to note that the MVC model falls short because we will not use an observation pattern for real-time data synchronization.

The diagram shows that we will use a local SQLite database to simulate a kind of Caching , which will allow to retain the consulted data and have it as a base to update the content each time the application is started.

To complete the Network MVC par excellence along with the REST practices, we need to use a ContentProvider together with a SyncAdapter. But these will be topics that we will see in future articles.

6. Application Coding Well, we already know what elements we should build to shape Feedky . If everything has gone well, so far your project in Android Studio must have the following materials:

MainActivity.java class Layout activity_main.xml Class DetailActivity.java Layout activity_detail.xml Layout item_layout.xml Before starting it is important to add the permission of connections to the internet and the network status in the Android Manifest :

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

We are going to use Volley to manage HTTP requests so incorporate it into the project the way you want. In my case I add it as an additional module.

When the above conditions are ready, then we go to coding each step of operation.

Step # 1: Create the SQLite Database Before thinking about making a request it is necessary to have our local storage. It is logical that in terms of conceptual design of databases , only the entity Entrance is needed . With this table we will ensure the data of the feed.

Rss Reader Database on Android

So we must find that our Contract Class or database script implements the following command:

CREATE TABLE entry ( _ID INTEGER PRIMARY KEY AUTOINCREMENT , title TEXT , description TEXT , url TEXT , thumb_url TEXT ); The table has the respective columns to represent the content of the elements in the list.

title: It is the title of the entry. Description: It is the summary of the entry. url: Link of the article to visualize its detail. thumb_url: URL of the thumbnail (thumbnail). With these conditions your script would look like this:

import android . provider . BaseColumns ;

/ **

* Created by Beautiful Programming
*
* Class that represents a restore script of the initial state of the database
* /

public class ScriptDatabase { / *

   Label for Debugging
    * / private static final String TAG = ScriptDatabase . class . getSimpleName ();
       
   // Metainformation of the final public static database String ENTRADA_TABLE_NAME = "entry" ; public static final String STRING_TYPE = "TEXT" ; public static final String INT_TYPE = "INTEGER" ;
       
       
       
   // Fields in the input table public static class ColumnEntries { public static final String ID = BaseColumns . _ID ; public static final String TITLE = "title" ; public static final String DESCRIPTION = "description" ; public static final String URL = "url" ; public static final String URL_MINIATURA
       
           
           
           
           
          = "thumb_url" ; } 
   
   // CREATE command for the table PUBLISH public static end String CREATE_ENTER = "CREATE TABLE" + ENTRADA_TABLE_NAME + "(" + ColumnEntries . ID + "" + INT_TYPE + "primary key autoincrement ," + ColumnEntries . TITLE + "" + STRING_TYPE + "not null," + ColumnEntries . DESCRIPTION + "" + STRING_TYPE + ","+
      
              
                       
                       
                       
                   ColumnEntries . URL + "" + STRING_TYPE + "," + ColumnEntries . URL_MINIATURE + "" + STRING_TYPE + ")" ;    
                     


} Now we will extend the SQLiteOpenHelper class to create our database administrator. Here we will include three methods for vital operations: The insertion of rows, the modification and the obtaining of all the elements of the input table:

FeedDatabase.java

import android . content . ContentValues ; import android . content . Context ; import android . database . cursor ; import android . database . sqlite . SQLiteDatabase ; import android . database . sqlite . SQLiteOpenHelper ; import android . useful . Log ;




import com . herprogramacin . hermosaprogramacion . RssParse . Item ;

import java . useful . HashMap ; import java . useful . List ;


/ **

* Created by Beautiful Programming.
*
* Class that manages access and operations to the database
* /

public final class FeedDatabase extends SQLiteOpenHelper {

   // Fast mapping of indices private static final int COLUMN_ID = 0 ; private static final int COLUMN_TITLE = 1 ; private static final int COLUMN_DESC = 2 ; private static final int COLUMN_URL = 3 ;
       
       
       
       
   / *
   Singleton instance
   * / private static FeedDatabase singleton ;
     
   / *
   Debug label
    * / private static final String TAG = FeedDatabase . class . getSimpleName ();
       


   / *
   Database name
    * / public static final String DATABASE_NAME = "Feed.db" ;
       
   / *
   Current version of the database
    * / public static final int DATABASE_VERSION = 1 ;
       


   private FeedDatabase ( Context context ) { super ( context , 
               DATABASE_NAME , null , 
               DATABASE_VERSION );  
       
               
   }
   / **
    * Returns the single instance of the singleton
    *
    * @param context context where requests will be executed
    * @return Instance
    * / Public static synchronized FeedDatabase getInstance ( Context context ) { if ( singleton == null ) { 
           singleton = new FeedDatabase ( context . GetApplicationContext ()); } return singleton ; }
       
            
       
       
   
   @Override public void onCreate ( SQLiteDatabase db ) { // Create the table 'entry' 
       db . execSQL ( ScriptDatabase . CREAR_ENTRADA );
     
       


   }
   @Override public void onUpgrade ( SQLiteDatabase db , int oldVersion , int newVersion ) { // Add the changes that will be made to the 
       db schema . execSQL ( "DROP TABLE IF EXISTS" + ScriptDatabase . ENTRADA_TABLE_NAME ); 
       onCreate ( db ); }
       
         
   
   / **
    * Get all the records in the input table
    *
    * @return cursor with records
    * / public Cursor getEntries () { // We select all the rows of the table 'entry' return getWritableDatabase (). rawQuery ( "select * from" + ScriptDatabase . ENTRADA_TABLE_NAME , null ); }
     
       
       
                  
   
   / **
    * Insert a record in the input table
    *
    * @param title entry title
    * @param description description of the entry
    * @param url url of the article
    * @param thumb_url thumbnail url
    * / public void insertEntry ( String title , String description , String url , String thumb_url ) {
    
           
           
           
            
       ContentValues values = new ContentValues (); 
       values . put ( ScriptDatabase . ColumnEntradas . TITLE , title ); 
       values . put ( ScriptDatabase . ColumnEntries . DESCRIPTION , description ); 
       values . put ( ScriptDatabase . ColumnEntradas . URL , url ); 
       values . put  ( ScriptDatabase . ColumnEntries . URL_MINIATURA , thumb_url );
       // Inserting the record in the database 
       getWritableDatabase (). insert ( ScriptDatabase . ENTRADA_TABLE_NAME , null ,
               
               
               values
       ); }
   
   / **
    * Modify the values ​​of the columns of an entry
    *
    * @param id entry identifier
    * @param title new entry title
    * @param description new description for the entry
    * @param url new url for the entry
    * @param thumb_url new url for the entry thumbnail
    * / public void updateEntry ( int id , String title , String description , String url , String thumb_url ) {
    
                                 
                                 
                                 
                                  
       ContentValues values = new ContentValues (); 
       values . put ( ScriptDatabase . ColumnEntradas . TITLE , title ); 
       values . put ( ScriptDatabase . ColumnEntries . DESCRIPTION , description ); 
       values . put ( ScriptDatabase . ColumnEntradas . URL , url ); 
       values . put  ( ScriptDatabase . ColumnEntries . URL_MINIATURA , thumb_url );
       // Modify input 
       getWritableDatabase (). update ( ScriptDatabase . ENTRADA_TABLE_NAME , 
               values , ScriptDatabase . ColumnEntradas . ID + "=" , new String [] { String . valueOf ( id )});
               
                
                
   }

} How do you see insertEntry (), updateEntry (), and getEntries () represent the operations needed. You can also use a singleton pattern to generalize the database assistant and access it from a single instance, that's why you see the getInstance () method and the private constructor.

Android Studio provides a template to create a singleton. See how we do with Volley that also implements this design style ...

Step # 2: Create Singleton Pattern for Volley To create a new singleton that limits the spread of Volley you must right click on your java package and select "Java Class".

Create New Class In Android Studio Now select the "Singleton" option and name the class as VolleySingleton :

New Singleton Type Class In Android Studio Remember that we need to implement a queue of requests and an image loader to download images. In the end the class would be like this:

VolleySingleton.java

import android . content . Context ; import android . graphics . Bitmap ; import android . support . v4 . useful . LruCache ;


import com . android . volley . Request ; import com . android . volley . RequestQueue ; import com . android . volley . toolbox . ImageLoader ; import com . android . volley . toolbox . Volley ;



/ **

* Created by Beautiful Programming.
*
* Class representing a HTTP Volley client
* /

public final class VolleySingleton {

   // Attributes private static VolleySingleton singleton ; private ImageLoader imageLoader ; private RequestQueue requestQueue ; private static Context context ;
     
    
    
     


    private VolleySingleton ( Context context ) { VolleySingleton . context = context ; 
       requestQueue = getRequestQueue ();  
       
       imageLoader = new ImageLoader ( requestQueue , new ImageLoader . ImageCache () { private end LruCache < String , Bitmap > 
                           cache = new LruCache <> ( 40 );  
                 
                        
                   @Override public Bitmap getBitmap ( String url ) { return cache . get ( url ); }
                     
                       
                   
                   @Override public void putBitmap ( String url , Bitmap bitmap ) { 
                       cache . put ( url , bitmap ); } }); }
                      
                   
               
   
   / **
    * Returns the single instance of the singleton
    * @param context context where requests will be executed
    * @return Instance
    * / Public static synchronized VolleySingleton getInstance ( Context context ) { if ( singleton == null ) { 
           singleton = new VolleySingleton ( context . GetApplicationContext ()); } return singleton ; }
       
            
       
       
   
   / **
    * Gets the instance of the request queue
    * @return request queue
    * / public RequestQueue getRequestQueue () { if ( requestQueue == null ) { 
           requestQueue = Volley . newRequestQueue ( context . getApplicationContext ()); } return requestQueue ; }
     
           
       
       
   
   / **
    * Add the request to the queue
    * @param req request
    * @param <T> Final result of type T
    * / public < T > void addToRequestQueue ( Request < T > req ) { 
       getRequestQueue (). add ( req ); }
      
   
   public ImageLoader getImageLoader () { return imageLoader ; }  
       
   

} Step # 3: Generate an XML Parser for the RSS Feed In my opinion, this is the core of the problem we are taking to create our Feedky reader . The other topics we have already discussed in previous articles, but the XML parsing is new.

Remember when we saw parsing of JSON formats ?

Through an auxiliary class we read the flow of the JSON objects, where we identified the important attributes and converted them into Java objects to be used in our lists.

That's exactly what you have to do with the XML tags . The question is what class or library should we use to parse the elements of the feed. In simple words what we need is to move from an XML hierarchy to Java objects.

Parsing XML to Java on AndroidThe Android Developers documentation has a practical example that parse an Atom feed with a library called XMLPULL , which contains a main class called XmlPullParser that allows the reading of the XML elements of a data flow.

Within the Java library we can find another class called SAXParser for XML parsing very useful also if you want to take a look.

Similar to JsonReader, XmlPullParser has methods to obtain labels, attributes, namespaces and CDATA contents . But in particular I do not like to implement long classes in low level to extract data, that's why I'll tell you about an excellent parsing library that I found ...

THE SIMPLE LIBRARY FOR XML SERIALIZATION The Simple library is a powerful tool for both serializing XML elements and for deserializing them as Java objects. It gives us a system of annotations that facilitates tremendously the description of the objects that the XML tags will reference.

It really leaves the parsing at a high level and we can save a lot of development time. Include Simple XML Framework in Android Studio To include it in Android Studio you must download the Simple 2.7.1 distribution package .

Simple XML Framework Download Page

You extract the contents of the .rar file and then you go to the "jar" folder. Once there, copy and paste the simple-xml-2.7.1.jar file into the "libs" folder of your main module:

Folder libs From Android Studio

Now press right on the file and select the "Add As Library ..." option:

Add As Library In Android Studio

Select the module where you want to refer to its operation:

Choose App Module in Android Studio

With this we will have at our disposal the features of Simple Framework XML at our disposal. How to parse XML files with Simple? You can see the complete documentation on the use in the "Tutorial" section of the official website.

However, I am going to summarize the characteristics of deserialization that we need to use. The way to establish which labels we want to obtain and in what type of organization is determined through the reference annotations .

The idea is to establish with them which classes represent the labels, which are daughters, what attributes they have, if it is necessary to obtain several elements, etc. Some of the most frequent are:

@Root: Represents the Java equivalent of an XML object. @Attribute: Reference the attribute of an XML element in a Java object. @Element: Used to represent a child element of an XML tag. @ElementList: Refers to a list of child elements of the same type and characteristics. to. Xml Tags : To indicate that a class is equivalent to an xml tag, simply locate the @Root annotation at the top of its header. For example…

@Root ( name = "rss" , strict = false ) public class Rss { ... }


In the previous case, the Rss class is created to represent the <rss> tag of the format. For the @Root annotation you can specify two parameters: name and strict.

Where name is the name of the XML tag and strict indicates to the framework if we need to deserialize all the child elements and attributes of the tag in our class.

For name we will use the string "rss". For strict we will use false, since <rss> has attributes that we do not want to reflect in our class.

b. Child elements : Use the annotation @Element if you want to declare one element as the child of another. For example, the Rss class must contain a Channel object as a child:

// Inside Rss @Element private Channel channel ;


We can also specify a series of attributes:

data: Determines whether the element is inside a CDATA block or not. name: The name of the child element. required: Specifies whether the value of the element is mandatory or not. type: It is the data type of the value of the element. c. Element Lists: If you want to indicate that an element contains a list use the annotation @ElementList. A good example of this would be where the classChannelcontains a list of Item elements as we saw in the Rss hierarchy.

Channel.java

import org . simpleframework . xml . ElementList ; import org . simpleframework . xml . Root ;


import java . useful . List ;

/ **

* Created by Beautiful Programming.
*
* Class that represents the <channel> tag of the feed
* /

@Root ( name = "channel" , strict = false ) public class Channel {


   @ElementList ( inline = true ) private List < Item > items ;  
    
   public Channel () { }  
   
   public Channel ( List < Item > items ) { this . items = items ; }  
       
   
   public List < Item > getItems () { return items ; } }  
       
   

The inline parameter tells the framework that <channel> does not only contain the list of <item> elements, but that there are other elements. If you indicate true the framework will ignore the different elements of the list. The default value is false.

d. Namespaces: We have already said that sometimes the Rss formatswill have namespacecs that represent extension modules for the representation of detailed data about an element.

That is why we should use the @Namespace annotation to satisfy this type of hierarchy.

For example…

The <media: content> tag implements a namespace for the description of multimedia elements of each entry in the feed. We know that this has an attribute with the value of the url of the miniature, therefore we need its reading.

The implementation of the namespace is declared in the <rss> node, so right there we should use an @Namespace annotation:

Rss.java

import org . simpleframework . xml . Element ; import org . simpleframework . xml . Namespace ; import org . simpleframework . xml . Root ;


/ **

* Created by Beautiful Programming
*
* Class that represents the <rss> element of the feed
* /

@Root ( name = "rss" , strict = false ) @Namespace ( reference = " http : // search . Yahoo . Com / mrss / " ) public class Rss {



   @Element private Channel channel ;
    
   public Rss () { }  
   
   public Rss ( Channel channel ) { this . channel = channel ; }  
       
   
   public Channel getChannel () { return channel ; } }  
       
   

Under @Root you indicate the namespace. You reference the reference with the reference parameter. In this case, the value is the URI of the Media module .

However now you must declare the prefix of the element that represents the <media: content> tag in the Item class:

Item.java

import org . simpleframework . xml . Element ; import org . simpleframework . xml . Namespace ; import org . simpleframework . xml . Root ;


/ **

* Created by Beautiful Programming.
*
* Class that represents the <item> tag of the feed
* /

@Root ( name = "item" , strict = false ) public class Item {


   @Element ( name = "title" ) private String title ;
    
   @Element ( name = "description" ) private String description ; 
    
   @Element ( name = "link" ) private String link ;
    
   @Element ( name = "content" ) @Namespace ( reference = " http : // search . Yahoo . Com / mrss / " , prefix = "media" ) private Content content ;
   
    


   public Item () { }  
   
   public Item ( String title , String description , String link , Content content ) { this . title = title ; this . description = description ; this . link = link ; this . content = content ; }     
       
       
       
       
   
   public String getTitle () { return title ; }  
       
   
   public String getDescription () { return description ; }  
       
   
   public String getLink () { return link ; }  
       
   
   public Content getContent () { return content ; } }  
       
   

As you can see, the Content class represents the tag with the namespace. We simply use the @Namespace annotation including the prefix parameter with the value of the "average" prefix.

and. Attributes : The Rss file contains almost no attributes that interest us in our tags, except for the url attribute of the <media: content> tag. To extract it simply mark a variable with the annotation @Attribute.

Content.java

import org . simpleframework . xml . Attribute ; import org . simpleframework . xml . Root ;


/ **

* Created by Beautiful Programming.
*
* Class that represents the <media: content> tag of the feed
* /

@Root ( name = "content" , strict = false ) public class Content {


   @Attribute ( name = "url" ) private String url ;
    
   public Content () { }  
   
   public Content ( String url ) { this . url = url ; }  
       
   
   public String getUrl () { return url ; } }  
       
   

and. The Serializer class : The Simple libraryuses its Serializer main class for the representation of an XML element that can be serialized or deserialized.

Although we can not instantiate it directly, a class called Persister is used to create an instance that allows data to be persisted. Persister implements a large number of XML data reading and writing methods depending on the source and type of data.

If you downloaded the feed to have local access, you can use the read method of the Serializer class in the following way:

Serializer serializer = new Persister (); File source = new File ( "path / folder / rss.xml" ); Rss rss = serializer . read ( Rss . class , source );

This method receives the type of element with which the xml file will be deserialized, which has a reference in the source object of type File.

However, you can also load it from an InputStream data stream. But we'll see that in the next step ...

Step # 4: Create an XML Personalized Request With Volley Now the turn is for our HTTP request. We know we can use the HttpURLConnection client for that purpose, but as you know, Volley automates a lot of the work.

Similar to the custom request for JSON formats that was created as an example in the Volley article, we must derive our request from the Request <T> class.

XmlRequest.java

import android . useful . Log ;

import com . android . volley . AuthFailureError ; import com . android . volley . NetworkResponse ; import com . android . volley . ParseError ; import com . android . volley . Request ; import com . android . volley . Response ; import com . android .



volley . toolbox . HttpHeaderParser ;

import org . simpleframework . xml . Serializer ; import org . simpleframework . xml . core . Persister ; import java . io . UnsupportedEncodingException ; import java . useful . Map ;



/ **

* Created by Beautiful Programming.
*
* Custom request for dealing with XML flows
* /

public class XmlRequest < T > extends Request < T > {

   private static final String TAG = XmlRequest . class . getSimpleName ();    
   // Attributes private final Class < T > clazz ; private final Map < String , String > headers ; private final Response . Listener < T > listener ; private final Serializer serializer = new Persister ();
     
      
     
       
   / **
    * It is predefined for the use of GET requests
    * / Public XMLRequest ( String url , Class < T > clazz , Map < String , String > headers , Response . Listener < T > listener , Response . ErrorListener ErrorListener ) { Super ( Method . GET , url , ErrorListener ); this . clazz = clazz
       
                       
       
       ; this . headers = headers ; this . listener = listener ; }
       
       
   
   @Override public Map < String , String > getHeaders () throws AuthFailureError { return headers ! = Null ? headers : super . getHeaders (); }
        
          
   
   @Override protected void deliverResponse ( T response ) { 
       listener . onResponse ( response ); }
     
   
   @Override protected Response < T > parseNetworkResponse ( NetworkResponse response ) { try {
     
        
           // Converting the stream in formatted format UTF-8 String xml = new String ( response . Data , "UTF-8" );
              
           // Debugging ... Log . d ( TAG , xml );
           
           // Sending the paired response return Response . success ( 
                  serializer . read ( clazz , xml ), HttpHeaderParser . parseCacheHeaders ( response ));
            
                   
       } catch ( UnsupportedEncodingException e ) { return Response . error ( new ParseError ( e )); } catch ( Exception e ) { 
           e . printStackTrace (); return Response . error ( new ParseError ( e )); } } }   
             
          
             
       
   

When sending the response with parseNetworkResponse () we see that the flow coming from response is converted to String and taken with read (). This will return directly to a java object of the clazz type, which in our case is Rss.

Step # 5: Send Request to Forbes Server Sending the request to obtain the XML format is through the addRequestQueque () method of our singleton Volley.

But where should you invoke it?

Well, this choice depends a lot on the MVC architecture of Red. Ideally, the model makes queries to the server to generate an immediate caching and not atrophy our main thread. Here a Content Provider would come in handy.

However it is possible to do it in the view or the controller as long as it is in the background. It is also important to define the way in which the updating of the data in the database will be observed in order for the list to be refreshed.

Now, the caching of the simple data is done on SQLite, but ... how to do the caching of the images? That has no problem, Volley is in charge of managing this for you.

Perform XML request from the main activity We will add the new request to the queue of Volley requests in the onCreate () method of MainActivity. The idea is to create a method that processes the response to the request and stores the information in the database:

@Override protected void onCreate ( Bundle savedInstanceState ) { super . onCreate ( savedInstanceState );

   setContentView ( R . layout . activity_main ); // Get the list 
   listView = ( ListView ) findViewById ( R . Id . List );
 
   
    
   VolleySingleton . getInstance ( this ). addToRequestQueue ( new XMLRequest <> ( 
                   URL_FEED , Rss . class , null , new Response . Listener < RSS > () { @Override public void onResponse ( Rss response ) { // Caching FeedDatabase . getInstance ( MainActivity . esta ).
            
                   
                   
                     
                       
                         
                           
                           
                                   synchronizeEntries ( response . getChannel (). getItems ()); // Initial data load ...
                           
                       } }, new Response . ErrorListener () { @Override public void onErrorResponse ( VolleyError error ) { Log . d ( TAG , "Error volley" + mistake . getMessage ()); } } ) );
                   
                     
                       
                         
                             
                       
                   
           
   

} Perform information caching

The response obtained from the request must be immediately stored in our database. For this the method synchronizarEntradas () was created, which processes the list of items thrown.

/ **

* Process a list of items for local storage
* and synchronization.
*
* @param entries list of items
* / public void synchronizeEntries ( List < Item > entries ) { / *
 
   
   # 1 Temporally map new entries to make a
       comparison with local
   * / HashMap < String , Item > entryMap = new HashMap < String , Item > (); for ( Item e : entries ) { 
       entryMap . put ( e . getTitle (), e ); }
       
     
   
   / *
   # 2 Get local tickets
    * / Log . i ( TAG , "View currently stored items" ); Cursor c = getEntries (); assert c ! = null ; Log . i ( TAG , "found" + c . getCount () + "inputs, computing ..." );
    
   
    
       
   / *
   # 3 Start comparing entries
    * / int id ; String title ; String description ; String url ;
   
   
   
   
   while ( c . moveToNext ()) {  
       id = c . getInt ( COLUMN_ID ); 
       title = c . getString ( COLUMN_TITLE ); 
       description = c . getString ( COLUMN_DESC ); 
       url = c . getString ( COLUMN_URL );
       Item match = entryMap . get ( title ); if ( match ! = null ) { // Filter existing entries. Remove to prevent future 
           entryMap entry . remove ( title );
          
           
           / *
           # 3.1 Check if the entry needs to be updated
           * / if (( match . getTitle () ! = null && ! match . getTitle (). equals ( title )) || ( match . getDescription () ! = null && ! match . getDescription (). equals ( description )) || ( match . getLink () ! = null && ! match . getLink ().
                 
                        
                       equals ( url ))) { // Update entries 
               actualizarEntrada ( 
                       id , 
                       match . getTitle (), 
                       match . getDescription (), 
                       match . GetLink (), 
                       match . getContent (). getUrl () ); 
               
               
           } } } 
   c . close ();
       
   
   / *
   # 4 Add new entries
   * / for ( Item e : entryMap . values ()) { Log . i ( TAG , "Inset: title =" + e . getTitle ()); 
       insertarEntrada ( 
               e . getTitle (), 
               e . getDescription (), 
               e . GetLink (), 
               e . getContent (). getUrl () ); } Log
     
         
       
   
   . i ( TAG , "Records were updated" ); 


} This method is in charge of saving in the database all the entries that the feed has from the list that you enter as a parameter.

As you can see in the comments, 4 steps were established that mark your journey. The first was to map the new entries into a new set whose key is the title of the entry. The title was chosen because it will represent its identifier.

Then the existing local entries in the database were obtained. This will allow a comparison between both groups. Where will be filtering the duplicate entries to make the tour.

If the entry exists but had some change in its structure, its content is updated through the updateEntry () method.

Once the comparison is finished, those entries that still remain on the map are stored, which do not yet exist.

Performing image caching Although volley provides caching based on the DiskBaseCache class, responses are subject to the directives that the external server has set.

That is, if the server has declared that its resources expire in 30 minutes, do not expect the images to remain for longer than that amount.

Or even if the cache control headers indicate that the flow should not be stored, then you will not have the stored thumbnail at any time.

How to keep the images in cache?

Well, there are several libraries that can be helpful to store our thumbnails on the local disk. One of them is Android Universal Image Loader , which allows you to download images, give them persistence in cache and view them in an optimized way.

Example Android Application With Universal Loader Library The Picasso library is also an excellent option. As universal, it allows you to cache images, in addition to having a very short learning curve.

Example Android Application With Picasso Bookstore Now if you do not want to go that far, you can write your own definition of local cache with the help of Jake Wharton and his implementation of caching .

However, the solution that I will implement for this tutorial is based on the modification of the same Volley library.

Have you seen the operation of the parseNetworkResponse () method in the requests?

Well, that method when returning the answer with the method success () uses as parameter the HTTP headers that the server has sent.

To parse the headers that come in the answer there exists the class HttpHeaderParser, which compares the labels of each header and extracts its corresponding values.

It is right there where the duration of our images and their caching arrangement originates through the static method parseCacheHeaders ().

Now ... what if we alter this method or create a new one so that the values ​​of the headers are ignored?

In this discussion about the alteration of the HTTP headers that Volley receives , a way to handle this situation is explained where the duration results are ignored.

We simply need to create a new method called parseIgnoreCacheHeaders () and call it in the ImageRequest class, which is the request that ImageLoader uses.

Let's see:

// Inside HttpHeaderParse ... public static Cache . Entry parseIgnoreCacheHeaders ( NetworkResponse response ) { long now = System . currentTimeMillis ();


   Map < String , String > headers = response . headers ; long serverDate = 0 ; String serverEtag = null ; String headerValue ; 
    
    
   
   headerValue = headers . get ( "Date" ); if ( headerValue ! = null ) { 
       serverDate = HttpHeaderParser . parseDateAsEpoch ( headerValue ); }
       
   
   serverEtag = headers . get ( "ETag" );
   final long cacheHitButRefreshed = 3 * 60 * 1000 ; // 3 minutes available before the final operations long cacheExpired = 24 * 60 * 60 * 1000 ; // expires in 24 hours end long softExpire = now + cacheHitButRefreshed ; end long ttl = now + cacheExpired ;       
            
    
    
   Cache . Entry entry = new Cache . Entry (); 
   entry . data = response . data ; 
   entry . etag = serverEtag ; 
   entry . softTtl = softExpire ; 
   entry . ttl = ttl ; 
   entry . serverDate = serverDate ; 
   entry . responseHeaders = headers ;  
   return entry ; }

Now go to the doParse () method of the ImageRequest class (which is responsible for parsing the flow to bitmap) and make the success method of the response implement our new method:

return Response . success ( bitmap , HttpHeaderParser . parseIgnoreCacheHeaders ( response )); The way you know if it works or not is running the application so that the thumbnails are loaded. After that, disconnect the internet connection, close the application open it again. If all the thumbnails appear, then it was a success.

I do not know the functional effectiveness of this method. It should still be tested with the Volley tracking to see the response times and hitting of the cache. However, it is functional and easy to implement.

Step # 6: Create A Custom CursorAdapter For The List Because the list is populated directly from the contents of the database, it is necessary to derive our adapter from the CursorAdapter class to traverse the records.

We will use a View Holder design pattern to optimize findViewById () calls on our adapter. The images will be obtained through the requests of the ImageLoader and thus automatically saved in cache:

FeedAdapter.java

import android . content . Context ; import android . database . cursor ; import android . support . v4 . widget . CursorAdapter ; import android . view . LayoutInflater ; import android . view . View ; import android . view . ViewGroup ; import android .



widget . TextView ;

import com . android . volley . toolbox . ImageLoader ; import com . android . volley . toolbox . NetworkImageView ; import com . herprogramacin . hermosaprogramacion . Model . ScriptDatabase ; import com . herprogramacin . hermosaprogramacion . R ; import com . herprogramacin .


hermosaprogramacion . Web . VolleySingleton ;

/ **

* Created by Beautiful Programming
*
* Adapter to inflate the ticket list
* / public class FeedAdapter extends CursorAdapter {
    
   / *
   Debug Label
    * / private static final String TAG = FeedAdapter . class . getSimpleName ();
       
   / **
    * View holder to avoid multiple calls from findViewById ()
    * / static class ViewHolder { TextView title ; TextView description ; NetworkImageView image ;
      
       
       
       
       int tituloI ; int descriptionI ; int imageI ; }
       
       
   
   public FeedAdapter ( Context context , Cursor c , int flags ) { super ( context , c , flags );    
       
   }
   public View newView ( Context context , Cursor cursor , ViewGroup parent ) { LayoutInflater inflater = LayoutInflater . from ( parent . getContext ());    
        
       View view = inflater . inflate ( R . layout . item_layout , null , false );  
       ViewHolder vh = new ViewHolder ();  
       // Store references 
       vh . title = ( TextView ) view . findViewById ( R . id . title ); 
       vh . description = ( TextView ) view . findViewById ( R . id . description ); 
       vh . image = ( NetworkImageView ) view . findViewById ( R . id   . image );
       // Set indices 
       vh . tituloI = cursor . getColumnIndex ( ScriptDatabase . ColumnEntradas . TITLE ); 
       vh . descriptionI = cursor . getColumnIndex ( ScriptDatabase . ColumnEntries . DESCRIPTION ); 
       vh . imageI = cursor . getColumnIndex ( ScriptDatabase . ColumnEntries . URL_MINIATURE );
       view . setTag ( vh );
       return view ; }
   
   public void bindView ( View view , Context context , Cursor cursor ) {    
       final ViewHolder vh = ( ViewHolder ) view . getTag ();  
       // Set the text to the title 
       vh . title . setText ( cursor . getString ( vh . tituloI ));
       // Get access to the description and its length int ln = cursor . getString ( vh . descriptionI ). length (); String description = cursor . getString ( vh . descriptionI );
       
       
       // Shorten description to 150 characters if ( ln > = 150 ) 
           vh . description . setText ( description . substring ( 0 , 150 ) + "..." ); else vh . description . setText ( description );
          
       
       // Get image URL String thumbnailUrl = cursor . getString ( vh . imageI );
       
       // Get instance of ImageLoader ImageLoader imageLoader = VolleySingleton . getInstance ( context ). getImageLoader ();
        
       // Dump data in the image view 
       vh . image . setImageUrl ( thumbnailUrl , imageLoader );
   } }

If you look closely, the view holder also stores the index of the cursor columns to avoid obtaining it multiple times.

We have also added a restriction for the description size of 150 characters. And we have used a NetworkImageView to assign the images through the image loader.

Step # 7: Populate The List Asynchronously The next step is to declare all the global instances within our main activity to project the elements of the interface. With this we can create an asynchronous task that queries all records in the database in the background without altering the main thread .

Although asynchronous tasks are excellent for background work, you are not able to satisfy the record of an observer. At the moment we have not seen the CursorLoader class for the management of operations with the database, but it is the ideal one for this type of processes.

The asynchronous task should load the data in a cursor with the method getEntries () and then present this data to the adapter that will be associated with the list:

public class LoadData extends AsyncTask < Void , Void , Cursor > {

   @Override protected Cursor doInBackground ( Void ... params ) { // Initial load of records return FeedDatabase . getInstance ( MainActivity . this ). getEntries ();
      
       
        
   }
   @Override protected void onPostExecute ( Cursor cursor ) { super . onPostExecute ( cursor );
     
       
       // Create the adapter 
       adapter = new FeedAdapter ( MainActivity . Esta , 
               cursor , SimpleCursorAdapter . FLAG_REGISTER_CONTENT_OBSERVER );  
               
               
       // Relate the list with the 
       listView adapter . setAdapter ( adapter ); } }
   

Then start the asynchronous task in onCreate (), when synchronizeEntries () has been carried out.

Before you must make sure that the internet connection is available. Remember that this you find out with the ConnectivityManager connection manager:

@Override protected void onCreate ( Bundle savedInstanceState ) { super . onCreate ( savedInstanceState );

   setContentView ( R . layout . activity_main );
 
   
   // Get the list 
   listView = ( ListView ) findViewById ( R . Id . List ); 
   ConnectivityManager connMgr = ( ConnectivityManager ) 
           getSystemService ( Context . CONNECTIVITY_SERVICE ); NetworkInfo networkInfo = connMgr . getActiveNetworkInfo (); if ( networkInfo ! = null && networkInfo . isConnected ()) { VolleySingleton . getInstance ( this ). addToRequestQueue ( new XmlRequest <> ( 
   
       
       
                
                       URL_FEED , Rss . class , null , new Response . Listener < Rss > () { @Override public void onResponse ( Rss response ) { // Caching FeedDatabase . getInstance ( MainActivity . this ). 
                                       synchronizeEntries ( response . getChannel (). getItems ()); // Initial data load ...
                       
                       
                         
                           
                             
                               
                               
                               
                               new LoadData (). execute (); } }, new Response . ErrorListener () { @Override public void onErrorResponse ( VolleyError error ) { Log . d ( TAG , "Error volley" + mistake . getMessage ()); } } ) ); } else { Log . i ( TAG , "The internet connection is not available" 
                           
                       
                         
                           
                             
                                 
                           
                       
               
       
     
        ); 
       adapter = new FeedAdapter ( this , FeedDatabase . getInstance ( this ). getEntries (), SimpleCursorAdapter . FLAG_REGISTER_CONTENT_OBSERVER ); 
       listView . setAdapter ( adapter ); }  
               
               
               
   

} Step # 8: View Entries in the Activity Detail Now it only remains to use explicit intents to visualize the content of the URL of the item that the user presses in the list. This means that when assigning the OnItemClickListener listener to the list we must use the startActivity () method, where we will add the url of the selected entry as an extra value. Let's see how to do it:

// Register listener from the listView list . setOnItemClickListener ( new AdapterView . OnItemClickListener () { @Override public void onItemClick ( AdapterView <?> parent , View view , int position , long id ) { Cursor c = ( Cursor ) adapter . getItem ( position );



       // Get url of the selected entry String url = c . getString ( c . getColumnIndex ( ScriptDatabase . ColumnEntradas . URL ));
       
       // New intent explicit Intent i = new Intent ( MainActivity . This , DetailActivity . Class );
          
       // Set url 
       i . putExtra ( "url-extra" , url );
       // Start activity 
       startActivity ( i ); } });
   

As you know, getItem () allows you to obtain the instance of the data source that has been selected by the user. When doing a Cursor casting we can get the URL column and thus build our intent successfully.

Now simply retrieve the value of the url from the side of the detail activity and load the contents of the url on the WebView:

DetailActivity.java

import android . os . Bundle ; import android . support . v7 . app . AppCompatActivity ; import android . webkit . WebView ; import android . webkit . WebViewClient ;



import com . herprogramacin . hermosaprogramacion . R ;

/ **

* Created by Beautiful Programming
*
* Activity that shows the detail of an article in the feed
* /

public class DetailActivity extends AppCompatActivity {

   / *
   Debug label
    * / private static final String TAG = DetailActivity . class . getSimpleName ();
       
   @Override protected void onCreate ( Bundle savedInstanceState ) { super . onCreate ( savedInstanceState ); 
       setContentView ( R . layout . activity_detail );
     
       
       // Dehabilitar title of the activity if ( getSupportActionBar ()! = Null ) 
               getSupportActionBar (). setDisplayShowTitleEnabled ( false );
       


       // Retrieve url String urlExtra = getIntent (). getStringExtra ( "url-extra" );
       
       // Get WebView WebView webview = ( WebView ) findViewById ( R . Id . Webview );
        
       // Enable Javascript in 
       webview rendering . getSettings (). setJavaScriptEnabled ( true );
       // Transmit 
       webview locally . setWebViewClient ( new WebViewClient ()); 
       // Load the contents of the 
       webview url . loadUrl ( urlExtra );


   }

} Finally, execute the Feedky project and test its operation: Reader Feeds Application on Android

Conclusions Remember that there are two widely spread standards for the dissemination of web content called RSS and Atom . Depending on the source of origin, the correct labels for parsing must also be chosen.

Use the Simple Framework library to save XML parsing time. Although there are alternatives such as XmlPullParser and SAXParser own Android, these require a description of low level, more maintenance and complex reuse.

With Volley you can create a custom request to parse and deserialize XML flows with amazing simplicity.

Although this article did not implement an observer pattern synchronization, it is necessary to use classes such as SyncAdapter, ContentProvider and Service to complete the process (topics that will be explained in future articles).

About This Tutorial