Pull to refresh is a widely adopted feature in mobile apps. Facebook, Gmail, Instagram almost all popular apps have this feature where the user can drag the screen downward that signals the server to send new data.
We have already covered the infinite scroll implementation for Android and iOS. Check out the article here.
Prerequisites and Target Audience
Developers having a basic understanding of frontend and backend coding and development environment.
Basic node and npm commands (for backend simulation), expo setup (for frontend implementation).
Why you should have this feature
Nowadays considering how easily the user can operate the application determines how many users might be using your application.
Also, cleaner the User Interface more likable is the application to the user. Now suppose your page feed is loaded but now user wants to reload the feed to check on if there are any new elements in your feed.
So, as a user, it will be quite convenient to pull down at the top of the page and reload instead of reopening the application.
The application flow
We are going to develop our mobile app which will run on Android and iOS. We are going to use react native framework.
For the backend server, we will use simple Nodejs mock server. You can replace the same with real data with few tweaks and reuse the code.
Upon each pull to refresh request, Nodejs server will provide the fresh mock dataset to showcase on the app.
Let’s code.
Setting up Backend
Create a new directory and switch to it using the terminal.
Create a new Node project using the following command.
Install the required dependencies using the following command.
Create a new file and name it app.js and copy/paste the following code.
const app = express()
const router = express.Router()
router.get('/fetch-paginated-data', (req, res) => {
var pageNo = parseInt(req.query.pageNo)
var pageSize = parseInt(req.query.pageSize)
//checking if page number is invalid
if (pageNo <= 0) {
var response = {
success: false,
message: 'Invalid Page Number'
};
return res.status(200).json(response);
} else {
//fetch data from database based on given page no and page size
var index = (parseInt(pageNo - 1) * parseInt(pageSize)) + 1;
var list = [];
for (var i = 0; i < pageSize - 1; i++) {
list.push({
index: index,
data: 'Data ' + index
});
index++;
}
var response = {
success: true,
list: list
};
return res.status(200).json(response);
}
});
app.use('/', router)
app.listen(3000);
Run the backend app:
Now we have a backend simulation ready which will keep on giving us data for any page number and any page size request.
Building the Mobile App
We are going to use react native framework to code our mobile app. We can use multiple tools to use this framework. In this tutorial we are going to use expo.
You can install expo cli if you have not already by running the following command.
Then, create a new expo project using the following command.
It will ask you two questions. Fill up the proper name and slug URL for your app.
? Choose a template: expo-template-blank
Now switch to the folder and install the dependencies.
Open the App.js and copy/paste the following code.
import { StyleSheet, ActivityIndicator, View,RefreshControl } from 'react-native';
import { Container, Content, Text, List } from 'native-base';
import axios from 'axios';
export default class App extends React.Component {
constructor(props) {
super(props);
//initialize state values
this.state = {
pageNo: 1,
pageSize: 20,
showLoadingMore:false,
data: [],
loadMoreData: true,//to denote whether bottom of list is reached
shouldHit: true, //whether more data needs to be fetched
dataReceived: false, //whether initial data is fetched
refreshing:false
}
}
componentWillMount = () => {
this.fetchData();
}
//function to fetch more data as per current page number
fetchData = () => {
if(this.state.pageNo!=1){
//when we try to fetch more data show loader at the bottom
this.setState({
showLoadingMore:true
})
}
var systemIPAddress='192.168.1.25'; // add your IP.
var url = 'http://'+systemIPAddress+':3000/fetch-paginated-data?pageNo='+this.state.pageNo+'&pageSize='+this.state.pageSize;
axios
.get(url)
.then(response => {
if (response.data.success) {
//add data to list and change the state to render new content
let receivedDataList = response.data.list;
let currentDataList = this.state.data;
//append to existing list
let newDataList = currentDataList.concat(receivedDataList);
//render new list
//once new list is set we are ready to load more data if bottom is reached
let loadMoreData =true;
if(this.state.refreshing){
this.setState({
pageNo:this.state.pageNo+1,
data:newDataList,
dataReceived:true,
loadMoreData: loadMoreData,
showLoadingMore:false,
refreshing:false
})
}else{
this.setState({
pageNo:this.state.pageNo+1,
data:newDataList,
dataReceived:true,
loadMoreData: loadMoreData,
showLoadingMore:false,
})
}
} else {
//no more data to be loaded
this.setState({
shouldHit:false,
showLoadingMore:false,
refreshing:false
})
}
})
.catch(error => {
console.log(error)
});
}
onRefreshHandler = () => {
//reset pageNo to 1
this.setState({refreshing: true,pageNo:1,data:[],dataReceived:false});
let reactNativeInstance = this;
//timeout to simulate loading
setTimeout(()=>{
reactNativeInstance.fetchData();
},100);
}
render() {
const isCloseToBottom = ({ layoutMeasurement, contentOffset, contentSize }) => {
const paddingToBottom = 40;
let result = layoutMeasurement.height + contentOffset.y >= contentSize.height - paddingToBottom;
//true if the end is reached other wise false
return result;
};
//initially display loader at the center
let listSection =
<View style={ styles.container} >
<ActivityIndicator size="large" color="#0000ff" />
</View>
if (this.state.dataReceived) {
if (this.state.data.length > 0) {
listSection = this
.state
.data
.map((record) => {
return (
<View key={record.index} style={[styles.container,{ margin: 10, height:40, borderWidth:1, borderColor:'black'}]}>
<Text>{record.index}. {record.data}</Text>
</View>
);
})
} else {
listSection = null;
}
}
if (this.state.refreshing) {
return (
<View style={styles.container}>
<Text>Refreshing Please Wait ...</Text>
</View>
)
} else if (this.state.dataReceived && this.state.data.length == 0) {
return (
<View style={styles.container}>
<Text>No records to display</Text>
</View>
)
} else {
return (
<Container style={{marginTop:40}}>
<Content
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this.onRefreshHandler}
/>
}
onScroll={({ nativeEvent }) => {
if (isCloseToBottom(nativeEvent)) {
//prevent multiple hits for same page number
if(this.state.loadMoreData){
//bottom reached start loading data
this.setState({
loadMoreData:false
})
this.fetchData();
}
}
}}>
<List>
{listSection}
</List>
{this.state.showLoadingMore ? <ActivityIndicator size="large" color="#0000ff" />: null}
</Content>
</Container>
)
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Pull to refresh Explanation
Import and Setup RefreshControl component as a JSX attribute for Content native-base component.
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this.onRefreshHandler}
/>
RefreshControl component has two attributes:
- refreshing: when true implies the user has attempted to refresh the list.
- onRefresh: points to the handler which handles the execution when the user clicks refresh.
Handler for onRefresh Event resets various state values to initial values and the code is as follows:
//reset pageNo to 1
this.setState({refreshing: true,pageNo:1,data:[],dataReceived:false});
let reactNativeInstance = this;
//timeout to simulate loading
setTimeout(()=>{
reactNativeInstance.fetchData();
},300);
}
Let’s run the app.
A new browser window will open, something like this.
Download the expo mobile app available on Google Play and iTunes store. Here is the link.
https://play.google.com/store/apps/details?id=host.exp.exponent&hl=en (For Android Phones)
https://itunes.apple.com/us/app/expo-client/id982107779?mt=8 (For iPhone)
Scan the QR code using the expo app and our custom app will run on your mobile phone.
Here is the demo video link.
Conclusion
If your mobile app is dealing with data then pull to refresh feature is a must. It’s interactive, useful and people sort of know how to use it already.