Realtime Data with Firebase

10m

Intermediate

In this guide, you'll learn how to make your prototypes feel more like real apps than ever before. We'll be using Firebase and the FirebaseData package from the Framer Store. This package allows you to seamlessly create and store variables in your prototype that sync in real time across multiple devices and sessions.

When you're finished, you should have a solid base to create prototypes such as:

  • A functional real-time chat
  • A to-do list that syncs across devices and sessions
  • A controller prototype that controls another prototype

What FirebaseData allows you to do is create a real-time synced data object between your prototypes. If you're unfamiliar with the data object, that’s ok—we’ll be covering it later on.

We're going to be hooking up a chat prototype that will allow you to chat between multiple previews and devices.

Getting Started

We’ll be making this project with Framer X. If you don’t have it already, download a copy and enjoy the 14-day trial.

The main concepts of Framer X that you'll be using are overrides and the data object, but don't worry if you're not familiar with them already as we'll go over the basics of using them.

Installing FirebaseData from the Store

The first thing you'll need is a package called FirebaseData from the Store.

Setting Up Your Canvas

After installing the package there should be three new components in your components panel: ChatTemplate, ChatInput and ChatThread. We’re now going to learn how to hook them together to create a working chat prototype.

  1. Place ChatTemplate on the canvas
  2. Select the ChatTemplate and add a frame around it with ⌘-return
  3. Place ChatInput and ChatThread on the canvas beside your ChatTemplate
  4. Drag ChatThread onto the corresponding spot on the ChatTemplate
  5. Drag ChatInput onto the corresponding spot on the ChatTemplate

Now that the main components of our chat prototype are assembled, we’ll learn how to wire them together with overrides.

Connecting Our Components with Overrides

Overrides are functions that allow you to control and connect any frame or component from the canvas by setting their properties. In this case they’re going to enable us to connect our message thread and message input by enabling them to read & edit a global list of chat messages called messages stored in a data state.

Creating an Overrides File

Let’s create a new file where we’ll write our overrides.

  1. Click on the ChatInput instance to select it.
  2. In the properties panel, click the section labeled Override.
  3. In the Files dropdown, select New File.
  4. Now click the Edit Code button.

This will open up a new overrides file named App.tsx, which we'll be using for the remainder of this guide.

Creating our Data State

Let’s start this file by creating a data object to hold our prototype’s state. In this case it will hold our chat messages in an array.

  1. At the top of our App.tsx file, add Data to the list of imports from the Framer Library.
  2. Delete the rest of the code in the file.
  3. Create a new data object as shown below.
import { Override, Data } from "framer"
const data = Data({
messages: []
})
COPY

Creating Overrides

The next step is to create overrides for our ChatThread and ChatInput that allow them to interact with our messages array in our data object. There are two things we need to achieve:

  1. Enable the chat thread to read and display our messages array
  2. Enable the chat input to add new messages to the end of the messages array

Override #1: Passing Messages to Chat Thread

Overrides allow us to set the props (short for properties) of any component on the canvas. We do this by creating an object with the names of the properties we want to set and the values to set for each of them.

In this case our ChatThread component accepts a prop called messages, and we're going to set it to be the value of the messages array from inside our data state. Once we add this override to our ChatThread component, it will be able to read and display the messages in our messages array.

// App.tsx
export function ChatThread(): Override {
return {
messages: data.messages,
}
}
COPY

Override #2: Adding Messages from Chat Input

To allow our chat input to add new messages to our messages array, we're going to give it an addMessage function as a prop (a property) via an override. Creating a function and passing it down to a component for it to use like this is called a callback function. This essentially means we're giving the component a helper function, and it will be used inside the component to 'call back' to the override and allow it to add a message to the messages array.

The way this works is that inside of the ChatInput component it's set up to expect a function called addMessage as a prop. This function is called whenever a new message is sent. The function inside our ChatInput component looks something like this:

