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.
- Create a New React Native Project
npx react-native init TaskManagerApp
cd TaskManagerApp
- Install Required Dependencies
npm install @react-native-firebase/app @react-native-firebase/auth @react-native-firebase/firestore
- 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.
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> ); };
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;
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.
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> ); };
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).
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; }, []);
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,
});