Building a news app in react-native using Expo and Express on the Node.js server

The blog post is divided into two sections, in first we will explore creating a basic server for our application. This server uses basic web scraping techniques to retrieve data from a trusted news portal. We are only scraping article title and article link, but can expand feature to include more data as well. In the next section, we will create an a cross-platform app from scratch for both iOS and Android. This app is going to retrieve the data from the server we created in Section 1 and display to the user.

Section 1

Building a server :

npm install --save express request-promise cheerio

Add these dependencies to a new file newsAPI.js.

const express = require("express");

const app = express();

const rp = require("request-promise");

const $ = require("cheerio");

app.use(express.json());


app.get("/", (req, res) => {

  res.send("Hello !!!");

});

const port = process.env.PORT || 3000;

app.listen(port, () => console.log(`http://localhost:${port}`));


Open console and type node newsAPI.js and you will be prompted with following message:

Running server on http://localhost:3000

Click on the link or go to web browser and type the address and you should see the "Hello !!!" message on your browser.

Now we create a similar GET endpoint. Let's decide on using a static URL for now. We will retrieve all news about Corona Virus from onlineKhabar.com.

We visit the site and click on कोरोना भाइरस under Trending ticker section. This opens all published news articles with the above tag in the site. Doing an Inspect Element on one of the news posts reveals the DOM path of the post. We identify that the corresponding tag is a div tag with classnames relative, list__post and show_grid--view. We will use this information to get a list of all posts displayed as grid in current page. Using Cheerio, we can use CSS selector to jump to obtain the list of div element for all posts that we need. Next, we loop through this list to find the a tag which holds the link and text. Let's also add the thumbnail image within this a tag, we can use this as image source in our mobile app. We will construct an array containing json objects for each news posts with fields title for news title, link for the article page and image for the thumbnail. Finally, we send this data via a get endpoint "/api/news".

app.get("/api/news", (req, res) => {

  const url = encodeURI(`https://www.onlinekhabar.com/trend/कोरोना-भाइरस/`);

  rp(url)

    .then((html) => {

      var data = $("div.relative.list__post.show_grid--view", html);

      const sendData = [];

      data.each((index, elm) => {

        const newsPiece = $(elm).find("a");

        sendData.push({

          title: newsPiece.text(),

          link: newsPiece.attr("href"),

          image: $(newsPiece).find("img").attr("src"),

        });

      });

      res.send(sendData);

    })

    .catch((err) => res.status(404).send("Error : " + err));

});

Now let's head back to the browser and visit http://localhost:3000/api/news. We should now get the following content(or something similar).

 [{"title":"वीरगञ्जमा ६० वर्षीय संक्रमितको मृत्यु","link":"https://www.onlinekhabar.com/2020/09/894795","image":"https://www.onlinekhabar.com/wp-content/uploads/2020/06/birgunj-health-care-hospital-300x182.jpg"},{"title":"कपिलवस्तुमा मृत्यु भएका पत्रकारमा कोरोना पुष्टि","link":"https://www.onlinekhabar.com/2020/09/894793","image":"https://www.onlinekhabar.com/wp-content/uploads/2020/08/Corona-Death-300x210.jpg"},{"title":"‘वीरगञ्ज र काठमाडौंलाई हेरेर गाउँको पढाइ नरोकौं’","link":"https://www.onlinekhabar.com/2020/09/894643","image":"https://www.onlinekhabar.com/wp-content/uploads/2020/09/Shishir-Khanal-Teach-for-Nepal_1230px-09-06-300x122.jpg"},{"title":"कोरोना संक्रमणबाट थप ११ जनाको मृत्यु पुष्टि","link":"https://www.onlinekhabar.com/2020/09/894785","image":"https://www.onlinekhabar.com/wp-content/uploads/2020/08/Corona-death-6-300x183.jpg"},{"title":"काठमाडौंमा दयनीय कन्ट्याक्ट ट्रेसिङ – प्रतिसंक्रमित दुईजना !","link":"https://www.onlinekhabar.com/2020/09/894783","image":"https://www.onlinekhabar.com/wp-content/uploads/2020/09/Ward-Statistics-09-07-191x300.jpg"}].

Our backend server is ready. Let's continue to run it on the localhost for now. Deployment options will be covered in a different post.


Section 2

Using Expo we can dive straight into code without much hassle in setting up codebase. Install expo using npm install expo-cli --global if you haven't already. I have already installed expo-cli so after entering expo init expo-news-app I will be prompted to choose the type of template to choose. Selecting a bare managed workflow seems good enough for us right now, so we will go ahead and press enter. Expo should now setup the project directory with all required files. Make sure you download the expo client app on your phone, Create and account on Expo and login to the expo client app to access your development app. If you are looking to publish and share, check out my other post on publishing first app here.

To begin on the app, start by creating a src directory inside the project folder. It isn't required to do this step but it's nice to give a little structure to the codebase. Here, create a file called News.js and another file NewsList.js. This News will be our base component for each individual news data, and newslist is our scrollable list that lets the user scroll through and select particular news articles. We'll create an index.js file to manage our exports. Next, go to App.js created by default. This is the entrypoint for our app, so we will create a base container view and render our news article there. Any time we need CSS for our view, we will use the Stylesheet component to define and implement our CSS.

