Building News Apps Using XML UI

Building a news app involves efficiently presenting timely information to users. While modern UI frameworks like Jetpack Compose offer a declarative approach, many legacy and existing projects still rely on XML for defining their user interfaces. This blog post will guide you through building a news app using XML UI in Android, covering essential components, data fetching, parsing, and UI updates.

Understanding XML UI in Android

XML (Extensible Markup Language) is used in Android to define the structure and elements of a user interface. XML layouts are crucial for structuring activities, fragments, and custom views. While it may be older compared to Compose, XML remains a relevant skill, especially for maintaining existing projects or working on certain types of apps.

Project Setup

Before diving into the code, set up a new Android project in Android Studio. Make sure you have the Android SDK and necessary build tools installed.

Step 1: Create a New Project

  • Open Android Studio and select “Create New Project”.
  • Choose “Empty Activity” as your project template.
  • Name your project (e.g., “NewsAppXML”) and configure the minimum SDK as needed.

Step 2: Add Dependencies

Update your build.gradle file to include the necessary dependencies for networking, XML parsing, and image loading.

dependencies {
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
    implementation 'com.google.android.material:material:1.11.0'
    implementation 'com.squareup.okhttp3:okhttp:4.11.0'
    implementation 'com.github.bumptech.glide:glide:4.16.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
    implementation 'androidx.recyclerview:recyclerview:1.3.2'
}

Explanation of Dependencies:

  • appcompat: Support library for older Android versions.
  • swiperefreshlayout: For pull-to-refresh functionality.
  • material: Material Design components.
  • okhttp: Efficient HTTP client for fetching data.
  • glide: Image loading and caching library.
  • recyclerview: Displaying lists of news articles efficiently.

Step 3: Add Internet Permission

Add internet permission in your AndroidManifest.xml file:

Designing the UI with XML

The UI for the news app will consist of a RecyclerView to display a list of news articles. Let’s create the layout files.

Step 1: Create activity_main.xml

This layout will contain a SwipeRefreshLayout wrapping a RecyclerView to allow users to refresh the news feed.


<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/swipeRefreshLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        tools:listitem="@layout/item_news"/>

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

Step 2: Create item_news.xml

This layout defines the structure for each news article item in the RecyclerView.


<androidx.cardview.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    app:cardCornerRadius="4dp"
    app:cardElevation="2dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/newsImageView"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:scaleType="centerCrop"
            android:contentDescription="News Image"/>

        <TextView
            android:id="@+id/newsTitleTextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:textSize="16sp"
            android:textStyle="bold"/>

        <TextView
            android:id="@+id/newsDescriptionTextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:textSize="14sp"/>

    </LinearLayout>

</androidx.cardview.widget.CardView>

Fetching News Data

We’ll use OkHttp to fetch news data from an XML API. Here’s an example API endpoint (replace with a real news XML feed):

https://example.com/newsfeed.xml

Step 1: Create a Data Model

Create a NewsItem data class to represent each news article.

data class NewsItem(
    val title: String,
    val description: String,
    val imageUrl: String
)

Step 2: Fetch Data

Implement a function to fetch the XML data using OkHttp.

import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.IOException

object NewsApi {
    private val client = OkHttpClient()

    fun fetchNewsData(url: String): String? {
        val request = Request.Builder().url(url).build()
        return try {
            val response = client.newCall(request).execute()
            response.body?.string()
        } catch (e: IOException) {
            e.printStackTrace()
            null
        }
    }
}

Parsing XML Data

Once you have the XML data, parse it to extract the news articles. Android does not provide a built-in XML parsing library. Thus, the XML must be extracted using other third party libraries such as jsoup or xmlPullParser.

Option 1: Parsing the News Data With Jsoup

implementation 'org.jsoup:jsoup:1.17.2'

import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.select.Elements

object XmlParser {

    fun parseNewsXml(xmlString: String?): List {
        val newsItems = mutableListOf()
        try {
            val document: Document = Jsoup.parse(xmlString, "UTF-8")
            val articles: Elements = document.select("item") // or whatever the element tag is

            for (article in articles) {
                val title = article.select("title").text()
                val description = article.select("description").text()
                val imageUrl = article.select("enclosure[url]").attr("url")
                newsItems.add(NewsItem(title, description, imageUrl))
            }
        } catch (e: Exception) {
            e.printStackTrace()
            // Handle parsing exceptions
        }
        return newsItems
    }
}

Option 2: Parsing the News Data With xmlPullParser


import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserFactory
import java.io.StringReader

object XmlParser {

