Building a Task Management App with React Native and Firebase

In today's fast-paced world, task management apps have become essential tools for staying organized and productive. This article will explore how to build a simple yet effective task management app using React Native and Firebase. We'll cover user authentication, CRUD operations for tasks, real-time collaboration, push notifications, and offline support.

Getting Started

To follow along with this tutorial, you should have basic knowledge of React Native and Firebase. Ensure you have Node.js, npm, and the React Native CLI installed on your machine. You'll also need a Firebase project set up.

  1. Create a New React Native Project
npx react-native init TaskManagerApp
cd TaskManagerApp
  1. Install Required Dependencies
npm install @react-native-firebase/app @react-native-firebase/auth @react-native-firebase/firestore
  1. Configure Firebase

Follow the steps on the Firebase console to add your project to the React Native app. Download the google-services.json file for Android and GoogleService-Info.plist for iOS and place them in the appropriate directories.

Setting Up Firebase Authentication

First, let's set up user authentication using Firebase. We'll allow users to sign up and log in with email and password.

  1. Create AuthContext

     // src/context/AuthContext.js
     import React, { createContext, useState, useEffect } from 'react';
     import auth from '@react-native-firebase/auth';
    
     export const AuthContext = createContext();
    
     export const AuthProvider = ({ children }) => {
       const [user, setUser] = useState(null);
    
       useEffect(() => {
         const unsubscribe = auth().onAuthStateChanged((user) => {
           setUser(user);
         });
         return unsubscribe;
       }, []);
    
       return (
         <AuthContext.Provider value={{ user, setUser }}>
           {children}
         </AuthContext.Provider>
       );
     };
    
  2. Create Authentication Screens

     // src/screens/LoginScreen.js
     import React, { useState, useContext } from 'react';
     import { View, TextInput, Button, Text } from 'react-native';
     import auth from '@react-native-firebase/auth';
     import { AuthContext } from '../context/AuthContext';
    
     const LoginScreen = ({ navigation }) => {
       const [email, setEmail] = useState('');
       const [password, setPassword] = useState('');
       const { setUser } = useContext(AuthContext);
    
       const handleLogin = async () => {
         try {
           await auth().signInWithEmailAndPassword(email, password);
         } catch (error) {
           console.error(error);
         }
       };
    
       return (
         <View>
           <TextInput
             placeholder="Email"
             value={email}
             onChangeText={setEmail}
           />
           <TextInput
             placeholder="Password"
             value={password}
             onChangeText={setPassword}
             secureTextEntry
           />
           <Button title="Login" onPress={handleLogin} />
           <Text onPress={() => navigation.navigate('SignUp')}>Sign Up</Text>
         </View>
       );
     };
    
     export default LoginScreen;
    
     // src/screens/SignUpScreen.js
     import React, { useState } from 'react';
     import { View, TextInput, Button } from 'react-native';
     import auth from '@react-native-firebase/auth';
    
     const SignUpScreen = ({ navigation }) => {
       const [email, setEmail] = useState('');
       const [password, setPassword] = useState('');
    
       const handleSignUp = async () => {
         try {
           await auth().createUserWithEmailAndPassword(email, password);
         } catch (error) {
           console.error(error);
         }
       };
    
       return (
         <View>
           <TextInput
             placeholder="Email"
             value={email}
             onChangeText={setEmail}
           />
           <TextInput
             placeholder="Password"
             value={password}
             onChangeText={setPassword}
             secureTextEntry
           />
           <Button title="Sign Up" onPress={handleSignUp} />
           <Button title="Back to Login" onPress={() => navigation.navigate('Login')} />
         </View>
       );
     };
    
     export default SignUpScreen;
    
  3. Set Up Navigation

     // App.js
     import React from 'react';
     import { NavigationContainer } from '@react-navigation/native';
     import { createStackNavigator } from '@react-navigation/stack';
     import { AuthProvider } from './src/context/AuthContext';
     import LoginScreen from './src/screens/LoginScreen';
     import SignUpScreen from './src/screens/SignUpScreen';
     import HomeScreen from './src/screens/HomeScreen';
    
     const Stack = createStackNavigator();
    
     const App = () => {
       return (
         <AuthProvider>
           <NavigationContainer>
             <Stack.Navigator initialRouteName="Login">
               <Stack.Screen name="Login" component={LoginScreen} />
               <Stack.Screen name="SignUp" component={SignUpScreen} />
               <Stack.Screen name="Home" component={HomeScreen} />
             </Stack.Navigator>
           </NavigationContainer>
         </AuthProvider>
       );
     };
    
     export default App;
    

