Lesson 3: Firebase & Cursor

Build the essential features of MealSnap app with AI assistance

Working with Firebase in Cursor

In this lesson, we'll implement the essential features of our MealSnap app using Firebase and Cursor. We'll focus on authentication, database integration, and the core image-based calorie calculation.

Key Takeaway: Cursor's AI capabilities dramatically accelerate the implementation of Firebase features, allowing rapid development of sophisticated functionality.

Video Lesson

Implementing Core Features

Let's break down the implementation of our core features step by step.

Step 1: Implementing Firebase Authentication

We'll start by creating an authentication service and login/signup screens:

"Successfully signed up a user. User appeared in Firebase console. Sign-in and sign-out working."

Let's create an authentication service using AI assistance:

// authService.js
import { auth } from './firebase-config';
import {
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signOut,
  onAuthStateChanged
} from 'firebase/auth';

// Register a new user
export const registerUser = async (email, password) => {
  try {
    const userCredential = await createUserWithEmailAndPassword(auth, email, password);
    return { user: userCredential.user, error: null };
  } catch (error) {
    return { user: null, error: error.message };
  }
};

// Sign in existing user
export const signInUser = async (email, password) => {
  try {
    const userCredential = await signInWithEmailAndPassword(auth, email, password);
    return { user: userCredential.user, error: null };
  } catch (error) {
    return { user: null, error: error.message };
  }
};

// Sign out current user
export const signOutUser = async () => {
  try {
    await signOut(auth);
    return { error: null };
  } catch (error) {
    return { error: error.message };
  }
};

// Listen to auth state changes
export const subscribeToAuthChanges = (callback) => {
  return onAuthStateChanged(auth, (user) => {
    callback(user);
  });
};

Step 2: Creating Authentication Screens

Next, we'll implement the login and registration screens:

// LoginScreen.js
import React, { useState } from 'react';
import { 
  View, Text, TextInput, TouchableOpacity, 
  StyleSheet, Alert, ActivityIndicator 
} from 'react-native';
import { signInUser } from '../services/authService';

const LoginScreen = ({ navigation }) => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [loading, setLoading] = useState(false);

  const handleLogin = async () => {
    if (!email || !password) {
      Alert.alert('Error', 'Please fill in all fields');
      return;
    }
    
    setLoading(true);
    const { user, error } = await signInUser(email, password);
    setLoading(false);
    
    if (error) {
      Alert.alert('Error', error);
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Welcome to MealSnap</Text>
      <Text style={styles.subtitle}>Sign in to continue</Text>
      
      <TextInput
        style={styles.input}
        placeholder="Email"
        value={email}
        onChangeText={setEmail}
        keyboardType="email-address"
        autoCapitalize="none"
      />
      
      <TextInput
        style={styles.input}
        placeholder="Password"
        value={password}
        onChangeText={setPassword}
        secureTextEntry
      />
      
      <TouchableOpacity 
        style={styles.button}
        onPress={handleLogin}
        disabled={loading}
      >
        {loading ? (
          <ActivityIndicator color="#fff" />
        ) : (
          <Text style={styles.buttonText}>Login</Text>
        )}
      </TouchableOpacity>
      
      <TouchableOpacity 
        style={styles.button}
        onPress={() => navigation.navigate('Register')}
      >
        <Text style={styles.link}>
          Don't have an account? Sign Up
        </Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  // Styles created by AI
});

Step 3: Building the Meal Tracking Screen

Now we'll implement the main meal tracking screen with AI assistance:

// MealsScreen.js
import React, { useState, useEffect } from 'react';
import { 
  View, Text, FlatList, TouchableOpacity, 
  StyleSheet, Image 
} from 'react-native';
import { db, auth } from '../services/firebase-config';
import { 
  collection, query, where, orderBy, 
  getDocs, Timestamp 
} from 'firebase/firestore';