    fun parseNewsXml(xmlString: String?): List {
        val newsItems = mutableListOf()
        try {
            val factory = XmlPullParserFactory.newInstance()
            factory.isNamespaceAware = true
            val parser = factory.newPullParser()

            parser.setInput(StringReader(xmlString))

            var eventType = parser.eventType
            var currentNewsItem: NewsItem? = null

            while (eventType != XmlPullParser.END_DOCUMENT) {
                when (eventType) {
                    XmlPullParser.START_TAG -> {
                        when (parser.name) {
                            "item" -> currentNewsItem = NewsItem("", "", "") // Initialize empty item
                            "title" -> currentNewsItem = currentNewsItem?.copy(title = parser.nextText())
                            "description" -> currentNewsItem = currentNewsItem?.copy(description = parser.nextText())
                            "enclosure" -> {
                                if (parser.getAttributeValue(null, "url") != null) {
                                    currentNewsItem = currentNewsItem?.copy(imageUrl = parser.getAttributeValue(null, "url"))
                                }
                            }
                        }
                    }
                    XmlPullParser.END_TAG -> {
                        if (parser.name == "item" && currentNewsItem != null) {
                            newsItems.add(currentNewsItem)
                            currentNewsItem = null // Reset for the next item
                        }
                    }
                }
                eventType = parser.next()
            }
        } catch (e: Exception) {
            e.printStackTrace()
            // Handle parsing exceptions
        }
        return newsItems
    }
}

Displaying News Articles in RecyclerView

Create an adapter for the RecyclerView to display the parsed news articles.

Step 1: Create NewsAdapter

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide

class NewsAdapter(private val newsItems: List) :
    RecyclerView.Adapter() {

    class NewsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val newsImageView: ImageView = itemView.findViewById(R.id.newsImageView)
        val newsTitleTextView: TextView = itemView.findViewById(R.id.newsTitleTextView)
        val newsDescriptionTextView: TextView = itemView.findViewById(R.id.newsDescriptionTextView)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_news, parent, false)
        return NewsViewHolder(view)
    }

    override fun onBindViewHolder(holder: NewsViewHolder, position: Int) {
        val newsItem = newsItems[position]
        holder.newsTitleTextView.text = newsItem.title
        holder.newsDescriptionTextView.text = newsItem.description
        Glide.with(holder.itemView.context)
            .load(newsItem.imageUrl)
            .into(holder.newsImageView)
    }

    override fun getItemCount(): Int = newsItems.size
}

Step 2: Update MainActivity


import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import kotlinx.coroutines.*

class MainActivity : AppCompatActivity() {

    private lateinit var recyclerView: RecyclerView
    private lateinit var swipeRefreshLayout: SwipeRefreshLayout
    private lateinit var newsAdapter: NewsAdapter
    private val newsItems = mutableListOf()

    private val newsUrl = "YOUR_NEWS_XML_FEED_URL" // Replace with your XML feed URL

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        recyclerView = findViewById(R.id.recyclerView)
        swipeRefreshLayout = findViewById(R.id.swipeRefreshLayout)

        recyclerView.layoutManager = LinearLayoutManager(this)
        newsAdapter = NewsAdapter(newsItems)
        recyclerView.adapter = newsAdapter

        swipeRefreshLayout.setOnRefreshListener {
            loadNewsData()
        }

        loadNewsData()
    }

    private fun loadNewsData() {
        swipeRefreshLayout.isRefreshing = true
        CoroutineScope(Dispatchers.IO).launch {
            val xmlData = NewsApi.fetchNewsData(newsUrl)
            val parsedNews = XmlParser.parseNewsXml(xmlData)

            withContext(Dispatchers.Main) {
                newsItems.clear()
                newsItems.addAll(parsedNews)
                newsAdapter.notifyDataSetChanged()
                swipeRefreshLayout.isRefreshing = false
            }
        }
    }
}

Error Handling

Network Errors


try {
    val response = client.newCall(request).execute()
    if (!response.isSuccessful) {
        throw IOException("Network error: ${response.code}")
    }
    response.body?.string()
} catch (e: IOException) {
    Log.e("NewsApi", "Error fetching data: ${e.message}")
    null
}

Parsing Exceptions


try {
    // XML Parsing Logic
} catch (e: XmlPullParserException) {
    Log.e("XmlParser", "XML parsing error: ${e.message}")
    emptyList()
} catch (e: IOException) {
    Log.e("XmlParser", "IO error during parsing: ${e.message}")
    emptyList()
} catch (e: Exception) {
    Log.e("XmlParser", "Generic parsing error: ${e.message}")
    emptyList()
}

UI Thread Handling


withContext(Dispatchers.Main) {
    try {
        newsItems.clear()
        newsItems.addAll(parsedNews)
        newsAdapter.notifyDataSetChanged()
    } catch (e: Exception) {
        Log.e("MainActivity", "UI update error: ${e.message}")
        // Display a user-friendly error message using a Toast or Snackbar
    } finally {
        swipeRefreshLayout.isRefreshing = false
    }
}

Conclusion

Building a news app using XML UI in Android involves designing layouts, fetching data from an XML feed, parsing the data, and displaying it in a RecyclerView. While this approach might seem traditional compared to modern declarative UI frameworks, it is still highly relevant for many existing projects and legacy systems. By following this guide, you can create a robust news app using XML while maintaining performance and readability.