Practical Development of News Applications Based on HarmonyOS Next: Building an Intelligent Information Platform from Scratch
1. Project Overview and Environment Preparation
In this era of information explosion, developing a news application based on HarmonyOS Next holds significant practical importance. We will leverage various services provided by AppGallery Connect to create a comprehensive application with core features such as news browsing, personalized recommendations, and bookmark sharing.
Development Environment Requirements:
- DevEco Studio 3.1 or later
- HarmonyOS SDK API 9+
- Registered Huawei Developer Account
Project Initialization Steps:
- Create a new project in DevEco Studio using the "Application" template
- Create a corresponding project in the AppGallery Connect console
- Configure the application's package name and signing certificate
// Basic configuration example in app.json5
{
"app": {
"bundleName": "com.example.newshub",
"vendor": "example",
"versionCode": 1,
"versionName": "1.0.0",
"icon": "$media:app_icon",
"label": "$string:app_name"
}
}
2. News Data Acquisition and Display
2.1 Network Requests and Data Parsing
The core of a news application lies in obtaining real-time news data. We will use ArkTS's networking capabilities in conjunction with AppGallery Connect's cloud functions.
// Network request utility class
import http from '@ohos.net.http';
class NewsService {
private httpRequest = http.createHttp();
// Fetch news list
async fetchNewsList(category: string, page: number): Promise<NewsItem[]> {
let url = `https://your-api-endpoint.com/news?category=${category}&page=${page}`;
try {
let response = await this.httpRequest.request(
url,
{ method: 'GET' }
);
if (response.responseCode === 200) {
let result = JSON.parse(response.result.toString());
return result.data as NewsItem[];
} else {
console.error(`Request failed, status code: ${response.responseCode}`);
return [];
}
} catch (error) {
console.error(`Request error: ${error.message}`);
return [];
}
}
}
// News data model
interface NewsItem {
id: string;
title: string;
summary: string;
content: string;
imageUrl: string;
publishTime: string;
source: string;
category: string;
}
2.2 Implementing the News List Interface
Use ArkUI's List component to display the news list and implement pull-to-refresh and infinite scrolling functionalities.
@Entry
@Component
struct NewsListPage {
@State newsList: NewsItem[] = [];
@State currentCategory: string = 'technology';
@State currentPage: number = 1;
@State isLoading: boolean = false;
@State isRefreshing: boolean = false;
private newsService = new NewsService();
// Lifecycle function - load data when page appears
onPageShow() {
this.loadData();
}
// Load data
async loadData() {
this.isLoading = true;
let newData = await this.newsService.fetchNewsList(this.currentCategory, this.currentPage);
this.newsList = [...this.newsList, ...newData];
this.isLoading = false;
}
// Pull-to-refresh
async onRefresh() {
this.isRefreshing = true;
this.currentPage = 1;
this.newsList = [];
await this.loadData();
this.isRefreshing = false;
}
// Infinite scroll
async loadMore() {
if (this.isLoading) return;
this.currentPage++;
await this.loadData();
}
build() {
Column() {
// Category selector
CategoryTabs({
categories: ['technology', 'business', 'sports', 'entertainment'],
currentCategory: this.currentCategory,
onCategoryChange: (category) => {
this.currentCategory = category;
this.onRefresh();
}
})
// News list
List({ space: 10 }) {
ForEach(this.newsList, (item: NewsItem) => {
ListItem() {
NewsCard({ newsItem: item })
.onClick(() => {
router.pushUrl({ url: 'pages/DetailPage', params: { newsId: item.id } });
})
}
}, (item: NewsItem) => item.id)
// Loading indicator
if (this.isLoading && !this.isRefreshing) {
ListItem() {
LoadingIndicator()
.height(60)
.width('100%')
}
}
}
.width('100%')
.height('100%')
.onScrollIndex((startIndex: number) => {
// Automatically load more when scrolling to bottom
if (startIndex >= this.newsList.length - 3) {
this.loadMore();
}
})
}
.width('100%')
.height('100%')
}
}
3. User Authentication and Personalized Features
3.1 Integrating AppGallery Connect Authentication Service
import { agconnect } from '@hw-agconnect/api-ohos';
import '@hw-agconnect/auth-ohos';
class AuthService {
// User login
async login(email: string, password: string): Promise<boolean> {
try {
await agconnect.auth().signIn(email, password);
return true;
} catch (error) {
console.error('Login failed:', error);
return false;
}
}
// User registration
async register(email: string, password: string): Promise<boolean> {
try {
await agconnect.auth().createUser(email, password);
return true;
} catch (error) {
console.error('Registration failed:', error);
return false;
}
}
// Get current user
getCurrentUser(): agconnect.auth.AuthUser | null {
return agconnect.auth().currentUser;
}
// User logout
async logout(): Promise<void> {
await agconnect.auth().signOut();
}
}
3.2 Implementing News Bookmarking
// Bookmark service
class FavoriteService {
private cloudDB: agconnect.cloudDB.CloudDBZone;
constructor() {
const config = {
name: 'FavoriteZone',
persistenceEnabled: true
};
this.cloudDB = agconnect.cloudDB.CloudDBZoneWrapper.openCloudDBZone(config);
}
// Add bookmark
async addFavorite(newsId: string, userId: string): Promise<boolean> {
try {
const favorite = {
id: `${userId}_${newsId}`,
newsId,
userId,
createTime: new Date().getTime()
};
await this.cloudDB.executeUpsert('Favorite', [favorite]);
return true;
} catch (error) {
console.error('Bookmark failed:', error);
return false;
}
}
// Remove bookmark
async removeFavorite(newsId: string, userId: string): Promise<boolean> {
try {
const query = agconnect.cloudDB.CloudDBZoneQuery.where('Favorite')
.equalTo('id', `${userId}_${newsId}`);
await this.cloudDB.executeDelete(query);
return true;
} catch (error) {
console.error('Unbookmark failed:', error);
return false;
}
}
// Check if bookmarked
async isFavorite(newsId: string, userId: string): Promise<boolean> {
const query = agconnect.cloudDB.CloudDBZoneQuery.where('Favorite')
.equalTo('id', `${userId}_${newsId}`);
const result = await this.cloudDB.executeQuery(query);
return result.length > 0;
}
// Get user bookmarks
async getUserFavorites(userId: string): Promise<Favorite[]> {
const query = agconnect.cloudDB.CloudDBZoneQuery.where('Favorite')
.equalTo('userId', userId)
.orderByDesc('createTime');
return await this.cloudDB.executeQuery(query);
}
}
4. News Details and Interaction Features
4.1 Implementing the News Detail Page
@Entry
@Component
struct NewsDetailPage {
@State newsDetail: NewsDetail | null = null;
@State isFavorite: boolean = false;
@State comments: Comment[] = [];
@State newComment: string = '';
private newsId: string = '';
private newsService = new NewsService();
private favoriteService = new FavoriteService();
private commentService = new CommentService();
private currentUser = agconnect.auth().currentUser;
onPageShow() {
const params = router.getParams();
this.newsId = params?.newsId || '';
this.loadNewsDetail();
this.loadComments();
this.checkFavoriteStatus();
}
async loadNewsDetail() {
this.newsDetail = await this.newsService.fetchNewsDetail(this.newsId);
}
async checkFavoriteStatus() {
if (this.currentUser) {
this.isFavorite = await this.favoriteService.isFavorite(
this.newsId,
this.currentUser.uid
);
}
}
async toggleFavorite() {
if (!this.currentUser) {
prompt.showToast({ message: 'Please login first' });
return;
}
if (this.isFavorite) {
await this.favoriteService.removeFavorite(this.newsId, this.currentUser.uid);
} else {
await this.favoriteService.addFavorite(this.newsId, this.currentUser.uid);
}
this.isFavorite = !this.isFavorite;
}
async loadComments() {
this.comments = await this.commentService.getCommentsForNews(this.newsId);
}
async submitComment() {
if (!this.currentUser || !this.newComment.trim()) return;
await this.commentService.addComment({
newsId: this.newsId,
userId: this.currentUser.uid,
userName: this.currentUser.displayName || 'Anonymous',
content: this.newComment,
createTime: new Date().getTime()
});
this.newComment = '';
await this.loadComments();
}
build() {
Column() {
if (this.newsDetail) {
Scroll() {
Column({ space: 15 }) {
// News title section
Text(this.newsDetail.title)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 10 })
// News source and time
Row() {
Text(this.newsDetail.source)
.fontSize(14)
.fontColor('#666')
Text(this.newsDetail.publishTime)
.fontSize(14)
.fontColor('#666')
.margin({ left: 15 })
}
.justifyContent(FlexAlign.Start)
.width('100%')
// News image
Image(this.newsDetail.imageUrl)
.width('100%')
.aspectRatio(1.78) // 16:9 ratio
.objectFit(ImageFit.Cover)
// News content
Text(this.newsDetail.content)
.fontSize(16)
.lineHeight(24)
.width('100%')
// Action buttons
Row({ space: 20 }) {
Button(this.isFavorite ? 'Bookmarked' : 'Bookmark')
.onClick(() => this.toggleFavorite())
Button('Share')
.onClick(() => {
// Implement share functionality
})
}
.margin({ top: 20, bottom: 20 })
// Comments section
Text('Comments')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 10 })
// Comment input
if (this.currentUser) {
Row() {
TextInput({ text: this.newComment, placeholder: 'Write your comment...' })
.onChange((value: string) => {
this.newComment = value;
})
.layoutWeight(1)
Button('Send')
.onClick(() => this.submitComment())
}
.width('100%')
.height(40)
}
// Comments list
ForEach(this.comments, (comment: Comment) => {
CommentItem({ comment })
}, (comment: Comment) => comment.id)
}
.padding(15)
}
} else {
LoadingIndicator()
.width(50)
.height(50)
}
}
.width('100%')
.height('100%')
}
}
5. Personalized Recommendations and Data Analysis
5.1 User Behavior-Based Recommendation System
class RecommendationService {
private cloudDB: agconnect.cloudDB.CloudDBZone;
constructor() {
const config = {
name: 'RecommendationZone',
persistenceEnabled: true
};
this.cloudDB = agconnect.cloudDB.CloudDBZoneWrapper.openCloudDBZone(config);
}
// Record user viewing behavior
async recordView(newsId: string, userId: string, duration: number) {
const viewRecord = {
id: `${userId}_${newsId}_${Date.now()}`,
newsId,
userId,
duration,
timestamp: new Date().getTime()
};
await this.cloudDB.executeUpsert('ViewRecord', [viewRecord]);
}
// Get personalized recommendations
async getPersonalizedRecommendations(userId: string, count: number = 10): Promise<NewsItem[]> {
// Can integrate more sophisticated recommendation algorithms here
const query = agconnect.cloudDB.CloudDBZoneQuery.where('ViewRecord')
.equalTo('userId', userId)
.orderByDesc('timestamp')
.limit(5);
const recentViews = await this.cloudDB.executeQuery(query);
const viewedCategories = new Set<string>();
recentViews.forEach(view => {
viewedCategories.add(view.news.category);
});
// Simulate category-based recommendations
return await this.newsService.fetchNewsByCategories(
Array.from(viewedCategories),
count
);
}
}
5.2 Integrating AppGallery Connect Analytics
import '@hw-agconnect/analytics-ohos';
class AnalyticsService {
private analytics = agconnect.analytics();
// Record news view event
logNewsView(newsId: string, category: string, duration: number) {
this.analytics.logEvent('news_view', {
news_id: newsId,
category: category,
duration: duration,
timestamp: new Date().getTime()
});
}
// Record user interaction event
logUserInteraction(eventType: string, newsId: string) {
this.analytics.logEvent(eventType, {
news_id: newsId,
user_id: agconnect.auth().currentUser?.uid || 'anonymous',
timestamp: new Date().getTime()
});
}
// Set user properties
setUserProperties(properties: Record<string, string>) {
this.analytics.setUserProperties(properties);
}
}
6. Application Publishing and Continuous Optimization
6.1 Application Packaging and Publishing
- Configure release signing: Generate release certificates in AppGallery Connect
- Build release version: In DevEco Studio, execute "Build > Build HAP(s)/APP(s) > Build APP"
- Submit for review: Upload the generated .app file to AppGallery Connect
6.2 Performance Optimization Recommendations
- Image loading optimization:
// Use image caching and lazy loading
Image(this.newsItem.imageUrl)
.width('100%')
.height(200)
.objectFit(ImageFit.Cover)
.syncLoad(false) // Enable async loading
.cached(true) // Enable caching
- List rendering optimization:
List() {
ForEach(this.newsList, (item: NewsItem) => {
ListItem() {
NewsCard({ newsItem: item })
}
}, (item: NewsItem) => item.id)
}
.recycle(true) // Enable list item recycling
.edgeEffect(EdgeEffect.None) // Disable edge effects for better performance
- Network request optimization:
// Use caching strategy to reduce duplicate requests
async fetchNewsList(category: string, page: number): Promise<NewsItem[]> {
let cacheKey = `news_${category}_${page}`;
let cachedData = this.cache.get(cacheKey);
if (cachedData && Date.now() - cachedData.timestamp < 300000) { // 5-minute cache
return cachedData.data;
}
// Actual network request...
}
6.3 User Feedback and Iteration
- Integrate AppGallery Connect feedback service:
import '@hw-agconnect/feedback-ohos';
class FeedbackService {
private feedback = agconnect.feedback();
// Submit user feedback
async submitFeedback(content: string, contact?: string) {
await this.feedback.submit({
content: content,
contact: contact || '',
type: 'suggestion'
});
}
// Check for new replies
async checkForReplies() {
const replies = await this.feedback.getReplies();
return replies.filter(reply => !reply.isRead);
}
}
- Implement A/B testing with Remote Configuration:
import '@hw-agconnect/remoteconfig-ohos';
class ABTestService {
private remoteConfig = agconnect.remoteConfig();
constructor() {
this.remoteConfig.applyDefault({
'homepage_layout': 'list',
'detail_font_size': '16',
'enable_dark_mode': true
});
}
async fetchConfig() {
await this.remoteConfig.fetch(3600); // 1-hour expiration
await this.remoteConfig.apply();
}
getHomepageLayout(): string {
return this.remoteConfig.getValue('homepage_layout').asString();
}
}
7. Conclusion and Future Prospects
Through this tutorial, we have fully implemented a news application based on HarmonyOS Next, covering the entire process from data acquisition and user interaction to data analysis. HarmonyOS Next, combined with the rich services provided by AppGallery Connect, offers powerful support for developers to build high-quality applications.
Future Expansion Directions:
- Incorporate HarmonyOS's distributed capabilities for cross-device news reading experiences
- Add video news support using HarmonyOS's multimedia capabilities
- Develop smartwatch applications to expand usage scenarios
- Integrate smarter recommendation algorithms to enhance user experience
News application development is an ongoing iterative process. It is recommended to regularly analyze user behavior through AppGallery Connect's analytics data to continuously optimize product features and user experience.
Top comments (0)