// ChatInput.tsx
function sendMessage() {
const message = {
text: inputValue,
author: getUserID(),
timestamp: getDate(),
}
props.addMessage(message)
}
COPY

When the send button (or enter) is pressed, a new message object is created with three pieces of information in it:

  • Text: The text that's currently inside of the input field, which becomes the content of the message
  • Author: A randomly set identification number for differentiating message senders
  • Timestamp: The time an which the message is sent, which is used for animating in newly sent messages

Once the message object is created, the component looks for a function called addMessage in its props and passes it the newly created message object. This is where the override function that we're creating is going to be used. Let's create that function in our override now.

We know that addMessage is going to be given a new message object as an argument. From there we will assign the messages array from our data state to be equal to an array of the current contents of our messages array, but with the new message from our input components added to the end of it. In code that will look like this: data.messages = [...data.messages, message], which uses an array destructuring assignment. The full override will look like this:

// App.tsx
export function ChatInput(): Override {
return {
addMessage(message) {
data.messages = [...data.messages, message]
},
}
}
COPY

Once we apply this override to our ChatInput component it will then be able to add messages to the messages array.

Adding Overrides to Components

Now that our overrides are complete, we’re going to add them to our ChatThread and ChatInput components on the canvas. Once they're added, the components will be able to interact with the data object where the messages are stored. Here's what our App.tsx file should look like right now:

import { Override, Data } from "framer"
const data = Data({
messages: []
})
export function ChatThread(): Override {
return {
messages: data.messages,
}
}
export function ChatInput(): Override {
return {
addMessage(message) {
data.messages = [...data.messages, message]
},
}
}
COPY

Back in the canvas, we can apply each of these overrides to their respective components.

  1. Click on the ChatInput instance to select it.
  2. In the overrides section of the properties panel, click on the override dropdown
  3. Select the ChatInput override from the dropdown
  4. Repeat for the ChatThread component with the ChatThread override

Checkpoint: Testing Local Chat

Click on the preview button in the top right of the Framer window (or use ⌘-P). If you see an empty chat window in the preview, that's a good sign. Send some messages using the ChatInput and see if they show up in the ChatThread.

If your preview looks something like this, you're on the right track.

Syncing Up with Firebase

Now we’re going to take this chat app to the next level by making it real. To do this, we’re going to use the FirebaseData package in code using the Firebase service from Google. Firebase is essentially a plug-and-play backend for your apps, and powers the FirebaseData package.

Let’s start by importing FirebaseData into our code file.

  1. At the top of our App.tsx file, add a new line below our Framer import
  2. On this line import FirebaseData as shown below
import { Override, Data } from "framer"
import { FirebaseData } from "firebase-data"
const data = Data({
messages: []
})
COPY

Framer might think there's an error on our new line, but everything will still work properly. For now, we can tell Framer to ignore it by putting //@ts-ignore on the previous line.

Now we need what's called a config object. This is a Javascript object that tells Firebase what Firebase account and project we'll be using for our prototype. This is why my chat messages will only be shown in my prototype, and your messages in yours.

Setting Up Firebase

The Firebase feature we're going to be using is called the realtime database. The following steps will walk you through setting up a Firebase real-time database and retrieving your Firebase config information, which gives your prototype access to your Firebase project.

  1. Go to https://firebase.google.com and click Get started
  2. Create a project with any name you like (skip analytics if prompted)
  3. Under Develop in the left bar menu, click Database
  4. Scroll down to the “Realtime Database” section and click Create Database
  5. When prompted for “Security Rules in Realtime Database”, select Start in test mode
  6. Once that page loads, go to Project Settings in the top of the left menu
  7. Within the General tab of the settings, scroll to the section titled “Your apps” and click on the web option (</> icon) to start a new web project (Un-check the Firebase hosting checkbox)
  8. Back in the console, scroll down to your Your apps and under the “Firebase SDK snippet” heading, select Config
  9. Copy the contents of the code block for the next step

Bringing it All Together

