Writing Cloud Functions In Dart

Stefan Matthias Aust
ICNH
Published in
5 min readMay 11, 2020
Photo by chuttersnap on Unsplash

Can you write Firebase Cloud Functions in Dart?

Yes you can and here is how.

The article has been updated to Firebase CLI version 8.4.

Firebase Setup

Install Node.js and the Firebase CLI if you haven’t done so already:

npm install -g firebase-tools

Then, log into the Firebase Console and create a new project (or pick one you want to use). Because later in this tutorial, we will demonstrate a function that accesses the Cloud Firestore, create such a database in your prefered region and remember that region. You must deploy your functions in the same region as your database if you don’t want to pay for the data traffic between different regions!

Back on the terminal, also log into Firebase here:

firebase login

Then, last but not least, setup the functions project in your current directory:

firebase init functions

Choose the Firebase project you created earlier and pick “JavaScript” as language and deselect “ESLint” (which we don’t need as we don’t write JavaScript on our own). Then allow firebase to download all dependencies via npm and wait while it downloads half of the internet.

This process created an empty firebase.json file and a directory called functions which contains a typical node project with a package.json file, a node_modules directory and an index.js file which contains a “hello world” example.

Open index.js and uncomment the example code. Then test the created function to make sure, your setup was successful, running:

npm run serve

The Firebase emulator should print the URL to access the cloud function. On my machine this is http://localhost:5001/<projectname>/us-central1/helloWorld. It also starts a fancy web UI for its emulator on http://localhost:4000/. It will even automatically pick up changes to index.js while running which is quite nice. Eventually, stop the server.

Switch to Dart

Next, setup the Dart project.

Install the Dart SDK if you haven’t done so already.

Create a new pubspec.yaml file in the functions directory with the following content:

name: functions
version: 1.0.0
environment:
sdk: ‘>=2.8.0 <3.0.0’
dependencies:
firebase_functions_interop: ^1.0.2
dev_dependencies:
build_runner: ^1.9.0
build_node_compilers: ^0.2.4

Run pub get to download all dependencies.

Create a directory called node and create a file called index.dart inside that folder that looks like this:

import 'package:firebase_functions_interop/firebase_functions_interop.dart';void main() {
functions[‘moin’] = functions
.region('europe-west3')
.https.onRequest(greet);
}
void greet(ExpressHttpRequest request) {
request.response
..writeln('Moin, moin!')
..close();
}

Note: You must replace europe-west3 with your region or omit that line.

Create another file called build.yaml with this content:

targets:
$default:
sources:
— node/**
— lib/**
builders:
build_node_compilers|entrypoint:
options:
compiler: dart2js

Add these lines to .gitignore:

ui-debug.log
build/
.dart_tool/
.packages

Now run

pub run build_runner build -o node:build

This converts node/index.dart to build/index.dart.js. The convention, to use a node directory instead of the usual bin directory has been built into the NodeJS wrapper package that is used internally. It does the heavy-lifting of translating Dart to JavaScript. You can add more Dart files to lib if you like.

Then change package.json and add:

  "main": "build/index.dart.js"

You can remove index.js. The JavaScript reign is about to end.

Next, run npm run serve again. When accessing the shown URL, the text “Moin, moin!” should be displayed. We successfully created a cloud function in Dart! Eventually, stop the server.

Deploying the Function

Replace the content of firebase.json with this content:

{
"functions": {
"ignore": [
".dart_tool",
".packages",
"**/build/*.map",
"**/build/packages",
"build.yaml",
"node",
"node_modules",
"pubspec.*"
]
}
}

Let’s now deploy the cloud function to Firebase:

npm run deploy

Your function should be live now.

Please notice that by default, the package.json is configured to use Node.js version 8. This version will soon be disabled by Google. You must switch to version 10. However, you will have then to pay for using this version.

Development Workflow

To develop locally, use watch instead of build like so:

pub run build_runner watch -o node:build

and — in a second terminal — run:

npm run serve

Then change the Dart source and observe the build_runner to recompile the Dart source and the Firebase development server to reload the JavaScript function. Now, you’re ready to leave the JavaScript world behind and only use Dart for both your client and server development. Happy times!

Firestore Integration

Let’s define a Firestore trigger which will automatically be called every time a new document is add to the foo collection. It creates a similar document in the bar collection that has the same name property as the document in foo.

First, initialize, download and run the Firestore emulator:

firebase init emulators
firebase emulators:start

A fancy UI is available on http://localhost:4000/.

Now replace index.dart with the following code and make sure that the Dart build_runner is still watching you.

import 'package:firebase_functions_interop/firebase_functions_interop.dart';void main() {
functions['foo'] = functions
.region('europe-west3')
.firestore.document('/foo/{id}').onCreate(createHook);
}
Future<void> createHook(DocumentSnapshot snapshot, EventContext context) async {
final name = snapshot.data.getString('name');
final data = DocumentData.fromMap({'name': name});
await snapshot.firestore.collection('/bar').add(data);
}

Note: Make sure that the function runs in the same region as your datastore! Also notice that the store region name and the function region name might not be identical.

As both the Firebase emulator and the build watcher are running, saving the Dart file should automatically (re)reploy your function and and you’re good to go and test it buy manually creating a new document in foo, adding at least a name property to that document. Then switch to bar and observe that our function has successfully created a document.

For the last time, deploy:

npm run deploy

Now go to Firebase console, open your Firestore database and test the trigger by creating a foo/<id> document with a name property and observe the creation of a new bar/<id> docment with a similar name property. <id> stands for the automatically created document identifier.

Conculsion

At ICNH, we’re building Flutter apps for fun and profit and we love to use Firebase as a simple to use backend. It is very well integrated with Flutter because Google provides ready to use packages most aspects of Firebase. For most mobile application, we also have to customize the server part. Using the same programming language for server development as for client development really helps to make this process much nicer.

We successfully created a custom backend for a Flutter app using only Dart.

Source code

--

--

Stefan Matthias Aust
ICNH
Editor for

App Developer · Co-Founder of I.C.N.H GmbH · Pen & paper role player