Chúng ta sống trong một thế giới luôn thay đổi không ngừng. Vì vậy, nếu bạn muốn cập nhật những gì đang xảy ra, bạn sẽ cần một ứng dụng Tin tức tốt.
Để giúp bạn học công nghệ thú vị và bắt kịp xu hướng, trong bài viết này chúng ta sẽ xây dựng một Ứng dụng Tin tức cho Android sử dụng React Native. Ứng dụng sẽ lấy tiêu đề từ nhiều kênh tin tức khác nhau và hiển thị theo danh mục.

Đây là giao diện ứng dụng khi hoàn tất. Hãy bắt đầu ngay!
Cách Cài Đặt Expo
Expo là gì? Expo là một framework giúp bạn xây dựng và triển khai ứng dụng React Native nhanh chóng và dễ dàng.
Hãy cài đặt nó.
npm install --global expo-cliChạy lệnh này trong terminal để cài đặt Expo CLI. Ở đây chúng ta dùng --global để cài đặt toàn cục.
Sau khi cài đặt xong, chúng ta cần tạo một dự án Expo.
expo init News-ApplicationSử dụng lệnh trên để khởi tạo dự án. Nó sẽ hỏi một vài câu hỏi như tên ứng dụng, có muốn dùng TypeScript, hay bắt đầu với dự án trống. Chỉ cần chọn blank và nhấn Enter.
Sau đó, nó sẽ tải tất cả các gói và phụ thuộc vào thư mục.
Bây giờ, di chuyển vào thư mục dự án. Để chạy ứng dụng, gõ expo start. Nó sẽ mở công cụ phát triển trong trình duyệt.

