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.