The code you copied from the Firebase console is called a firebaseConfig object. We’re going to paste it into our App.tsx file right below the import statements.

import { Override, Data } from "framer"
import { FirebaseData } from "firebase-data"
// Your config should look something like this
const firebaseConfig = {
apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
authDomain: "framer-firebase.firebaseapp.com",
databaseURL: "https://framer-firebase.firebaseio.com",
projectId: "framer-firebase",
storageBucket: "",
messagingSenderId: "xxxxxxxxxxxx",
appId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
}
const data = Data({
messages: []
})
COPY

The next and final step is to power-up our data object into a FirebaseData object. To do this, we’re going to change the line where we set up our data object and swap it for a FirebaseData object.

  1. Change the word Data to FirebaseData
  2. Add in firebaseConfig as the first argument to FirebaseData
// Before
const data = Data({
messages: []
})
COPY
// After
const data = FirebaseData(firebaseConfig, {
messages: []
})
COPY

That’s it! Your final App.tsx file should look something like this:

import { Override, Data } from "framer"
import { FirebaseData } from "firebase-data"
const firebaseConfig = {
apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
authDomain: "framer-firebase.firebaseapp.com",
databaseURL: "https://framer-firebase.firebaseio.com",
projectId: "framer-firebase",
storageBucket: "",
messagingSenderId: "xxxxxxxxxxxx",
appId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
}
const data = FirebaseData(firebaseConfig, {
messages: []
})
export function ChatThread(): Override {
return {
messages: data.messages,
}
}
export function ChatInput(): Override {
return {
addMessage(message) {
data.messages = [...data.messages, message]
},
}
}
COPY

If you open the preview window, you should be able to chat like you could before. However, if you start a Live Preview and open it in the browser or send the link to someone, they should also see your messages. Everyone viewing the prototype should be able to send and receive messages as they would in a real chat app.

You've successfully hooked up a working chat prototype in Framer X and have learned the basics of using real-time data with Firebase!

Optional: A Peek Inside FirebaseData

If you're curious about how the FirebaseData package works, the following is a quick look behind the curtain.

The feature of Firebase that you set up earlier is called the realtime database, which is a service that gives you a place to store and sync app data in real time. While it's possible to use the Firebase real-time database without the use of the FirebaseData package, FirebaseData abstracts away plenty of tedious code and utility functions that you need each time you wanted to use the Firebase database on its own.

Essentially what FirebaseData is doing is extending the functionality of the default Framer data object, and syncing it to a Firebase database behind the scenes. The 'master' data object is stored on your Firebase database and becomes the source of truth for all FirebaseData objects you have hooked up in your prototypes.

What this means is that when your prototype starts, it starts with the default data object that is defined in your overrides file (messages: [] in this case) while in the background it connects to the Firebase database and grabs the latest version of the data object properties that it has stored. Once it has the most up-to-date data object from Firebase, it syncs it to your prototype. This is when any past messages in your chat prototype will show up.

The last step that makes Firebase and FirebaseData special is that when you change one of properties on your FirebaseData object (eg. adding a new message to data.messages), the Firebase database instantly updates and syncs the update with every device/prototype that is connected to it with FirebaseData. In our chat app, this is what happens when we add a new message in one prototype, and it shows up in real time in the rest of the prototypes. The magic part is that this all happens in a matter of milliseconds, making your prototypes feel instantly synced.

Optional: Using FirebaseData Without the Store Package

Alternatively, power users can install the package from NPM with: yarn add firebase-data

From there, you can then use it the same way as above by importing it at the top of your overrides file. More info on this method of installation can be found here: https://www.framer.com/support/using-framer-x/npm-packages/

Wrap Up

Hopefully this guide helps open the door to all the possibilities of using real-time data in your future prototypes. I'll be showing off some different uses of FirebaseData soon on Twitter—please feel free to reach me there if you have any questions. If there are any issues with the FirebaseData package, please file an issue or submit a PR on Github.