Implementing Task Management with Firestore

Next, let's implement CRUD operations to manage tasks.

  1. Create TaskContext

     // src/context/TaskContext.js
     import React, { createContext, useState, useEffect, useContext } from 'react';
     import firestore from '@react-native-firebase/firestore';
     import { AuthContext } from './AuthContext';
    
     export const TaskContext = createContext();
    
     export const TaskProvider = ({ children }) => {
       const [tasks, setTasks] = useState([]);
       const { user } = useContext(AuthContext);
    
       useEffect(() => {
         if (user) {
           const unsubscribe = firestore()
             .collection('tasks')
             .where('userId', '==', user.uid)
             .onSnapshot((querySnapshot) => {
               const tasks = querySnapshot.docs.map((doc) => ({
                 id: doc.id,
                 ...doc.data(),
               }));
               setTasks(tasks);
             });
           return unsubscribe;
         }
       }, [user]);
    
       return (
         <TaskContext.Provider value={{ tasks }}>
           {children}
         </TaskContext.Provider>
       );
     };
    
  2. Create Task CRUD Operations

     // src/screens/HomeScreen.js
     import React, { useState, useContext } from 'react';
     import { View, TextInput, Button, FlatList, Text } from 'react-native';
     import firestore from '@react-native-firebase/firestore';
     import { TaskContext } from '../context/TaskContext';
     import { AuthContext } from '../context/AuthContext';
    
     const HomeScreen = () => {
       const [task, setTask] = useState('');
       const { tasks } = useContext(TaskContext);
       const { user } = useContext(AuthContext);
    
       const addTask = async () => {
         if (task.trim()) {
           await firestore().collection('tasks').add({
             userId: user.uid,
             task,
             createdAt: firestore.FieldValue.serverTimestamp(),
           });
           setTask('');
         }
       };
    
       const deleteTask = async (taskId) => {
         await firestore().collection('tasks').doc(taskId).delete();
       };
    
       return (
         <View>
           <TextInput
             placeholder="Add Task"
             value={task}
             onChangeText={setTask}
           />
           <Button title="Add Task" onPress={addTask} />
           <FlatList
             data={tasks}
             keyExtractor={(item) => item.id}
             renderItem={({ item }) => (
               <View>
                 <Text>{item.task}</Text>
                 <Button title="Delete" onPress={() => deleteTask(item.id)} />
               </View>
             )}
           />
         </View>
       );
     };
    
     export default HomeScreen;
    

Real-Time Collaboration and Push Notifications

Real-time collaboration is achieved through Firestore's real-time updates, as shown above. For push notifications, you can use Firebase Cloud Messaging (FCM).

  1. Set Up FCM

     npm install @react-native-firebase/messaging
    
     // App.js (Add FCM setup)
     import messaging from '@react-native-firebase/messaging';
     import { useEffect } from 'react';
    
     useEffect(() => {
       const unsubscribe = messaging().onMessage(async (remoteMessage) => {
         Alert.alert('A new FCM message arrived!', JSON.stringify(remoteMessage));
       });
       return unsubscribe;
     }, []);
    
  2. Request Permissions and Handle Notifications

     useEffect(() => {
       const requestPermission = async () => {
         const authStatus = await messaging().requestPermission();
         const enabled =
           authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
           authStatus === messaging.AuthorizationStatus.PROVISIONAL;
    
         if (enabled) {
           console.log('Authorization status:', authStatus);
         }
       };
       requestPermission();
     }, []);
    

Offline Support

To ensure the app works offline, enable Firestore's offline persistence.

// src/firebaseConfig.js
import firestore from '@react-native-firebase/firestore';

firestore().settings({
  persistence: true,
});