Simple Todo app using reactnative,firebase and nativebase
This tutorial is the second part of react native firebase series.We are going to continue from where we left of in the last tutorial which can be found here.
In this part we are going to learn how to use real-time database provided by firebase for storing and retrieving database entries.
We need to enable anonymous database update feature of firebase realtime db:
First login into firebase console and go to your project.
Click on the database tab on the left and then click on rules tab in the main page.
Modify the rules to allow anonymous db updates as follows:
{
"rules": {
".read": true,
".write":true
}
}
We will modify our Main.js file as follows:
First create a reference to database method in constructor:
constructor(props) {
super(props);
this.tasksRef = this.props.firebaseApp.database().ref();
this.state = {
user:null,
loading: true,
newTask: ""
}
}
Create a listener for database changes in the componentDidMount method and then populate task array as follows:
componentDidMount(){
// start listening for firebase updates
this.listenForTasks(this.tasksRef);
}//listener to get data from firebase and update listview accordingly
listenForTasks(tasksRef) {
tasksRef.on('value', (dataSnapshot) => {
var tasks = [];
dataSnapshot.forEach((child) => {
tasks.push({
name: child.val().name,
_key: child.key
});
}); this.setState({
tasks:tasks
});
});
}
The method creates a listener for getting updates from firebase and update the tasks array in realtime.
We store the name as well as key of each task so that we can remove the task on completion based on key value which is unique for each database item.
We are going to use Card and CardItem component of Nativebase to display our todo list.
Include appropriate components in the top of Main as follows:
import {
AppRegistry,
ActivityIndicator,
AsyncStorage,
ListView,
StyleSheet,
Text,
View,
TextInput,
} from 'react-native';
import React, {Component} from 'react';
import { Header,Container,Title, Content, Icon, Card, CardItem ,Button, Fab,Footer } from 'native-base';
Modify the render method of Main as follows:
render() {
// console.log("tasks value",this.state.tasks);
// If we are loading then we display the indicator, if the account is null and we are not loading
// Then we display nothing. If the account is not null then we display the account info.
const content = this.state.loading ?
<ActivityIndicator size="large"/> :
this.state.user &&
<Content>
<Card dataArray={this.state.tasks}
renderRow={(task) => this._renderItem(task)} >
</Card>
</Content>
;
// console.log("loading user",this.state.user,this.state.loading);
return (
<Container>
<Header>
<Button transparent>
<Icon name='ios-menu' />
</Button>
<Title>To Do</Title>
<Button transparent onPress={() => this.logout()}>
<Icon name='ios-arrow-back' />
</Button> </Header>
{content}
<Footer style={styles.footer}>
<TextInput
value={this.state.newTask}
style={styles.textEdit}
onChangeText={(text) => this.setState({newTask: text})}
placeholder="New Task"
/>
<Fab
active={this.state.active}
containerStyle={{ marginRight: 0,width:20 }}
style={styles.floatButton}
position="bottomRight"
onPress={() => this._addTask()}
>
<Icon name="md-add" />
</Fab> </Footer> </Container>
);
}
We have created a input box at the footer of the screen to add new task using TextInput and FloatingActionButton(FAB) of nativebase which executes addtask handler function which is defined as follows:
//add a new task to firebase app
_addTask() {
// console.log("task value",this.state.newTask);
if (this.state.newTask === "") {
return;
}
this.tasksRef.push({ name: this.state.newTask});
this.setState({newTask: ""});
alert("Task added successfully");
}
The add task handler checks if textInput is empty and creates a new database entry with name of the task in the firebase database.It then sets newTask value to blank for adding new task and displays an alert to the user about successful task addition.
The render method contain Card component which takes input dataArray as our tasks array and renders each arrayitem using in renderRow props using renderItem handler which is defined as follows:
_renderItem(task) {
// console.log("task",task._key);
const onTaskCompletion= () => {
// console.log("clickrecived",this.tasksRef.child(task._key).remove());
this.tasksRef.child(task._key).remove().then(
function() {
// fulfillment
alert("The task "+task.name+" has been completed successfully");
},
function() {
// fulfillment
alert("The task "+task.name+" has not been removed successfully");
});
}
return (
<ListItem task={task} onTaskCompletion={onTaskCompletion} />
);
}
Here we are passing onTaskcompletion method as prop which we will discuss later.For now just observe that it return a ListItem component to parent renderRow for each array item.
Lets us create ListItem component.For this create a new components folder inside src folder besides pages and styles folder.Create ListItem.js file in the Components folder and write the following code:
import React, {
Component
} from 'react';
import {CardItem,Icon, Text} from 'native-base';
import styles from '../styles/mainstyle.js';class ListItem extends Component {
render() {
return (
<CardItem>
<Icon name='md-create' />
<Text>{this.props.task.name}</Text>
<Icon name='md-checkmark' onPress={() => this.props.onTaskCompletion()}/>
</CardItem>
);
}
}module.exports = ListItem;
Here we have simply created a carditem which can be further customised based on your needs. We have added a checkmark besides each task by which we can remove the task when it is completed.The onPress method of the checkmark icon gives a call to onTaskCompletion method in renderItem method of Main component.
const onTaskCompletion= () => {
// console.log("clickrecived",this.tasksRef.child(task._key).remove());
this.tasksRef.child(task._key).remove().then(
function() {
// fulfillment
alert("The task "+task.name+" has been completed successfully");
},
function() {
// fulfillment
alert("The task "+task.name+" has not been removed successfully");
});
The onTaskcompletion removes the item from firebase database based on the key that it receives from the carditem object.It then displays alert the user that the given task is completed.
The complete Main.js will look like this after making all the changes:
'use strict';
import {
AppRegistry,
ActivityIndicator,
AsyncStorage,
ListView,
StyleSheet,
Text,
View,
TextInput,
} from 'react-native';
import React, {Component} from 'react';
import { Header,Container,Title, Content, Icon, Card, CardItem ,Button, Fab,Footer } from 'native-base';//Pages and styles
import Login from './Login';
import ListItem from '../components/ListItem.js';
import styles from '../styles/mainstyle.js';//Component
export default class Account extends Component { constructor(props) {
super(props);
this.tasksRef = this.props.firebaseApp.database().ref();
this.state = {
user:null,
loading: true,
newTask: ""
}
} componentDidMount(){
// start listening for firebase updates
this.listenForTasks(this.tasksRef);
} componentWillMount() {
// get the current user from firebase
// const userData = this.props.firebaseApp.auth().currentUser;
AsyncStorage.getItem('userData').then((user_data_json) => {
let userData = JSON.parse(user_data_json);
this.setState({
user: userData,
loading: false,
active:'true',
tasks:[]
});
}); } //listener to get data from firebase and update listview accordingly
listenForTasks(tasksRef) {
tasksRef.on('value', (dataSnapshot) => {
var tasks = [];
dataSnapshot.forEach((child) => {
tasks.push({
name: child.val().name,
_key: child.key
});
}); this.setState({
tasks:tasks
});
});
} render() {
// console.log("tasks value",this.state.tasks);
// If we are loading then we display the indicator, if the account is null and we are not loading
// Then we display nothing. If the account is not null then we display the account info.
const content = this.state.loading ?
<ActivityIndicator size="large"/> :
this.state.user &&
<Content>
<Card dataArray={this.state.tasks}
renderRow={(task) => this._renderItem(task)} >
</Card>
</Content>
;
// console.log("loading user",this.state.user,this.state.loading);
return (
<Container>
<Header>
<Button transparent>
<Icon name='ios-menu' />
</Button>
<Title>To Do</Title>
<Button transparent onPress={() => this.logout()}>
<Icon name='ios-arrow-back' />
</Button> </Header>
{content}
<Footer style={styles.footer}>
<TextInput
value={this.state.newTask}
style={styles.textEdit}
onChangeText={(text) => this.setState({newTask: text})}
placeholder="New Task"
/>
<Fab
active={this.state.active}
containerStyle={{ marginRight: 0,width:20 }}
style={styles.floatButton}
position="bottomRight"
onPress={() => this._addTask()}
>
<Icon name="md-add" />
</Fab> </Footer> </Container>
);
} //render ListItems as per our needs
_renderItem(task) {
// console.log("task",task._key);
const onTaskCompletion= () => {
// console.log("clickrecived",this.tasksRef.child(task._key).remove());
this.tasksRef.child(task._key).remove().then(
function() {
// fulfillment
alert("The task "+task.name+" has been completed successfully");
},
function() {
// fulfillment
alert("The task "+task.name+" has not been removed successfully");
});
}
return (
<ListItem task={task} onTaskCompletion={onTaskCompletion} />
);
} //add a new task to firebase app
_addTask() {
// console.log("task value",this.state.newTask);
if (this.state.newTask === "") {
return;
}
this.tasksRef.push({ name: this.state.newTask});
this.setState({newTask: ""});
alert("Task added successfully");
}
logout() {
// logout, once that is complete, return the user to the login screen.
AsyncStorage.removeItem('userData').then(() => {
this.props.firebaseApp.auth().signOut().then(() => {
this.props.navigator.push({
component: Login
});
});
}); }
}AppRegistry.registerComponent('Account', () => Account);
Update the mainstyle.js with following additonal parts:
textEdit:{
bottom:0,
flex:1,
width:300,
},
floatButton:{
flex:1,
top:48,
left:50,
alignSelf:'center',
backgroundColor:'#34A34F'
},
footer:{
justifyContent:'center',
alignItems:'center',
backgroundColor:'white'
}
Once you are done with all the changes reload the app on phone and you will get a todo app interface in the main page after you login which looks like this:
todoapp1
todoapp2
I did not get time to search for logout icon on top right but back button does the job of log out for now.
The complete source code for the app can be found on Github repo here.Feel free to fork the project or report issues.
The app currently will not work offline since we have not enabled data persistence.I will cover offline data storage in the next tutorial so stay tuned.
You can make your own improvements and additions to the app to make it more usable like:
- Allow editing task name on press on pencil/create icon.
- Add new screen/component on press of Plus button which allows user to add additional information like due date of task,set reminder for the task or add image for the task
- Use firebase auth to associate task list for each user
Kindly mention your doubts/suggestions/appreciations in the comments section.