Ở đây bạn sẽ thấy nhiều tùy chọn bên trái, như chạy trên thiết bị Android/emulator, hoặc iOS simulator. Chúng ta sẽ chạy trên Web Browser, nên hãy chọn tùy chọn Run in Web Browser.
import { StatusBar } from 'expo-status-bar'; import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; export default function App() { return ( <View style={styles.container}> <Text>Open up App.js to start working on your app!</Text> <StatusBar style="auto" /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center', }, }); Đây là file App.js chứa boilerplate mặc định.

Bây giờ ứng dụng đang chạy.
Tạo Các Màn Hình Khác Nhau Với React Navigation
Bây giờ, hãy tạo các màn hình khác nhau cho ứng dụng. Chúng ta sẽ dùng React Navigation. Hãy cài đặt nó.
Truy cập https://reactnavigation.org/ và nhấn Read Docs để mở trang tài liệu.
Cài đặt React Navigation bằng lệnh:
npm install @react-navigation/native expo install react-native-screens react-native-safe-area-contextReact Navigation đã được cài đặt.
Chúng ta sẽ dùng bottomTabNavigator. Từ menu trái, chọn API Reference, sau đó Navigators, rồi Bottom Tabs.

Cài đặt Bottom Tabs:
npm install @react-navigation/bottom-tabsTrong file App.js, import Bottom Tabs để sử dụng:
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; Import các màn hình Tab:
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { NavigationContainer } from '@react-navigation/native'; const Tab = createBottomTabNavigator(); function MyTabs() { return ( <Tab.Navigator> <Tab.Screen name="Home" component={HomeScreen} /> <Tab.Screen name="Settings" component={SettingsScreen} /> </Tab.Navigator> ); }Đây là cách tạo Bottom Tabs.
Trong trường hợp của chúng ta, làm như sau:
<Tab.Navigator> <Tab.Screen name="All" component={All} /> <Tab.Screen name="Business" component={Business} /> <Tab.Screen name="Health" component={HealthScreen} /> <Tab.Screen name="Sports" component={SportsScreen} /> <Tab.Screen name="Tech" component={TechScreen} /> </Tab.Navigator>Cần tạo màn hình cho các tab: All News, Business News, Sports News, Health News, Tech News. Cũng tạo một component trong dự án cho mỗi màn hình.
Cần wrap TabNavigator vào NavigationContainer:
<NavigationContainer> <Tab.Navigator> <Tab.Screen name="All" component={All} /> <Tab.Screen name="Business" component={Business} /> <Tab.Screen name="Health" component={HealthScreen} /> <Tab.Screen name="Sports" component={SportsScreen} /> <Tab.Screen name="Tech" component={TechScreen} /> </Tab.Navigator> </NavigationContainer>Import các component ở đầu file:
import All from './screens/All'; import Business from './screens/Business'; import HealthScreen from './screens/Health'; import SportsScreen from './screens/Sports'; import TechScreen from './screens/Tech';Gộp toàn bộ code:
import React from 'react'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { NavigationContainer } from '@react-navigation/native'; import All from './screens/All'; import Business from './screens/Business'; import HealthScreen from './screens/Health'; import SportsScreen from './screens/Sports'; import TechScreen from './screens/Tech'; const Tab = createBottomTabNavigator(); export default function App() { return ( <NavigationContainer> <Tab.Navigator> <Tab.Screen name="All" component={All} /> <Tab.Screen name="Business" component={Business} /> <Tab.Screen name="Health" component={HealthScreen} /> <Tab.Screen name="Sports" component={SportsScreen} /> <Tab.Screen name="Tech" component={TechScreen} /> </Tab.Navigator> </NavigationContainer> ); } Kết quả sẽ như sau:

Chúng ta có 5 màn hình cho All, Business, Health, Sports, Tech.
Bây giờ cần điều chỉnh: thay đổi icon cho bottom tabs.
Cần thư viện icon, dùng react-native-elements.
npm install react-native-elementsGói này có nhiều lựa chọn icon.

Thêm icon vào Bottom Tab Navigator:
<Tab.Screen name="All" component={All} options={{ tabBarIcon: (props) => ( <Icon type='feather' name='home' color={props.color} /> ), }} />Đã thêm icon "home" cho trang chủ, loại feather.

Lặp lại cho các tab còn lại:
<Tab.Navigator> <Tab.Screen name="All" component={All} options={{ tabBarIcon: (props) => ( <Icon type='feather' name='home' color={props.color} /> ), }} /> <Tab.Screen name="Business" component={Business} options={{ tabBarIcon: (props) => ( <Icon type='feather' name='dollar-sign' color={props.color} /> ), }} /> <Tab.Screen name="Health" component={HealthScreen} options={{ tabBarIcon: (props) => ( <Icon type='feather' name='heart' color={props.color} /> ), }} /> <Tab.Screen name="Sports" component={SportsScreen} options={{ tabBarIcon: (props) => ( <Icon type='ionicon' name="tennisball-outline" color={props.color} /> ), }} /> <Tab.Screen name="Tech" component={TechScreen} options={{ tabBarIcon: (props) => ( <Icon type='ionicon' name="hardware-chip-outline" color={props.color} /> ), }} /> </Tab.Navigator>
Các tab/màn hình khác nhau đã hoàn thành, mỗi tab có icon riêng biệt.
Cách Gọi News API
Gọi News API từ https://newsapi.org/

Truy cập trang web và đăng ký. Sẽ nhận được API key.
Cần file config lưu các hằng số News, hãy tạo file:
export const API_KEY = ``; export const endpoint = `https://newsapi.org/v2/top-headlines`; export const country = 'in'Cần API_KEY, endpoint, và mã quốc gia.
Tạo service cho GET API Request.
Tạo file services.js.
Import API_KEY, endpoint, country ở đầu:
import { API_KEY, endpoint, country } from '../config/config';Viết body service:
export async function services(category = 'general') { let articles = await fetch(`${endpoint}?country=${country}&category=${category}`, { headers: { 'X-API-KEY': API_KEY } }); let result = await articles.json(); articles = null; return result.articles; }Fetch dữ liệu tin tức bằng endpoint, thêm country và category. Hàm nhận category mặc định là general. Truyền API_key trong headers.
Chuyển response sang JSON, lưu vào biến result. Trả về articles.
Toàn bộ file tham khảo:
import { API_KEY, endpoint, country } from '../config/config'; export async function services(category = 'general') { let articles = await fetch(`${endpoint}?country=${country}&category=${category}`, { headers: { 'X-API-KEY': API_KEY } }); let result = await articles.json(); articles = null; return result.articles; }Import service vào All.js:
import { services } from '../services/services';Dùng hook useState và useEffect. useEffect gọi service trong All.js, useState tạo state lưu response từ API.
import React, { useEffect, useState } from 'react' import { View } from 'react-native'; import { services } from '../services/services'; export default function All() { const [newsData, setNewsData] = useState([]) useEffect(() => { services('general') .then(data => { setNewsData(data) }) .catch(error => { alert(error) }) }, []) return ( <View> </View> ) } Gọi services trong useEffect, lưu response vào state newsData (mảng). Truyền tham số category là general. Màn hình này fetch tất cả tin tức, dùng category general. Màn hình khác sẽ đổi: health cho Health, sports cho Sports, v.v.
Hiển thị dữ liệu trên giao diện. Cần package Native Base. Cài đặt:
yarn add native-base styled-components styled-system expo install react-native-svg react-native-safe-area-contextTrong All.js, import từ Native Base:
import React, { useEffect, useState } from 'react' import { View, Text } from 'react-native'; import { NativeBaseProvider, FlatList, ScrollView, Divider, Image, Spinner } from 'native-base'; import { services } from '../services/services';Thêm NativeBaseProvider trong return:
return ( <NativeBaseProvider> </NativeBaseProvider> )Thêm Scroll View để cuộn khi dữ liệu vượt quá chiều cao màn hình:
<NativeBaseProvider> <ScrollView height={850}> </ScrollView> </NativeBaseProvider>Thêm FlatList hiển thị dữ liệu tin tức:
<NativeBaseProvider> <ScrollView height={850}> <FlatList data={newsData} renderItem={({ item }) => ( <View> </View> )} keyExtractor={(item) => item.id} /> </ScrollView> </NativeBaseProvider>FlatList nhận prop data là state newsData, trả về item từ renderItems. Tương tự map trong JS, duyệt mảng trả về item. Có keyExtractor để mỗi item duy nhất.
Hiển thị dữ liệu trong View:
<NativeBaseProvider> <ScrollView height={850}> <FlatList data={newsData} renderItem={({ item }) => ( <View> <View> </View> </View> )} keyExtractor={(item) => item.id} /> </ScrollView> </NativeBaseProvider>Thêm text trong child view:
<NativeBaseProvider> <ScrollView height={850}> <FlatList data={newsData} renderItem={({ item }) => ( <View> <View> <Text> {item.title} </Text> <Text> {item.publishedAt} </Text> <Text> {item.description} </Text> </View> </View> )} keyExtractor={(item) => item.id} /> </ScrollView> </NativeBaseProvider>Chứa tiêu đề, mô tả, ngày đăng.

Cách Tạo Kiểu Cho Ứng Dụng Tin Tức React Native
Ứng dụng hiện tại hiển thị tiêu đề, mô tả, ngày. Để đẹp hơn, cần styling.
Import StyleSheet từ React Native:
import { View, Text, StyleSheet } from 'react-native';<View> <View style={styles.newsContainer}> <Text style={styles.title}> {item.title} </Text> <Text style={styles.date}> {item.publishedAt} </Text> <Text style={styles.newsDescription}> {item.description} </Text> </View> </View>Tạo styles:
const styles = StyleSheet.create({ newsContainer: { padding: 10 }, title: { fontSize: 18, marginTop: 10, fontWeight: "600" }, newsDescription: { fontSize: 16, marginTop: 10 }, date: { fontSize: 14 }, });
Giao diện ứng dụng sau styling. Có thể cuộn xuống.
Cần đổi định dạng ngày dễ đọc hơn, vì không hiểu '2021-08-21T11:00:40Z'.
Dùng moment.js, cài đặt:
npm install moment --saveImport trong All.js:
<Text style={styles.date}> {moment(item.publishedAt).format('LLL')} </Text>Format như sau:

Tài liệu moment cung cấp nhiều format. Chọn 'LLL'.

Ngày giờ giờ dễ đọc hơn.
Cần divider tách các bài viết:
<View> <View style={styles.newsContainer}> <Text style={styles.title}> {item.title} </Text> <Text style={styles.date}> {moment(item.publishedAt).format('LLL')} </Text> <Text style={styles.newsDescription}> {item.description} </Text> </View> <Divider my={2} bg="#e0e0e0" /> </View>Sau khi thêm divider, ứng dụng trông như sau:

Các tiêu đề tin tức đã được tách biệt, trông đẹp hơn.
News API có ảnh, hãy thêm:
<View> <View style={styles.newsContainer}> <Image width={550} height={250} resizeMode={"cover"} source={{ uri: item.urlToImage, }} alt="Alternate Text" /> <Text style={styles.title}> {item.title} </Text> <Text style={styles.date}> {moment(item.publishedAt).format('LLL')} </Text> <Text style={styles.newsDescription}> {item.description} </Text> </View> <Divider my={2} bg="#e0e0e0" /> </View>Đã thêm ảnh dùng key urlToImage.

Ảnh tin tức đã hiển thị.
Thêm Spinner Hiển Thị Loading Tin Tức
Thêm spinner hiển thị khi tin tức đang tải.
Tạo điều kiện: nếu độ dài state newsData > 1, hiển thị FlatList chứa tin tức. Ngược lại hiển thị loading spinner.
Nghĩa là: nếu độ dài newsData < 1, state rỗng, API đang gọi. Khi API xong, lưu data vào newsData, độ dài state > 1.
{newsData.length > 1 ? ( <FlatList data={newsData} renderItem={({ item }) => ( <View> <View style={styles.newsContainer}> <Image width={550} height={250} resizeMode={"cover"} source={{ uri: item.urlToImage, }} alt="Alternate Text" /> <Text style={styles.title}> {item.title} </Text> <Text style={styles.date}> {moment(item.publishedAt).format('LLL')} </Text> <Text style={styles.newsDescription}> {item.description} </Text> </View> <Divider my={2} bg="#e0e0e0" /> </View> )} keyExtractor={(item) => item.id} /> ) : ( <View style={styles.spinner}> <Spinner color="danger.400" /> </View> )}Thêm style cho spinner:
spinner: { display: 'flex', justifyContent: 'center', alignItems: 'center', height: 400 }Toàn bộ code All.js tham khảo:
import React, { useEffect, useState } from 'react' import { View, Text, StyleSheet } from 'react-native'; import { NativeBaseProvider, FlatList, ScrollView, Divider, Image, Spinner } from 'native-base'; import { services } from '../services/services'; import moment from 'moment' export default function All() { const [newsData, setNewsData] = useState([]) useEffect(() => { services('general') .then(data => { setNewsData(data) }) .catch(error => { alert(error) }) }, []) return ( <NativeBaseProvider> <ScrollView height={850}> {newsData.length > 1 ? ( <FlatList data={newsData} renderItem={({ item }) => ( <View> <View style={styles.newsContainer}> <Image width={550} height={250} resizeMode={"cover"} source={{ uri: item.urlToImage, }} alt="Alternate Text" /> <Text style={styles.title}> {item.title} </Text> <Text style={styles.date}> {moment(item.publishedAt).format('LLL')} </Text> <Text style={styles.newsDescription}> {item.description} </Text> </View> <Divider my={2} bg="#e0e0e0" /> </View> )} keyExtractor={(item) => item.id} /> ) : ( <View style={styles.spinner}> <Spinner color="danger.400" /> </View> )} </ScrollView> </NativeBaseProvider> ) } const styles = StyleSheet.create({ newsContainer: { padding: 10 }, title: { fontSize: 18, marginTop: 10, fontWeight: "600" }, newsDescription: { fontSize: 16, marginTop: 10 }, date: { fontSize: 14 }, spinner: { display: 'flex', justifyContent: 'center', alignItems: 'center', height: 400 } });Màn hình All.js hoàn tất.
Dùng code tương tự cho các màn hình khác, chỉ đổi tham số trong services trong hook useEffect.
Business screen dùng business, Health dùng health, v.v.
import React, { useEffect, useState } from 'react' import { View, Text, StyleSheet } from 'react-native'; import { NativeBaseProvider, FlatList, ScrollView, Divider, Image, Spinner } from 'native-base'; import { services } from '../services/services'; import moment from 'moment' export default function Business() { const [newsData, setNewsData] = useState([]) useEffect(() => { services('business') .then(data => { setNewsData(data) }) .catch(error => { alert(error) }) }, []) return ( <NativeBaseProvider> <ScrollView height={850}> {newsData.length > 1 ? ( <FlatList data={newsData} renderItem={({ item }) => ( <View> <View style={styles.newsContainer}> <Image width={550} height={250} resizeMode={"cover"} source={{ uri: item.urlToImage, }} alt="Alternate Text" /> <Text style={styles.title}> {item.title} </Text> <Text style={styles.date}> {moment(item.publishedAt).format('LLL')} </Text> <Text style={styles.newsDescription}> {item.description} </Text> </View> <Divider my={2} bg="#e0e0e0" /> </View> )} keyExtractor={(item) => item.id} /> ) : ( <View style={styles.spinner}> <Spinner color="danger.400" /> </View> )} </ScrollView> </NativeBaseProvider> ) } const styles = StyleSheet.create({ newsContainer: { padding: 10 }, title: { fontSize: 18, marginTop: 10, fontWeight: "600" }, newsDescription: { fontSize: 16, marginTop: 10 }, date: { fontSize: 14 }, spinner: { display: 'flex', justifyContent: 'center', alignItems: 'center', height: 400 } });
Cuộn xuống màn hình Business, sẽ thấy tin tức liên quan kinh doanh.
Làm tương tự cho các màn hình khác:
useEffect(() => { services('business') .then(data => { setNewsData(data) }) .catch(error => { alert(error) }) }, [])useEffect(() => { services('health') .then(data => { setNewsData(data) }) .catch(error => { alert(error) }) }, [])useEffect(() => { services('sports') .then(data => { setNewsData(data) }) .catch(error => { alert(error) }) }, [])useEffect(() => { services('technology') .then(data => { setNewsData(data) }) .catch(error => { alert(error) }) }, [])Kết Luận
Chúc mừng! Ứng dụng tin tức đã hoàn thành.
Hãy xây dựng và thử nghiệm thêm. Có rất nhiều thứ bạn có thể làm.
Bạn có thể xem playlist xây dựng ứng dụng tin tức dùng React Native và Native Base trên kênh YouTube của tác giả.
Tải code tại: https://github.com/nishant-666/React-Native-News
Chúc bạn học tập vui vẻ.