const MealsScreen = ({ navigation }) => {
  const [meals, setMeals] = useState([]);
  const [loading, setLoading] = useState(true);
  const [viewMode, setViewMode] = useState('weekly'); // 'weekly' or 'monthly'

  useEffect(() => {
    fetchMeals();
  }, [viewMode]);

  const fetchMeals = async () => {
    try {
      setLoading(true);
      const userId = auth.currentUser.uid;
      
      // Calculate date range based on view mode
      const today = new Date();
      let startDate = new Date();
      
      if (viewMode === 'weekly') {
        startDate.setDate(today.getDate() - 7);
      } else {
        startDate.setMonth(today.getMonth() - 1);
      }
      
      const mealsRef = collection(db, 'meals');
      const q = query(
        mealsRef,
        where('userId', '==', userId),
        where('date', '>=', Timestamp.fromDate(startDate)),
        orderBy('date', 'desc')
      );
      
      const querySnapshot = await getDocs(q);
      const mealsList = [];
      
      querySnapshot.forEach((doc) => {
        mealsList.push({
          id: doc.id,
          ...doc.data(),
          date: doc.data().date.toDate()
        });
      });
      
      setMeals(mealsList);
    } catch (error) {
      console.error('Error fetching meals:', error);
    } finally {
      setLoading(false);
    }
  };

  const renderMealItem = ({ item }) => (
    <TouchableOpacity 
      style={styles.mealCard}
      onPress={() => navigation.navigate('MealDetail', { mealId: item.id })}
    >
      <Image 
        source={{ uri: item.imageUrl }} 
        style={styles.mealImage} 
      />
      <View style={styles.mealInfo}>
        <Text style={styles.mealName}>{item.name}</Text>
        <Text style={styles.mealCalories}>{item.calories} calories</Text>
        <Text style={styles.mealDate}>
          {item.date.toLocaleDateString()}
        </Text>
      </View>
    </TouchableOpacity>
  );

  return (
    <View style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.title}>Your Meals</Text>
        <View style={styles.viewToggle}>
          <TouchableOpacity
            style={[
              styles.toggleButton,
              viewMode === 'weekly' && styles.activeToggle
            ]}
            onPress={() => setViewMode('weekly')}
          >
            <Text style={styles.toggleText}>Weekly</Text>
          </TouchableOpacity>
          <TouchableOpacity
            style={[
              styles.toggleButton,
              viewMode === 'monthly' && styles.activeToggle
            ]}
            onPress={() => setViewMode('monthly')}
          >
            <Text style={styles.toggleText}>Monthly</Text>
          </TouchableOpacity>
        </View>
      </View>

      <FlatList
        data={meals}
        renderItem={renderMealItem}
        keyExtractor={item => item.id}
        contentContainerStyle={styles.mealsList}
        ListEmptyComponent={
          <Text style={styles.emptyText}>
            No meals recorded in this period
          </Text>
        }
      />
      
      <TouchableOpacity
        style={styles.addButton}
        onPress={() => navigation.navigate('AddMeal')}
      >
        <Text style={styles.addButtonText}>+</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  // Styles generated by AI
});

Step 4: Debugging and Troubleshooting

When implementing these features, we encountered some common issues and resolved them using AI:

Common Error:

Conflicting routes and layout components

Solution:

Use Cursor's terminal to check error logs and fix routing configuration

Common Error:

TypeScript files generated instead of JavaScript

Solution:

Update Cursor rules and explicitly instruct AI to use JavaScript

Using Cursor's checkpoint feature, we could easily roll back changes when needed and try again with clearer instructions.

Key Takeaways

  • AI excels at authentication implementation. Firebase Auth integration is significantly faster with AI assistance.
  • Use clear instructions for AI. The more specific your requests, the better the generated code.
  • Leverage checkpoints. They provide a safety net for experimenting with different approaches.
  • Test frequently. Verify functionality as you build rather than trying to fix everything at once.

Practice Exercise

Implement a Profile Screen

Using what you've learned, implement a user profile screen for the MealSnap app that allows users to:

  • View their profile information
  • Update their display name
  • Change their password
  • View a summary of their meal tracking statistics

Use Cursor and AI to generate the necessary components and Firebase integration code.

Share your implementation in our community