We've all been in this situation before: you work tirelessly to build a great app, you spend countless hours beta testing and fixing bugs, and you even put together a comprehensive marketing plan in time for your app's launch. Months of anticipation finally culminate when you submit your app to the app store… but instead of celebrating the new release, you find out that your app didn't meet the right criteria for listing so it got rejected 😱.
There's nothing quite as deflating as tripping at the finish line.
App store guidelines can be complicated and hard to remember because they're constantly changing, but developers still need to follow them in order to get their apps approved. Sometimes, these guidelines get overlooked or lost in the shuffle of all the other important pre-launch tasks. To help you avoid unnecessary delays and rejection, we're excited to introduce fastlane precheck for iOS apps! You can count on us to remember the rules so you can submit your app with confidence.
Often, apps get rejected from app stores because of simple, solvable issues with their metadata (such as title, description, changelog, keywords, etc.). Once rejected, you have to resubmit your app and it can take days before it's reviewed again. This wastes time and can throw off your schedule.
Instead of risking rejection or painstakingly auditing your own metadata, you can now use fastlane precheck to save you the time and hassle. fastlane will review your app for the most common causes of rejection 👍.
For example, we'll check your metadata to ensure it follows iOS app store rules such as:
If we detect any issues, we'll flag them for you so you can resolve them before you submit your app to the app store. And since fastlane can automatically download your app's metadata, this whole process takes only seconds!
fastlane precheck is the first new tool we've added to our app automation toolkit since joining Google - and we have even more exciting updates planned! Now that we've settled into our new home, we can't wait to move fastlane forward with our passionate community of iOS and Android developers.
In the meantime, install fastlane or update your version of fastlane (just run `fastlane update_fastlane`) to try precheck and submit your app with confidence and peace of mind 🚀.
Many Firebase developers focus on their apps alone, usually for Android, iOS, or the Web (or all three!). If you're like me, and you spend most of your time on the client side, it can be a hassle to also deal with any backend components. Now that we have Cloud Functions for Firebase, it's super easy and fun to write and deploy backend code in JavaScript without having to manage servers. Personally, my comfort zone is with Android. So, to get acquainted with Cloud Functions, I spent some time brushing up on my JavaScript and learning node.js, which Cloud Functions uses to run backend code.
As part of that period of ramping up, I learned a new way of managing asynchronous work in JavaScript. If you've had experience working with JavaScript in the browser or with Cloud Functions, you may already be familiar with a class called Promise. A promise is a very common way to handle async work. The closest concept for Android developers is the Task API that you sometimes use when dealing with Firebase APIs.
A promise lets you respond to a unit of work that's executing asynchronously, such as a database write or a network request. Cloud Functions understand promises; if you want a function to stay alive during async work, you can do this by returning a promise from the function (except for HTTP/S triggers, which require a response sent to the client). When you return a promise, be sure to return a promise that is "resolved" after all asynchronous work performed in that function is fully complete. Many developers have found this video from the Firebase channel on YouTube very helpful for understanding how promises work with Cloud Functions:
In the video, Jen describes two main ways of dealing with promises. First, there's using the then() method on a promise to chain items of work in sequence, where the next item of work depends on the results from the previous item. Second, there's Promise.all() which creates a new promise that resolves after multiple items of work happening in parallel are all complete. Between these two mechanisms, you can manage most work that normally occurs in a function. When the work is complete, you return the final promise from the function so that the Cloud Functions environment knows when to clean up.
then()
Promise.all()
For simple functions, this works out pretty easily. But many of you (including myself) have discovered that complicated functions can be really difficult to manage when there's a lot of work (with lots of promises) going on. One of the symptoms of mismanaged promises is seeing the following error in the function log in the Firebase console:
Error: read ECONNRESET
This error could mean a few different things. In some cases it could be a bug in a client library. In other cases, it's a symptom of a common mistake when dealing with promises.
If a function is terminated before its network connections are finished, that means those open connections could be forced to close, leaving an ECONNRESET in the log. Here's a situation when that can occur.
ECONNRESET
Imagine you want to start three items of async work, and the work is kicked off in a function called doSomeAsync(), which returns a promise that's resolved upon completion, and you return the promise from the third task like this:
doSomeAsync()
doSomeAsync(x) doSomeAsync(y) return doSomeAsync(z)
Returning a Promise from the function is good. But Cloud Functions needs a promise that resolves when all of the work is fully complete. If we don't have a guarantee that the last item of work will always finish after the other two, then Cloud Functions may clean up the function prematurely, causing problems. Instead, we need to combine all of the promises from all of the work into a new promise that resolves only after all of the others resolve, like this:
const promise_x = doSomeAsync(x) const promise_y = doSomeAsync(y) const promise_z = doSomeAsync(z) return Promise.all([promise_x, promise_y, promise_z])
If you're not tracking every single item of asynchronous work that you kick off in a function, you could see the dreaded ECONNRESET error in your log, and things may not work the way you expect. It really can take some diligence to keep all your promises!
If you want to see an example of a more complicated function, I have an open source tic-tac-toe web game that I wrote for a session I gave at Google I/O '17 that talks about how it was built entirely with Firebase.
For more tutorials and content about Cloud Functions, and other Firebase products, be sure to check out the Firebase channel on YouTube and the Firebase Blog.
Just two weeks ago, we hosted our party at WWDC to celebrate app development with the iOS community. We were thrilled to meet many of you including tons of talented developers from Apple, Uber, Spotify, Tinder, and more. With over a thousand RSVPs, it was amazing to see the excitement among the community!
Since we can't travel back in time, here are some highlights so you can relive the night of fun.
We love our customers - so what better way to show our appreciation then rolling out the red carpet? We also made sure to have friendly hosts to greet you at the door and security guards as rigorous as an IDS.
We noticed most of you felt right at home at Forager. It was great to see all of you mingle with each other and with us to and discuss app development and all the WWDC announcements of the day. Whether we found you at the stand up tables, or hanging out on the blue and yellow sofas: everyone was having fun!
Thank you to all those who submitted creative names for our cocktails and congrats to the winners! The Minty Multithread and Nulltonic Expression were hits and went great with the finger-licking good appetizers.
Many of you shared your passion projects with other developers on the flat screens throughout the venue. And we couldn't keep ourselves from grinning at some of the pics from the paparazzi style photo booth. See all the photo booth moments here.
Over half of you played AppShip3000 - our flagship cooperative multiplayer game. You either flexed your knowledge muscle, or learned a thing or two about Firebase. And many of you also saw one of our engineers live-code an app. Was it a hotdog? Or not? What do you think?
Since it's dub dub, we crafted custom designed iPhone protective cases for you to take home (in case cocktails and holding-onto-phones don't mix). Stylish tees included as usual.
Thank you to all those who joined us for the night - we were glad to share all the laughter with you. Check out our full photo album. (photos credit: Yvonne TNT)
Here's to next year!
In their words:
Firebase party is fancy one. there is a line 🤳💪🤸♀️🎉 pic.twitter.com/gsIyoM5NP1— Marcin Krzyzanowski (@krzyzanowskim) June 6, 2017
Firebase party is fancy one. there is a line 🤳💪🤸♀️🎉 pic.twitter.com/gsIyoM5NP1
Cool setup at the Firebase/Fabric party 🎉 pic.twitter.com/pTyC3QBmT9— Adam Oxner (@adoxner) June 6, 2017
Cool setup at the Firebase/Fabric party 🎉 pic.twitter.com/pTyC3QBmT9
Thanks @Firebase + @fabric for hosting tonight. We @apponboardinc had a blast meeting all the great developers! #wwdc2017 #wwdc pic.twitter.com/9V0eMaK0hR— Matt Chin (@mattchinni) June 6, 2017
Thanks @Firebase + @fabric for hosting tonight. We @apponboardinc had a blast meeting all the great developers! #wwdc2017 #wwdc pic.twitter.com/9V0eMaK0hR
Amazing part thrown by @Firebase #wwdc17 #firebaseparty pic.twitter.com/HbE5EqcEVg— NIkant Vohra (@nikant_vohra) June 6, 2017
Amazing part thrown by @Firebase #wwdc17 #firebaseparty pic.twitter.com/HbE5EqcEVg
Firebase Party was 🔥 https://t.co/MWmnQgw9n9— /Users/tomn/ (@tomn94) June 6, 2017
Firebase Party was 🔥 https://t.co/MWmnQgw9n9
#firebase party 🔥#WWDC17 https://t.co/g4EbybfgxW— Adrien Coye (@acoye) June 6, 2017
#firebase party 🔥#WWDC17 https://t.co/g4EbybfgxW
Icing on the #firebaseparty cake - Firebase and Fabric-branded M&Ms pic.twitter.com/aXhGXBk5i7— Dave Air (@dave_air) June 6, 2017
Icing on the #firebaseparty cake - Firebase and Fabric-branded M&Ms pic.twitter.com/aXhGXBk5i7
As the developer or the administrator of an app built on Firebase, you may need to perform various user management tasks. These include:
The Firebase Admin SDK provides a powerful API for performing these kinds of user management tasks from privileged environments. Using the Admin SDK, you can program these capabilities directly into your admin consoles, dashboards and other backend services. Unlike the Firebase client SDK, the Admin SDK is initialized with a service account credential, which grants the SDK elevated privileges necessary to perform user management operations.
This is not a brand new feature. The Firebase Admin Node.js SDK has had a user management API for a while. However, we are happy to announce that this API is now also available in our Admin Java SDK starting from version 5.1.0. This is one of the most requested features for the Admin Java SDK, and we are certain many Firebase app developers are going to find it useful.
The Java user management API closely mirrors its Node.js counterpart. Specifically, it consists of five new methods:
getUser()
getUserByEmail()
createUser()
updateUser()
deleteUser()
Following code snippet shows how to use some of these new methods in practice. In this example we retrieve an existing user account, and update some of its attributes:
final FirebaseAuth auth = FirebaseAuth.getInstance(); Task task = auth.getUserByEmail("editor@example.com") .addOnSuccessListener(userRecord -> { System.out.println("Successfully fetched user data: " + userRecord.getUid()); UpdateRequest update = userRecord.updateRequest() .setDisabled(false) // Enable the user account .setEmailVerified(true); // Set the email verification status auth.updateUser(update); }) .addOnFailureListener(e -> { System.err.println("Error fetching user data: " + e.getMessage()); });
You can learn more about the Firebase user management API from the Admin SDK documentation. Additionally, head over to our Github repo to see how it is implemented. (Yes, it's all open source!). You can also help us further improve this API by providing us feedback and reporting issues. As always, we are open to all sorts of contributions including pull requests to our codebase. Happy coding with Firebase!
Firebase Dynamic Links give you a single link that can send users either to your iOS or Android app, if they have it installed, or to the appropriate listing in the App Store or on Google Play, if they don't. Even better than that, Dynamic Links will deep link your users to the content they were looking for, even if they had to install the app in the process.
We've been working hard over the last year to make this experience smoother and more powerful for both developers and users, and we're happy to bring the latest set of improvements to developers in our iOS and Android SDKs.
Creating Dynamic Links through the Firebase Console was great for promotional campaigns, but we heard from our developers that they needed to be able to create user-to-user sharing campaigns programmatically from within their app.
To help you do just that, we've added support for generating both long and short dynamic links to the iOS and Android Firebase SDKs, which makes it quicker and easier to support these kind of use cases:
guard let deepLink = URL(string: "https://mydomain.com/page?param=value") else { return } let components = DynamicLinkComponents(link: deepLink, domain: domain) let iOSParams = DynamicLinkIOSParameters(bundleID: bundleID) iOSParams.minimumAppVersion = minVersion components.iOSParameters = iOSParams // Build the dynamic link let link = components.url // Or create a shortened dynamic link components.shorten { (shortURL, warnings, error) in if let error = error { print(error.localizedDescription) return } // TODO: Handle shortURL. }
String deepLink = "https://mydomain.com/page?param=value"; DynamicLink.Builder builder = FirebaseDynamicLinks.getInstance() .createDynamicLink() .setDynamicLinkDomain(domain) .setAndroidParameters(new DynamicLink.AndroidParameters.Builder() .setMinimumVersion(minVersion) .build()) .setLink(deepLink); // Build the dynamic link DynamicLink link = builder.buildDynamicLink(); // Or create a shortened dynamic link builder.buildShortDynamicLink() .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(ShortDynamicLink shortDynamicLink) { // } });
We've also rebuilt the Android API from the ground up to make it easier to handle incoming Dynamic Links into your app, with the new FirebaseDynamicLinks class. You can add the new library by adding the following to your build.gradle:
compile "com.google.firebase:firebase-dynamic-links:11.0.0"
Then processing an incoming Dynamic Link is easy in your launched activity:
FirebaseDynamicLinks.getInstance().getDynamicLink(getIntent()) .addOnSuccessListener( new OnSuccessListener() { @Override public void onSuccess(PendingDynamicLinkData data) { if (data == null || data.getLink() == null) { // No FDL pending for this app, don't do anything. return; } Intent launchIntent = data.getUpdateAppIntent(MainActivity.this); if (launchIntent != null) { startActivity(launchIntent); // launch upgrade flow. } Uri deepLink = dynamicLink.getLink(); String myAppItemId = deepLink.getQueryParameter("myAppItemId"); // TODO(developer): Display content for myAppItemId here! } });
As always, if you have any questions or feedback on the new API, please reach out through any of the channels on our support page.
.
Three years ago, we launched Firebase Hosting to make it easier for developers to deliver fast, engaging experiences on the web. Two months ago, we launched the beta of Cloud Functions for Firebase to let developers write custom backend logic without having to worry about servers or infrastructure. More recently at Google I/O, we brought Firebase Hosting and Cloud Functions together to provide a flexible set of tools to build Progressive Web Apps with world-class scale and performance.
You can connect an HTTPS Cloud Function to your Firebase Hosting app by adding a rewrite to the firebase.json configuration for your project:
firebase.json
{ "hosting": { "rewrites": [ {"source": "/function/**", "function":"myFunction"} ] } }
Once connected, matching requests seamlessly proxy to your Cloud Function (in the example above, a function named myFunction). This one-line change enables exciting new capabilities for Firebase Hosting users including:
myFunction
For Cloud Functions users, you can now run functions on an SSL-secured custom domain and get powerful caching to avoid unnecessary executions.
To get started using Cloud Functions on Firebase Hosting for your own Firebase project, take a look at our documentation. You can also learn more in our I/O session: Building Fast Web Experiences with Firebase Hosting.
In apps that allow users to post public content - for instance in forums, social networks and blogging platforms - there is always a risk that inappropriate content could get published. In this post, we'll look at ways you can automatically moderate offensive content in your Firebase app using Cloud Functions.
The most commonly used strategy to moderate content is "reactive moderation". Typically, you'll add a link allowing users to report inappropriate content so that you can manually review and take down the content that does comply with your house rules. You can better prevent offensive content from being publicly visible and complement your reactive moderation by adding automated moderation mechanisms. Let's see how you can easily add automatic checks for offensive content in text and photos published by users on your Firebase apps using Cloud Functions.
We'll perform two types of automatic content moderation:
Text moderation where we'll remove swearwords and all shouting (e.g. "SHOUTING!!!").
Image moderation where we'll blur images that contain either adult or violent content.
Automatic moderation, by nature, needs to be performed in a trusted environment (i.e. not on the client), so Cloud Functions for Firebase is a great, natural fit for this. Two functions will be needed to perform the two types of moderation.
The text moderation will be performed by a Firebase Realtime Database triggered function named moderator. When a user adds a new comment or post to the Realtime Database, a function is triggered which uses the bad-words npm package to remove swear words.We'll then use the capitalize-sentence npm package to fix the case of messages that contain too many uppercase letters (which typically meaning users are shouting). The final step will be to write back the moderated message to the Realtime Database.
moderator
To illustrate this we'll use a simple data structure that represents a list of messages that have been written by users of a chat room. These are made of an object with a text attribute that gets added to the /messages list:
text
/messages
/functions-project-12345 /messages /key-123456 text: "This is my first message!" /key-123457 text: "IN THIS MESSAGE I AM SHOUTING!!!"
Once the function has run on the newly added messages, we'll add two attributes: sanitized which is true when message has been verified by our moderation function and moderated which is true if it was detected that the message contained offensive content and was modified. For instance, after the function runs on the two sample messages above we should get:
sanitized
true
moderated
/functions-project-12345 /messages /key-123456 text: "This is my first message!", sanitized: true, moderated: false /key-123457 text: "In this message I am shouting." sanitized: true, moderated: true
Our moderator function will be triggered every time there is a write to one of the messages. We set this up by using the functions.database().path('/messages/{messageId}').onWrite(...) trigger rule. We'll moderate the message and write back the moderated message into the Realtime Database:
functions.database().path('/messages/{messageId}').onWrite(...)
exports.moderator = functions.database.ref('/messages/{messageId}') .onWrite(event => { const message = event.data.val(); if (message && !message.sanitized) { // Retrieved the message values. console.log('Retrieved message content: ', message); // Run moderation checks on on the message and moderate if needed. const moderatedMessage = moderateMessage(message.text); // Update the Firebase DB with checked message. console.log('Message has been moderated. Saving to DB: ', moderatedMessage); return event.data.adminRef.update({ text: moderatedMessage, sanitized: true, moderated: message.text !== moderatedMessage }); } });
In the moderateMessage function, we'll first check if the user is shouting and if so fix the case of the sentence and then remove all bad words using the bad-words package filter.
moderateMessage
bad-words
function moderateMessage(message) { // Re-capitalize if the user is Shouting. if (isShouting(message)) { console.log('User is shouting. Fixing sentence case...'); message = stopShouting(message); } // Moderate if the user uses SwearWords. if (containsSwearwords(message)) { console.log('User is swearing. moderating...'); message = moderateSwearwords(message); } return message; } // Returns true if the string contains swearwords. function containsSwearwords(message) { return message !== badWordsFilter.clean(message); } // Hide all swearwords. e.g: Crap => ****. function moderateSwearwords(message) { return badWordsFilter.clean(message); } // Detect if the current message is shouting. i.e. there are too many Uppercase // characters or exclamation points. function isShouting(message) { return message.replace(/[^A-Z]/g, '').length > message.length / 2 || message.replace(/[^!]/g, '').length >= 3; } // Correctly capitalize the string as a sentence (e.g. uppercase after dots) // and remove exclamation points. function stopShouting(message) { return capitalizeSentence(message.toLowerCase()).replace(/!+/g, '.'); }
Note: the bad-words package uses the badwords-list package's list of swear words which only contains around 400 of these. As you know the imagination of the user out there has no limit, so this is not an exhaustive list and you might want to extend the bad words dictionary.
To moderate images we'll set up a blurOffensiveImages function that will be triggered every time a file is uploaded to Cloud Storage. We set this up by using the functions.cloud.storage().onChange(...) trigger rule. We'll check if the image contains violent or adult content using the Google Cloud Vision API. The Cloud Vision API has a feature that specifically allows to detect inappropriate content in images. Then if the image is inappropriate we'll blur the image:
blurOffensiveImages
functions.cloud.storage().onChange(...)
exports.blurOffensiveImages = functions.storage.object().onChange(event => { const object = event.data; const file = gcs.bucket(object.bucket).file(object.name); // Exit if this is a move or deletion event. if (object.resourceState === 'not_exists') { return console.log('This is a deletion event.'); } // Check the image content using the Cloud Vision API. return vision.detectSafeSearch(file).then(data => { const safeSearch = data[0]; console.log('SafeSearch results on image', safeSearch); if (safeSearch.adult || safeSearch.violence) { return blurImage(object.name, object.bucket, object.metadata); } }); });
To blur the image stored in Cloud Storage, we'll first download it locally on the Cloud Functions instance, blur the image with ImageMagick, which is installed by default on all instances, then re-upload the image to Cloud Storage:
function blurImage(filePath, bucketName, metadata) { const filePathSplit = filePath.split('/'); filePathSplit.pop(); const fileDir = filePathSplit.join('/'); const tempLocalDir = `${LOCAL_TMP_FOLDER}${fileDir}`; const tempLocalFile = `${LOCAL_TMP_FOLDER}${filePath}`; const bucket = gcs.bucket(bucketName); // Create the temp directory where the storage file will be downloaded. return mkdirp(tempLocalDir).then(() => { console.log('Temporary directory has been created', tempLocalDir); // Download file from bucket. return bucket.file(filePath).download({ destination: tempLocalFile }); }).then(() => { console.log('The file has been downloaded to', tempLocalFile); // Blur the image using ImageMagick. return exec(`convert ${tempLocalFile} -channel RGBA -blur 0x8 ${tempLocalFile}`); }).then(() => { console.log('Blurred image created at', tempLocalFile); // Uploading the Blurred image. return bucket.upload(tempLocalFile, { destination: filePath, metadata: {metadata: metadata} // Keeping custom metadata. }); }).then(() => { console.log('Blurred image uploaded to Storage at', filePath); }); }
Cloud Functions for Firebase can be a great tool to reactively and automatically apply moderation rules. Feel free to have a look at our open source samples for text moderation and image moderation.