For News component, let's create something simple to use and understand. Since we have three types of data, viz. title, link and image in our data, we will choose to display the image on the left and title on the right.

We will not display link but rather, open the corresponding link in a browser. This is provided by the Linking component from react-native. We just need to pass in the URL and the component does the rest to let the user select the phone browser to open the URL in. This action should be callable when the user taps anywhere on that News component, thus the entire component is a clickable one. This means we need to wrap our base component in a TouchableOpacity and define an onPress event. For our Image component from react-native, we need to provide it with image URL and the width and height of the component. In case a news article does not have an image, we pass in the default app icon under assets folder. We render the news title using a Text component and the rest of the View will be filled by this title. This is made possible by the flexShrink :1 property on the stylesheet. Our News.js file will now look like this :

import React from "react"; import { Image, View, Text, StyleSheet, TouchableOpacity, Linking, } from "react-native"; const styles = StyleSheet.create({ newsContainer: { flexDirection: "row", padding: 5, alignItems: "stretch", justifyContent: "space-around", backgroundColor: "lightgrey", }, newsTitle: { margin: 5, fontSize: 18, flexShrink: 1, }, }); export default News = ({ newsData }) => { const imageThumbnail = { uri: newsData.image || require("../assets/icon.png"), width: 100, height: 75, }; return ( <TouchableOpacity onPress={() => Linking.openURL(newsData.link)}> <View style={styles.newsContainer}> <Image source={imageThumbnail} /> <Text style={styles.newsTitle}>{newsData.title}</Text> </View> </TouchableOpacity> ); };


Next, we will wrap this News component in a ScrollView. This scrollable view is essentially made up of a list of News, so it makes sense to use an array datatype and iterate a map function to generate News component for each element. We will also add in a RefreshControl provided by react-native. This refresh control, also known as Pull-To-Refresh action is a function that gets called when the user pulls the list down from the top of the view. In our case, we want to redo a network request that we called initially to load the news data. Our fetchNewsData function does this for us. By leveraging the Javascript fetch request, we make a call to our localhost server to the /api/news endpoint and update the received json response to newsData. We will also use the setRefreshing hook to choose when to display the refreshing icon on the UI by updating the value of refreshing.

This is our final NewsList.js : 

import React, { useEffect, useState } from "react"; import { RefreshControl, ScrollView } from "react-native"; import News from "./News"; export default NewsList = () => { const [newsData, setNewsData] = useState([]); const [refreshing, setRefreshing] = useState(true); const fetchNewsData = useEffect(() => { console.log("Called!"); setRefreshing(true); fetch("http://localhost:3000/api/news") .then((response) => response.json()) .then((data) => { setNewsData(data); setRefreshing(false); }) .catch((err) => { console.log(err); setRefreshing(false); }); }, []); return ( <ScrollView refreshControl={ <RefreshControl refreshing={refreshing} onRefresh={fetchNewsData} /> } > {newsData.map((el, idx) => ( <News key={idx} newsData={el} /> ))} </ScrollView> ); };

Our App component is simply going to place the app name as header at the very top using a Text component and then place the scrollable component NewsList. We will import it from the index.js file inside src folder.

import NewsList from "./NewsList"; export default NewsList;


This is our final App.js :

import React from "react"; import { StyleSheet, Text, View } from "react-native"; import NewsList from "./src"; export default function App() { return ( <View style={styles.container}> <Text style={styles.header}>Nepali News App</Text> <NewsList /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#fff", alignItems: "flex-start", justifyContent: "flex-start", margin: 10, padding: 10, }, header: { fontSize: 30, fontWeight: "bold", }, });


After completing all of this, save all files and go to command line. Type expo start --lan and it should print out a URL to show the app is running. If you haven't already downloaded a expo client app make sure you do it now and then you will see your running app on the Dashboard. When you select it, Expo is going to build the app on the fly and load it on your phone. If you see the list of news article same as I did, then you've completed developing your first expo app.

Find the full code here :

https://github.com/sndpwrites/expo-news-app

Follow me on Github!



Comments

  1. Hello Sandeep, I don't see the code for the server side in the Github link you've provided. Would you please point me to it? I am wondering how the directory structure should be. Should it be two directories- server and client, at the root as usual? In that case, how is that going to work when you publish the project to the expo server? Publish command will run in the client directory, and the server code is in a separate directory.

    ReplyDelete
  2. Hi Pauline, for the server side code please see the section 1 of the above blog post. For your convenience, I have updated the code in this pastebin link https://pastebin.com/8KLd0AEt that you can copy-paste into index.js.
    Since this is a simple server example, the server directory structure would consist of very few files not enough in a real world scenario. To allow for a standard server directory structure, you can try these commands after installing express.
    npx express-generator #this installs express-generator
    express myapp #this sets up the directory structure for you
    And yes you need to have separate directories for server(Express project) and client(Expo project). You are correct that the publish command only works in Expo projects for deploying the mobile app. To publish the server app, you need to look at hosting/deployment options. I personally recommend Digital Ocean for beginner users familiar with Ubuntu machines.

    Do let me know if you have more questions. Hope this helps. :)

    ReplyDelete

Post a Comment

Popular posts from this blog

Youtube not working in Vianet NetTV? Here's how you can fix it

Web scraping using BeautifulSoup in Python : EAN number vs price from a German e-commerce website