Developing a News Aggregator Mobile Application
News aggregator — not just RSS feed. Personalized stream from dozens of sources, offline reading, instant search, breaking news push notifications. Main technical challenge — responsive feed with hundreds of sources and thousands of cached items without draining battery.
Architecture: Where News Comes From
Three content aggregation approaches:
-
Own crawler on backend — parses RSS/Atom feeds by schedule (cron), stores normalized articles in DB. Mobile client works with your API only. Plus: format control, caching, deduplication. Minus: needs backend infrastructure.
-
NewsAPI / GNews / Currents API — ready aggregators with REST API. Quick start, but paid for commercial use, limited source set.
-
Hybrid — own crawler for priority sources + third-party API as backup.
For production app with real users — first or third option.
Personalized Feed: Client Architecture
Feed built on user subscriptions (sources, tags, categories) + ranking algorithm.
Client — paginated list with caching via Room (Android) or Core Data (iOS). Strategy: on app open show cached data instantly, fetch fresh in parallel.
// Android — Repository with NetworkBoundResource pattern
class NewsRepository(
private val newsApi: NewsApi,
private val newsDao: NewsDao
) {
fun getFeed(userId: String): Flow<Resource<List<Article>>> = networkBoundResource(
query = { newsDao.getArticles(userId) },
fetch = { newsApi.getFeed(userId, page = 1) },
saveFetchResult = { articles ->
newsDao.deleteOldArticles(olderThan = System.currentTimeMillis() - 7.days)
newsDao.insertArticles(articles)
},
shouldFetch = { cached -> cached.isEmpty() || cached.first().isStale() }
)
}
Pagination — Paging 3 on Android, custom cursor-based on iOS. Offset-based (page=2&per_page=20) breaks with new articles at top — user sees duplicates. Cursor-based (after_id=article_12345) avoids this.
Offline Reading
Works via two mechanisms:
- Auto cache feed in Room/Core Data (last N articles).
- Manual save — user explicitly adds to "Read Later".
Full offline reading needs storing not just metadata but HTML content. Either DB storage (blob) or filesystem. HTML parsed, rendered via WKWebView (iOS) or WebView with disabled network (Android).
// iOS — save content for offline
func saveForOffline(article: Article) async throws {
let content = try await contentParser.fetchFullText(url: article.url)
let sanitizedHTML = HTMLSanitizer.sanitize(content, baseURL: article.url)
let offlineArticle = OfflineArticle(
id: article.id,
title: article.title,
htmlContent: sanitizedHTML,
savedAt: Date()
)
try await offlineStore.save(offlineArticle)
}
Breaking News Push Notifications
Breaking news must arrive within minutes of publish. Scheme:
- Backend crawler detects article with
breakingtag or high engagement velocity. - Determines which users relevant (source/topic subscriptions).
- Sends push via FCM/APNs with
priority: high.
Client — deep link in push opens specific article:
// Android — handle deep link from push
override fun onMessageReceived(message: RemoteMessage) {
val articleId = message.data["article_id"] ?: return
val intent = Intent(this, ArticleActivity::class.java).apply {
putExtra("article_id", articleId)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
// Show notification with PendingIntent
}
Search
Instant search via local cache via Room FTS (Full Text Search):
@Fts4(contentEntity = ArticleEntity::class)
@Entity(tableName = "articles_fts")
data class ArticleFts(
@PrimaryKey @ColumnInfo(name = "rowid") val rowid: Int = 0,
val title: String,
val description: String
)
@Query("SELECT * FROM articles INNER JOIN articles_fts ON articles.rowid = articles_fts.rowid WHERE articles_fts MATCH :query")
fun searchArticles(query: String): Flow<List<ArticleEntity>>
FTS4/FTS5 in SQLite — search all text in milliseconds even on 50,000 articles.
Common Issues
Deduplication. One news from 5 sources — 5 different URLs, same content. Solution — MinHash or SimHash on backend for text similarity. Client displays deduplicated result.
Images in feed. Lazy load via Glide (Android) or Kingfisher (iOS). But 50 images on fast scroll — 50 parallel requests. Need prefetch with prioritization: RecyclerView.Adapter + GlidePrefetcher on Android, UITableViewDataSourcePrefetching on iOS.
Reading time. Show "5 min read" — count on backend by word count, cache in metadata.
Stack and Timeline
| Component | iOS | Android |
|---|---|---|
| List | UICollectionView + DiffableDataSource | RecyclerView + ListAdapter |
| DB | Core Data or SQLite.swift | Room + FTS5 |
| Images | Kingfisher | Glide |
| Network | URLSession + Combine | Retrofit + Coroutines |
| Push | APNs via OneSignal | FCM via OneSignal |
News aggregator MVP (feed, categories, offline, search, breaking push) — 6–10 weeks depending on platforms and backend complexity.







