Introduction: Why Antigravity × Firebase?
Real-time chat apps are among the most popular categories in mobile app development. However, they require managing WebSocket connections, authentication flows, push notifications, and data persistence — typically requiring weeks of development.
By combining Antigravity's AI agents with Firebase, you can semi-automate these complex implementations and build a production-grade real-time chat app in a fraction of the time. This guide walks you through the complete process, from project setup to Firebase Authentication, Realtime Database integration, and push notification implementation.
Who this guide is for:
- Developers comfortable with Antigravity's basic operations
- Anyone with Firebase experience or interest
- Those looking to build real-time features efficiently
Setting Up the Firebase Project
First, create a Firebase project in the console and prepare it for connection from Antigravity.
Installing and Initializing Firebase CLI
# Install Firebase CLI
npm install -g firebase-tools
# Log in
firebase login
# Initialize project (select Realtime Database + Authentication + Cloud Messaging)
firebase init
# Expected output:
# ✔ Firebase initialization complete!
# - database: Firebase Realtime Database Rules
# - auth: Firebase AuthenticationConfiguring Firebase in Your Antigravity Project
Add context to your agents.md file so Antigravity's agents understand your Firebase setup:
<!-- agents.md -->
# Firebase Chat App Agent
## Context
- Firebase project: my-chat-app-2026
- Realtime Database URL: https://my-chat-app-2026-default-rtdb.firebaseio.com
- Auth methods: Email/Password + Google Sign-In
- Target platform: Web (React)
## Task
Build the frontend and backend for a chat application.
Use Firebase Authentication for auth and Realtime Database for real-time message sync.Implementing Firebase Authentication
Let's build the authentication layer that forms the foundation of our chat app. Simply tell Antigravity's agent to "create the auth component," and it generates code like the following.
Creating the Auth Hook
// src/hooks/useAuth.ts
import { useState, useEffect } from "react";
import {
getAuth,
onAuthStateChanged,
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
signInWithPopup,
GoogleAuthProvider,
signOut,
type User,
} from "firebase/auth";
import { getDatabase, ref, set, serverTimestamp } from "firebase/database";
export function useAuth() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const auth = getAuth();
const db = getDatabase();
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (firebaseUser) => {
setUser(firebaseUser);
setLoading(false);
// Save user profile to database
if (firebaseUser) {
set(ref(db, `users/${firebaseUser.uid}`), {
displayName: firebaseUser.displayName || "Anonymous",
email: firebaseUser.email,
photoURL: firebaseUser.photoURL,
lastSeen: serverTimestamp(),
});
}
});
return unsubscribe;
}, [auth, db]);
const login = (email: string, password: string) =>
signInWithEmailAndPassword(auth, email, password);
const register = (email: string, password: string) =>
createUserWithEmailAndPassword(auth, email, password);
const loginWithGoogle = () => {
const provider = new GoogleAuthProvider();
return signInWithPopup(auth, provider);
};
const logout = () => signOut(auth);
return { user, loading, login, register, loginWithGoogle, logout };
}
// Expected output: useAuth() manages auth state and provides login/register/logout functionsReal-Time Message Sync with Realtime Database
Now let's implement the core logic for sending and receiving messages in real time using Firebase Realtime Database.
Message Hook
// src/hooks/useChat.ts
import { useState, useEffect, useCallback } from "react";
import {
getDatabase,
ref,
push,
onChildAdded,
query,
orderByChild,
limitToLast,
serverTimestamp,
off,
} from "firebase/database";
import type { User } from "firebase/auth";
interface Message {
id: string;
text: string;
senderId: string;
senderName: string;
senderPhoto: string | null;
timestamp: number;
}
export function useChat(roomId: string, user: User | null) {
const [messages, setMessages] = useState<Message[]>([]);
const db = getDatabase();
useEffect(() => {
if (!roomId) return;
const messagesRef = query(
ref(db, `rooms/${roomId}/messages`),
orderByChild("timestamp"),
limitToLast(100)
);
// Listen for new messages in real time
const handleNewMessage = onChildAdded(messagesRef, (snapshot) => {
const data = snapshot.val();
const message: Message = {
id: snapshot.key!,
text: data.text,
senderId: data.senderId,
senderName: data.senderName,
senderPhoto: data.senderPhoto,
timestamp: data.timestamp,
};
setMessages((prev) => [...prev, message]);
});
return () => {
off(messagesRef, "child_added", handleNewMessage);
};
}, [roomId, db]);
const sendMessage = useCallback(
async (text: string) => {
if (!user || !text.trim()) return;
const messagesRef = ref(db, `rooms/${roomId}/messages`);
await push(messagesRef, {
text: text.trim(),
senderId: user.uid,
senderName: user.displayName || "Anonymous",
senderPhoto: user.photoURL,
timestamp: serverTimestamp(),
});
},
[user, roomId, db]
);
return { messages, sendMessage };
}
// Expected output: Messages sync in real time and a send function is availableChat UI Component
// src/components/ChatRoom.tsx
import { useState, useRef, useEffect } from "react";
import { useAuth } from "../hooks/useAuth";
import { useChat } from "../hooks/useChat";
export function ChatRoom({ roomId }: { roomId: string }) {
const { user } = useAuth();
const { messages, sendMessage } = useChat(roomId, user);
const [input, setInput] = useState("");
const bottomRef = useRef<HTMLDivElement>(null);
// Auto-scroll when new messages arrive
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
const handleSend = async () => {
if (!input.trim()) return;
await sendMessage(input);
setInput("");
};
return (
<div className="chat-room">
<div className="messages">
{messages.map((msg) => (
<div
key={msg.id}
className={`message ${msg.senderId === user?.uid ? "own" : "other"}`}
>
<img
src={msg.senderPhoto || "/default-avatar.png"}
alt={msg.senderName}
className="avatar"
/>
<div className="bubble">
<span className="sender">{msg.senderName}</span>
<p>{msg.text}</p>
<time>{new Date(msg.timestamp).toLocaleTimeString("en-US")}</time>
</div>
</div>
))}
<div ref={bottomRef} />
</div>
<div className="input-area">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleSend()}
placeholder="Type a message..."
/>
<button onClick={handleSend}>Send</button>
</div>
</div>
);
}Security Rules and Push Notifications
Firebase Realtime Database Security Rules
{
"rules": {
"rooms": {
"$roomId": {
"messages": {
".read": "auth != null",
".write": "auth != null",
"$messageId": {
".validate": "newData.hasChildren(['text', 'senderId', 'senderName', 'timestamp'])
&& newData.child('senderId').val() === auth.uid
&& newData.child('text').val().length > 0
&& newData.child('text').val().length <= 1000"
}
}
}
},
"users": {
"$uid": {
".read": "auth != null",
".write": "auth.uid === $uid"
}
}
}
}These rules ensure only authenticated users can read and write messages, prevent impersonation, and limit messages to 1000 characters.
Push Notifications with Cloud Messaging
// src/services/notifications.ts
import { getMessaging, getToken, onMessage } from "firebase/messaging";
export async function setupNotifications() {
const messaging = getMessaging();
// Request notification permission
const permission = await Notification.requestPermission();
if (permission !== "granted") {
console.log("Notifications were denied");
return null;
}
// Get the FCM token
const token = await getToken(messaging, {
vapidKey: process.env.NEXT_PUBLIC_FIREBASE_VAPID_KEY,
});
// Handle foreground messages
onMessage(messaging, (payload) => {
const { title, body } = payload.notification || {};
if (title) {
new Notification(title, { body, icon: "/icon-192.png" });
}
});
return token;
}
// Expected output: FCM token retrieved and push notification reception enabledFrequently Asked Questions
Q: How do I give Antigravity's agents my Firebase configuration?
A: Create an agents.md file at your project root with your Firebase project ID, database URL, and authentication methods. The agent references this file when generating code, ensuring project-specific context is accurately reflected. For more details, see "agents.md Guide".
Q: Should I use Realtime Database or Firestore?
A: For use cases where real-time responsiveness is critical, like chat apps, Realtime Database offers superior latency. If you need complex queries or large-scale data, Firestore is the better choice. This guide uses Realtime Database to prioritize low latency.
Q: How many users can I support on the free tier?
A: Firebase's Spark (free) plan supports up to 100 simultaneous connections and 1GB of data storage for Realtime Database. This is sufficient for small team chats or personal projects. As your user base grows, we recommend upgrading to the Blaze (pay-as-you-go) plan.
Q: Can the app handle offline message sending?
A: Yes. Firebase Realtime Database has built-in offline caching. By enabling enablePersistence(), messages sent while offline are queued locally and automatically synced when the connection is restored.
A Note from an Indie Developer
Wrapping Up: Maximize Productivity with Agent-Driven Development
In this guide, we covered how to build a real-time chat app using Antigravity's agents and Firebase:
- Firebase Authentication: Email/password and Google sign-in
- Realtime Database: Real-time message sync with
onChildAdded - Security Rules: Impersonation prevention and input validation
- Cloud Messaging: Push notification implementation
By providing the right context through agents.md, you can implement these complex features efficiently. For Firebase basics, check out "Firebase × Antigravity Integration Guide". For advanced agent configuration, see "Multi-Agent Orchestration Guide".