Deploying to production is always a little nerve-wracking. What happens if the new version of the site has bugs you didn’t catch? One-click rollbacks in Firebase Hosting allow you to safely go back the last working version, but how can we ensure that the problem doesn’t happen in the first place?
The answer is simple. Test your site in a mirrored production environment. Fortunately for us, the Firebase CLI makes it simple to setup and deploy to multiple environments.
Adding and switching between environments with the Firebase CLI is as simple as one command: firebase use.
firebase use
When you first initialize your Firebase Hosting project with firebase init you specify what project you want to deploy your app to. This is your default project. The use command allows you to add another project.
firebase init
use
$ firebase use --add
This command prompts you to choose from one of your existing projects:
$ firebase use --add $ ? Which project do you want to add? (Use arrow keys) my-production-project > my-staging-project my-dev-project
Select the project you want to use for a different environment, and then give it an alias. The alias can really be whatever you want, but it’s common to use aliases like “development”, “staging”, or “production”.
$ firebase use --add $ ? Which project do you want to add? (Use arrow keys) my-production-project > my-staging-project my-dev-project ? What alias do you want to use for this project? (e.g. staging) staging Created alias staging my-staging-project. Now using alias staging (my-staging-project)
Once you’ve created a new alias, it will be set as the current environment for deployment. Running firebase deploy will deploy your app to that environment.
If you want to switch to another environment, just provide the alias in the use command.
$ firebase use default # sets environment to the default alias $ firebase use staging # sets environment to the staging alias
-P
$ firebase deploy -P staging # deploy to staging alias
That’s all there is to switching environments with Firebase Hosting. If you want a guided tour on getting setup, then check out our screencast. Let us know what you think in the comments!
With Firebase Dynamic Links, we made it easier for you to share deep links into your app by providing a single link that works across iOS and Android. It can even survive through the app installation process from the App Store and on Google Play. These Dynamic Links have many uses in email or social media campaigns, but one powerful use case is to enable your users to share elements of your app with their friends. A game, for instance, can take advantage of Firebase Dynamic Links to share replays of a level, so players can challenge their friends to beat their score.
And while this kind of user-to-user sharing can be quite effective -- word of mouth is still one of the most powerful drivers of app discovery -- the process of generating a Dynamic Link and making it easy for the user to send it to their friends through an SMS or email message is a sizeable chunk of work. Work that most developers would rather be spending on other tasks like building their app.
So that's the idea behind Firebase Invites: We wanted to take that process of sharing a Dynamic Link over SMS or email, and streamline it for developers. And now that we've seen some instances of Firebase Invites out in the real world, we wanted to share a couple of tips for you on how to better take advantage of Firebase Invites to make them more compelling.
Because Firebase Invites is built on top of Dynamic Links, your users can share specific deep link information with their friends. This means that invite recipients can immediately start up your app with an experience that's relevant to the invitation they clicked on, rather than just a standard home screen.
You should take advantage of this to ensure that you're sharing specific information about your app; not just building a generic "Share this app with my friends" feature. If you have an exercise app, make sure your users can share their latest workout or their jogging route with their friends. Or if you have a ride-sharing app with a referral code, make sure your users can share that code with their friends.
Along those lines, you should make sure that whatever interface you use to start the sharing process is close to the content your users will want to share. If you simply place a generic sharing option somewhere in your settings menu, you're probably not going to see a huge lift in usage or installs. On the other hand, if you place a "Share this!" button close to that jogging route or referral code your users want to share, it becomes a lot more compelling.
When you send an email invitation from Firebase Invites, the library can automatically populate the email with images and text taken directly from your app's Play Store or App Store listing. This is certainly nice from a convenience standpoint -- you can craft a nicely-formatted email full of content with just a few lines of code.
And if you were using Firebase Invites for a generic app sharing feature, this might be fine. But if you're using Firebase Invites to share a specific piece of content like we recommend, you can customize this outgoing email by taking advantage of the setEmailHtmlContent method on Android. This gives your client the ability to supply any html you want as the content of your outgoing email message, so you can display an email message that's more relevant to the content that your users are looking to share.
setEmailHtmlContent
For example, Yummly used Firebase Invites to power a feature where users could share specific recipes with their friends. By customizing the outgoing email on the client, Yummly provided detailed descriptions and images of each recipe for every recipient. Presenting more relevant information up front is a more interesting experience for recipients, which can lead to more engagement than the standard Firebase Invite email content.
And hey, while you're at it, why not use Firebase Remote Config in conjunction with App Invites to rapidly iterate over different versions of your email content? If you wire up your app to grab its outgoing email text from Remote Config rather than hard-coding it into your app, you can try out new emails without having to update your app. With a little bit of experimentation, you can figure out what kinds of email content are the most persuasive.
One important limitation to understand with Firebase Invites is that, while your Android users can send and receive invitations at any time, and your iOS users can receive invitations freely, if you want to send an invite on iOS, your user must be signed in with Google.
For many developers, this isn't a problem; they encourage users to sign in, and Google is one of their preferred providers. But other apps might not have sign-in at all, or don't support Google sign-in, which makes Firebase Invites a little less appealing on iOS. Many developers at this point are tempted to think, "Well, that's fine. I'll just support Firebase Invites on my Android app."
The problem with this approach is that if a developer supports sending invites from an Android device, their users will still send invites to all their friends -- both iOS and Android users! And if the Firebase Invites library isn't available on the iOS side to read in this invitation, the corresponding deep link data (and all the Firebase Invite magic) gets lost.
Therefore, we recommend that even if you decide to not support the sending of invites on iOS, you should at least support the ability to receive invites on iOS. That way, your iOS users can still make sure they retrieve all the appropriate deep link information when they accept an invite, to get the full sharing experience.
It seems like nearly every app has some kind of content -- whether it's a cool replay in a game, a funny picture, or a referral code -- that would benefit from sharing. And I'm guessing that somewhere on your app's to-do list, you have a "Let users share (designated content) with friends" item that you're still planning on implementing just as soon as you have a bunch of spare cycles to figure it out.
With Firebase Invites, we can take a lot of the work out of the process and move these features from the "figure it out one day" category into the "low-hanging fruit" category. Or, at least, fruit you can reach with a short stepladder. (Produce-based metaphors were never my strong suit.) So give it a try -- be sure to peruse our documentation, and check out some of our best practices for more invitation goodness.
UPDATE: We updated this tutorial for the Firebase 3.1.0 SDK which now supports the Firebase Realtime Database and Firebase Authentication.
Here at Firebase, we’re big React fans. Firebase synchronizes application state, and React re-renders the application UI based on state changes. It’s a perfect fit.
And with React Native, app development became a lot easier for JavaScript developers. Using React Native you can build real native apps with just JavaScript. And that’s just awesome-sauce. We live in the future. Let’s get started.
If you want to dive right into the code, you can check out the final GitHub repo here. Otherwise, let's go through this step-by-step.
Getting started with React Native is fairly easy, but there are some gotchas you should be aware of. If you already have React Native set up, you can skip this section.
First, you’ll need Homebrew, which is easy to install. You’ll also need Node.js 4.0 or higher. The React Native team recommends using nvm to manage your node versions (and I do too).
Once those tools have been installed, run the following commands:
brew install watchman npm install -g react-native-cli
Then finally, you can begin your project with the CLI command:
react-native init GroceryApp # or whatever you want
Open the main folder in your favorite editor.
atom GroceryApp # if you’re into Atom
To build a React Native project, run the following command:
react-native run-ios
This should launch the Simulator, and you should see the boilerplate screen.
React Native comes with hot-reloading, which means you can make an edit to the code, index.ios.js, and then hit Cmd+R and see your changes instantly update. And if you’re an iOS developer, or really any kind of developer, you know how cool that is.
Cmd+R
With the build setup done, let’s get Firebase up and running.
React Native manages dependencies through npm. To install Firebase, run the following command at the root of the project.
npm
npm install firebase --save
Open index.ios.js and add the following line to the top:
index.ios.js
import * as firebase from 'firebase';
Then right above the component, initialize Firebase with your config values:
// Initialize Firebase const firebaseConfig = { apiKey: "<your-api-key>", authDomain: "<your-auth-domain>", databaseURL: "<your-database-url>", storageBucket: "<your-storage-bucket>",, }; const firebaseApp = firebase.initializeApp(firebaseConfig);
What’s a const? A const is a read-only reference to a value, which makes sense here, because you don’t want to ever override the value of Firebase. You can use this keyword since you’re using Node 4.0 or higher. If your editor yells at you, it’s telling you a lie.
const
One ES2015 feature isn’t enough. Rather than use React.createClass() to define components, let’s use classes.
React.createClass()
React is component-based. Which means an app is just a tree of components, starting with a root component. As of React 0.14, you can use ES2015 classes to define React components.
In index.ios.js, let’s change the component to use a class rather than React.createClass().
class GroceryApp extends Component { render() { return ( <View style="{styles.container}"> </View> ); } }
Why ES2015 classes over React.createClass()? While there is quite the debate over the subject, it boils down to a matter of taste.
The shell of the app is complete. Let’s make it look good, or at least halfway decent.
React Native uses JavaScript rather than CSS for styling. This may sound like an extreme divergence, but it’s really not all that different. To declare a set of styles, you create a StyleSheet.
StyleSheet
var styles = StyleSheet.create({ container: { backgroundColor: '#f2f2f2', flex: 1, }, });
A StyleSheet contains a set of objects that represent CSS-like styles. Then you use these styles on a React component:
<View style="{styles.container}"> I’m a container lol! </View>
So don’t worry about losing any of your CSS skills. If anything, you should really learn CSS Flexbox to make styling in React Native a breeze.
Now that you’re a pro at styling in React, let’s declare the styles for the app.
Create a file named styles.js and add the following code from this file. These are the styles we’ll be using for the app.
styles.js
You’ll notice that React Native uses CommonJS modules. At the bottom of styles.js, the StyleSheet is exported using module.exports.
module.exports
This will allow you to import these styles using require(). Open index.ios.js, and add the following line of code:
require()
const styles = require('./styles.js')
Make sure to remove the styles variable at the bottom of the file.
The styles are now in place. Let’s take a look at the app’s component structure.
The best advice I have ever read on using React, is to start off by breaking the UI into a component hierarchy. Below is a visual outline of the app's component hierarchy.
The app is made up of five components:
Create a folder named components. Each one of these components is stored in the components folder. The exception is GroceryApp, since it is contained in index.ios.js.
components
GroceryApp
Add each one of these components below to the components folder:
ActionButton.js
'use strict'; import React, {Component} from 'react'; import ReactNative from 'react-native'; const styles = require('../styles.js') const constants = styles.constants; const { StyleSheet, Text, View, TouchableHighlight} = ReactNative; class ActionButton extends Component { render() { return ( <View style={styles.action}> <TouchableHighlight underlayColor={constants.actionColor} onPress={this.props.onPress}> <Text style={styles.actionText}>{this.props.title}</Text> </TouchableHighlight> </View> ); } } module.exports = ActionButton;
ListItem.js
import React, {Component} from 'react'; import ReactNative from 'react-native'; const styles = require('../styles.js') const { View, TouchableHighlight, Text } = ReactNative; class ListItem extends Component { render() { return ( <TouchableHighlight onPress={this.props.onPress}> <View style={styles.li}> <Text style={styles.liText}>{this.props.item.title}</Text> </View> </TouchableHighlight> ); } } module.exports = ListItem;
StatusBar.js
'use strict'; import React, {Component} from 'react'; import ReactNative from 'react-native'; const styles = require('../styles.js') const { StyleSheet, Text, View} = ReactNative; class StatusBar extends Component { render() { return ( <View> <View style={styles.statusbar}/> <View style={styles.navbar}> <Text style={styles.navbarTitle}>{this.props.title}</Text> </View> </View> ); } } module.exports = StatusBar;
With the components added, let’s make a static version of the app.
In index.ios.js, add the following imports to the top of the page:
import React, {Component} from 'react'; import ReactNative from 'react-native'; import * as firebase from 'firebase'; const StatusBar = require('./components/StatusBar'); const ActionButton = require('./components/ActionButton'); const ListItem = require('./components/ListItem'); const styles = require('./styles.js');
Then add the following snippet of code:
_renderItem(item) { return ( <ListItem item="{item}" onpress="{()" ==""> {}} /> ); } render() { return ( <View style="{styles.container}"> <StatusBar title="Grocery List"> <ListView datasource="{this.state.dataSource}" renderrow="{this._renderItem.bind(this)}" style="{styles.listview}/"> <ActionButton title="Add" onpress="{()" ==""> {}} /> </View> ); }
The render() function is the main view of the app, and _renderItem() sets the individual items in the list.
render()
_renderItem()
Next, create a constructor for the root component, GroceryApp.
constructor(props) { super(props); this.state = { dataSource: new ListView.DataSource({ rowHasChanged: (row1, row2) => row1 !== row2, }) }; }
The component has a special property called state, which manages the entire data flow of the application. We’ll dive deeper in the next section.
state
The app’s state is a ListView.DataSource, which is a class that provides efficient data processing to a ListView component. The next step is to display this data.
ListView.DataSource
ListView
Each component has a lifecycle, where certain functions are called at important events. When the component has first been rendered, componentDidMount() is called. This is where we want set any initial state of the app.
componentDidMount()
componentDidMount() { this.setState({ dataSource: this.state.dataSource.cloneWithRows([{ title: 'Pizza' }]) }) }
Build and run the app, and you should see the following static app.
Each one of these components just simply displays data, or sets a callback function for a touch.
The key thing to understand is that these components are not state-ful. They have properties that are set by their root component, GroceryApp. To begin to understand React, you have to learn how to manage state.
State is simply data that can change. This data is called state, because it represents the "state" of your application. If this data changes, your application will likely look different, hence being in a different "state". State is usually things like a list of todos, or an enabled/disabled button.
The root component will serve as the holder of state. State changes start at the root component, which is then responsible for updating the properties of its child components.
Component properties are immutable, meaning they can’t be modified. So, if you can’t modify the properties, how do they ever change? You re-render, the application by calling setState(), like seen in componentDidMount().
setState()
The setState() function is special because every time it is called, it will attempt to re-render the entire app. This means that if a child property is different from the last state, it will be re-rendered with the new value.
This is where React fits perfectly with Firebase. Because the Firebase database synchronizes application state across multiple devices, and React efficiently re-renders application state changes.
Create Realtime Database reference as a property in the constructor:
this.itemsRef = firebaseApp.database().ref();
Then, add the following function to the GroceryApp component:
listenForItems(itemsRef) { itemsRef.on('value', (snap) => { // get children as an array var items = []; snap.forEach((child) => { items.push({ title: child.val().title, _key: child.key }); }); this.setState({ dataSource: this.state.dataSource.cloneWithRows(items) }); }); }
This function creates a value listener for all grocery items. Whenever an item is added, changed, or removed, you’ll get the entire result set back as a DataSnapshot, from the Firebase SDK. Using the DataSnapshot, you call forEach(child), which iterates through all of the children and adds them to an array as a grocery list item. Notice in the .forEach function, a _key property is created from the DataSnapshot's .key() value. This makes life much easier when doing data operations later down the line.
DataSnapshot
forEach(child)
.forEach
_key
.key()
Once the array is populated, you’ll update the dataSource property on state using dataSource.cloneWithRows(items). The cloneWithRows() function is just a convenience method for creating a new ListView.DataSource based on the same DataSource previously defined.
dataSource.cloneWithRows(items)
cloneWithRows()
DataSource
Next, you write the listen up to componentDidMount():
componentDidMount() { this.listenForItems(this.itemsRef); }
Build and run the app. You should see an empty page, but try adding a few items using the Firebase App Dashboard or the super awesome data viewer Vulcan, and you’ll see it update in realtime!
This is awesome, but you’d rather have the "Add" button working. Let’s tackle that in the next section.
When the ActionButton is tapped, an alert should pop up prompting the user to enter an item. The AlertIOS API is what you’ll use to create this alert box.
ActionButton
AlertIOS
Add the following function to the GroceryApp component:
_addItem() { AlertIOS.prompt( 'Add New Item', null, [ { text: 'Add', onPress: (text) => { this.itemsRef.push({ title: text }) } }, ], 'plain-text' ); }
The AlertIOS API is quite extensible when it comes to building alerts. The first two parameters are simple, they're just the title of the alert box and an optional message. The third parameter is the meat of the API. Here you create an array that specifies the buttons available to the user. Each button can have a text, style, and an onPress callback function. The last parameter is type the of input, whether plain-text or secure-text.
text
style
onPress
plain-text
secure-text
To add an item, create an object in the buttons array. This object can add items in the onPress callback. The callback returns the text the user has entered. Use this text to .push() a new child onto the /items location.
.push()
/items
Next, you'll need to update render() to assign the onPress property of the ActionButton:
<ActionButton title="Add" onpress="{this._addItem.bind(this)}"> </ActionButton>
Build and run. When you tap add, and enter an item title, you should see it update on the list.
Awesome, but what’s a grocery list that can’t complete items?
The user can complete an item by tapping on it to open up an alert box. If they press the "Complete" option, you can delete it from the list.
Modify _renderItem(item) to include an onPress callback:
_renderItem(item)
_renderItem(item) { const onPress = () => { AlertIOS.prompt( 'Complete', null, [ {text: 'Complete', onPress: (text) => this.itemsRef.child(item._key).remove()}, {text: 'Cancel', onPress: (text) => console.log('Cancel')} ], 'default' ); }; return ( <ListItem item="{item}" onpress="{onPress}"> ); }
To "complete" an item, you'll need to delete it from the Firebase database. Using .child(key), you can drill down into a specific item in the list. The onPress callback is a closure, therefore it has access to the outer environment which contains the item parameter. This is where the _key property comes in hand.
.child(key)
item
When the "Complete" option is tapped, you can find the specific child by using the _key property on item. Then you can call .remove() to delete the item in the Firebase database.
.remove()
Build and run, yet again. Tap any ListItem , tap "Complete", and you should see it removed from the list.
ListItem
Check out the completed app on Github. And, if you’re feeling generous, we would love a star. Feel free to fork the repo, and even send in a PR if you want.
If you’re running into issues, open up a question on Stackoverflow, we monitor the Firebase tag closely, or drop a line in our community Slack Team.
refHandle = postRef.observeEventType(FIRDataEventType.Value, withBlock: { (snapshot) in let postDict = snapshot.value as! [String : AnyObject] // … })
firebase.database().ref('posts/' + postId).on('value', function(snapshot) { var post = snapshot.val(); // … });
mPostReference.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { // Get Post object and use the values to update the UI Post post = dataSnapshot.getValue(Post.class); // … } @Override public void onCancelled(DatabaseError databaseError) { // Getting Post failed, log a message Log.w(TAG, "loadPost:onCancelled", databaseError.toException()); // … } });
var storageRef = firebase.storage.ref("folderName/file.jpg");
let storageRef = FIRStorage.reference().child("folderName/file.jpg")
StorageReference storageRef = FirebaseStorage.getInstance().reference().child("folderName/file.jpg");
var storageRef = firebase.storage.ref("folderName/file.jpg"); var fileUpload = document.getElementById("fileUpload"); fileUpload.on(‘change’, function(evt) { var firstFile = evt.target.file[0]; // get the first file uploaded var uploadTask = storageRef.put(firstFile); });
let storageRef = FIRStorage.reference().child("folderName/file.jpg"); let localFile: NSURL = // get a file; // Upload the file to the path "folderName/file.jpg" let uploadTask = storageRef.putFile(localFile, metadata: nil)
StorageReference storageRef = FirebaseStorage.getInstance().reference().child("folderName/file.jpg"); Uri file = Uri.fromFile(new File("path/to/folderName/file.jpg")); UploadTask uploadTask = storageRef.putFile(file);
<input type="file" />
UploadTask
DownloadTasks
var storageRef = firebase.storage.ref("folderName/file.jpg"); var fileUpload = document.getElementById("fileUpload"); fileUpload.on(‘change’, function(evt) { var firstFile = evt.target.file[0]; // get the first file uploaded var uploadTask = storageRef.put(firstFile); uploadTask.on(‘state_changed’, function progress(snapshot) { console.log(snapshot.totalBytesTransferred); // progress of upload }); });
let storageRef = FIRStorage.reference().child("folderName/file.jpg"); let localFile: NSURL = // get a file; // Upload the file to the path "folderName/file.jpg" let uploadTask = storageRef.putFile(localFile, metadata: nil) let observer = uploadTask.observeStatus(.Progress) { snapshot in print(snapshot.progress) // NSProgress object }
StorageReference storageRef = FirebaseStorage.getInstance().reference().child("folderName/file.jpg"); Uri file = Uri.fromFile(new File("path/to/images/file.jpg")); UploadTask uploadTask = storageRef.putFile(file); uploadTask.addOnProgressListener(new OnProgressListener() { @Override public void onProgress(UploadTask.TaskSnapshot snapshot) { System.out.println(snapshot.getBytesTransferred().toString()); } });
var storageRef = firebase.storage.ref("folderName/file.jpg"); storageRef.getDownloadURL().then(function(url) { console.log(url); });
let storageRef = FIRStorage.reference().child("folderName/file.jpg"); storageRef.downloadURLWithCompletion { (URL, error) -> Void in if (error != nil) { // Handle any errors } else { // Get the download URL for 'images/stars.jpg' } }
StorageReference storageRef = FirebaseStorage.getInstance().reference().child("folderName/file.jpg"); storageRef.getDownloadUrl().addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Uri uri) { // Got the download URL for 'users/me/profile.png' } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception exception) { // Handle any errors } });
// Only a user can upload their profile picture, but anyone can view it service firebase.storage { match /b/<bucket>/o { match /users/{userId}/profilePicture.png { allow read; allow write: if request.auth.uid == userId; } } }
import ReactNative from "react-native"; import * as firebase from 'firebase'; // Initialize Firebase const firebaseConfig = { apiKey: "<YOUR-API-KEY>", authDomain: "<YOUR-AUTH-DOMAIN>", databaseURL: "<YOUR-DATABASE-URL>", storageBucket: "<YOUR-STORAGE-BUCKET>" }; firebase.initializeApp(firebaseConfig);
signInWithPopup()
signInWithRedirect()
linkWithPopup()
linkWithRedirect()
signInWithCredential()
File
Blob
import * as firebase from 'firebase'; firebase.initializeApp({ databaseURL: "<YOUR-DATABASE-URL>", });