This codelab walks you through using AngularDart and Firebase to create a web app with a realtime shared counter.

TRY IT

If you know how to program, you can complete this codelab. You don't need previous experience with Dart or with web programming.

What are Dart, AngularDart, and Firebase?

Dart is an open source programming language that you can use for web and mobile applications development. AngularDart is a framework that you can use for building full-featured web applications with Dart. AngularDart is used heavily by Google—for example, in AdWords and AdSense. Firebase is a service that provides a backend that you can use without writing server-side code.

What you'll build

You'll use AngularDart and a Firebase library to build a shared, real-time counter.

What you'll learn

This codelab uses—but doesn't try to teach you about—HTML, CSS, Angular syntax, the Dart language, and common Dart APIs. You can learn more about these glossed-over topics by following the links sprinkled around the following pages.

What you'll use

This codelab requires version 1.23 (or higher) of the Dart SDK. The instructions feature Dartium and the WebStorm IDE, but if you have some experience with Dart, you can use whichever tools you prefer.

In this step, you download the software that you need.

Get Dart and Dartium

If you haven't already done so, get the Dart SDK (1.23 or a compatible version) and Dartium.

Note where you installed the SDK and Dartium. You'll need these paths when you configure WebStorm.

Get WebStorm, IntelliJ IDEA, or a plugin

If you don't have a favorite editor already, install and configure WebStorm. Alternatively, download one of the Dart plugins for other IDEs and editors.

More information

In this step, you create an AngularDart app, no coding required. WebStorm uses a template to provide all the files your app needs.

Launch WebStorm.

In the Welcome to WebStorm window, click Create New Project. A New Project form appears.

Select Dart from the list on the left.

If the Dart SDK path and Dartium path fields don't have values, enter them. Make sure the Dart version is at least 1.23.

Make sure that Generate sample content is checked. (It might take a few seconds to show up.)

Select AngularDart Web App from the list of sample content.

Change the name of the project in the Location input field from untitled to firebase_counter.

Check your settings against the following screenshot, then click Create.

Give WebStorm a few seconds, and new Angular web application project will be loaded in WebStorm, including all dependencies.

Let's run the starter app!

In WebStorm, click the Debug button at the top-right of the window (it has ‘index.html' next to it).

If this is the first time Dartium has executed after being downloaded, it opens a duplicate tab and displays messages for first-time Chromium users. You'll see warnings that you can safely ignore.

After a few seconds, you should see something like this:

SEE THE SOURCE CODE

Let's write some code for our first AngularDart component!

In this step, you implement a simple AngularDart component for your counter.

AngularDart makes it easy to split apps into many small, independent components. But for our purposes, one component is enough for the whole app.

In the left-hand panel, open the project folder and the lib folder inside it. Then double-click app_component.dart.

The app_component.dart file holds the source code for our main component, the AppComponent class. Think about it as the app itself.

The starter app doesn't have any state in the main component, but our counter app will. Let's add it.

Edit the inside of the AppComponent class to look like this:

...
class AppComponent {
  int count = 0;
}

We merely removed the comment, substituting a single field (count) that we initialized to 0.

Open lib/app_component.html, the HTML template for our component, and change the contents of the file to be only this:

{{count}}

Refresh the app in the web browser, either by reloading the page or by clicking the Debug button again. You should now see the current value of our counter (0):

SEE THE SOURCE CODE

What does the app_component.dart source code mean?

The lib/app_component.dart file imports the angular2 and angular_components libraries as well as todo_list_component.dart, which implements a component from the sample project. We will not use that component, but you can leave it here as-is.

The @Component annotation on AppComponent specifies the value of several parameters:

Next, we'll modify the app to load data from the Firebase database.

In the next steps you'll use the Dart firebase package, which provides a library that wraps the Firebase JavaScript SDK.

To use the firebase package, you first need to add it to your app's dependencies.

Open the pubspec.yaml file (in the root of your project).

Add firebase: ^3.0.0 to the dependencies section.

dependencies:
  angular2: ^3.0.0
  angular_components: ^0.5.1
  firebase: ^3.0.0

Save, then click Get dependencies at the top right of WebStorm's editor.

The pub get tool downloads the firebase package (and its dependencies) into your project.

Wait until the firebase (and other related) dependencies are loaded.

Before you use the Dart firebase library, you must add the JavaScript it depends on to your app.

Include the latest firebase.js file in your web/index.html file, just before the other scripts:

<script src="https://www.gstatic.com/firebasejs/3.9.0/firebase.js"></script>

After adding the script, your index.html file should look like this:

...
<script src="https://www.gstatic.com/firebasejs/3.9.0/firebase.js"></script>
<script defer src="main.dart" type="application/dart"></script>
<script defer src="packages/browser/dart.js"></script>
...

Now you are prepared to use Firebase in your Dart code.

In lib/app_component.dart:

  1. Add an import of Firebase library at the top:
import 'package:firebase/firebase.dart';
  1. Declare that AppComponent implements the OnInit interface:
class AppComponent implements OnInit {

Why? We want to initialize our connection and load our data from Firebase right after our app is instantiated. We'll do this in the ngOnInit() method. This method is one of the component's lifecycle hooks.

AppComponent is underlined. If you mouse over it, you'll see the message "Missing concrete implementation of OnInit.ngOnInit".

Click the underlined AppComponent identifier, then click the red light bulb that appears and select Create 1 missing override(s).

Alternately, use the Alt-Enter keyboard shortcut when your cursor is in the underlined text.

The ngOnInit() method is created and your code should now look like this:

class AppComponent implements OnInit {
  int count = 0;

  @override
  ngOnInit() {
    // TODO: implement ngOnInit
  }
}

Inside ngOnInit(), call the initializeApp() function with the configuration object for your Firebase project:

ngOnInit() {
  initializeApp(
     apiKey: "AIzaSyAH7S_gsce9RtNI8w0z7doiP3ugVJM8ZbI",
     authDomain: "angulardart-firebase-io-2017.firebaseapp.com",
     databaseURL: "https://angulardart-firebase-io-2017.firebaseio.com",
     storageBucket: "angulardart-firebase-io-2017.appspot.com");
}

You're done with the configuration but aren't doing anything with Firebase yet. That's the next step.

We'll use our ngOnInit() method to load data from the Firebase Realtime Database, a cloud-hosted NoSQL database that stores data as JSON.

Our Firebase database structure looks like this:

We have a counter path with some numeric value in the database. Our application will connect to this database and load the current value of counter.

First, we need to get the reference to our database counter path. In the library, we have a DatabaseReference object for this and we'll store it in a ref instance variable in our component's class.

In this step, you edit lib/app_component.dart.

Create the ref instance variable:

class AppComponent implements OnInit {
  int count = 0;
  DatabaseReference ref;

In ngOnInit(), get the database reference to our 'counter' path from the database method (after the call to initializeApp()):

ngOnInit() {
  initializeApp(
      ...);

  ref = database().ref('counter');
};

To load the data using the 'counter' path, you can listen to the onValue stream. Several event streams are available, but onValue is good when you want to listen to the event on the initial load, and then again each time the data changes.

Add the following to ngOnInit() to start updating the app's count property according to the newest database value:

ref.onValue.listen((e) {
  count = e.snapshot.val();
});

How does this work? When the value event appears, the handler function specified to onValue.listen is called. This function needs to retrieve the data for the current counter value. It can get the data snapshot (which is a copy of data at the database) from the event first, and then retrieve the concrete value.

This new value goes into the count property of our app, and AngularDart takes care of updating the view.

The ngOnInit() method now looks like this:

@override
ngOnInit() {
  initializeApp(
      apiKey: "AIzaSyAH7S_gsce9RtNI8w0z7doiP3ugVJM8ZbI",
      authDomain: "angulardart-firebase-io-2017.firebaseapp.com",
      databaseURL: "https://angulardart-firebase-io-2017.firebaseio.com",
      storageBucket: "angulardart-firebase-io-2017.appspot.com");

  ref = database().ref('counter');
  ref.onValue.listen((e) {
    count = e.snapshot.val();
  });
}

Refresh the app in the web browser. You should now see a nonzero value there:

The value might be different or might change depending on the actual value in the Firebase database. The Firebase database is realtime, so you always see the actual value there.

SEE THE SOURCE CODE

In the next step, you'll add more interactivity to the counter.

The application is a little bit boring now. It shows the current value of counter from the Firebase Realtime Database, but it doesn't do anything special. Let's fix that.

In this step, you'll add like and dislike buttons to change the value in the shared counter.

Add two HTML buttons to the HTML template. Edit the lib/app_component.html file to be:

<button>Dislike</button>
<span>{{count}}</span>
<button>Like</button>

You've added like and dislike buttons, but they don't do anything yet. To make them react, you need to register a click handler on them. The click handler specifies which function is called when the event is triggered.

Register click handlers in the template's HTML:

<button (click)="dislike()">Dislike</button>
<span>{{count}}</span>
<button (click)="like()">Like</button>

Refresh the app in the browser.

The app will stop at the loading screen and it will throw an exception on startup: NoSuchMethodError: Class 'AppComponent' has no instance getter 'dislike'. This is great! It shows that AngularDart will check if the methods we are calling from the template actually exist. If they don't, that's definitely a bug we want to fix. And we don't need to click around our app to find out about it.

The problem is that we have refer to dislike() and like() functions for our buttons, but we haven't implemented them yet in our AppComponent class.

The dislike() method should decrease the counter's value; the like() method should increase it. Let's print something to console in our methods to try it.

Implement the like() and dislike() functions in app_component.dart like so:

...
class AppComponent implements OnInit {
  ...
 
  dislike() {
    print("dislike");
  }
  
  like() {
    print("like");
  }
}

Test it in the web browser and watch the Debug console.

Now we'll implement the like and dislike functionality. The Firebase database is realtime, but because we can do lots of very fast and concurrent modifications, we need to use the transaction() method, which atomically updates data at the actual database location.

The transaction() method takes a function as a parameter. This function should update an existing value into the new value. Firebase ensures that there are no conflicts with other clients writing to the same location at the same time. If there would be a conflict, Firebase updates the data from the database and runs your function again using the updated data.

Both the dislike() and like() methods need to use this transaction, so let's wrap this functionality into a new method called updateDatabase().

The updateDatabase() method takes an update function (a closure) as a parameter. This function is used for data manipulation in the transaction. The updateDatabase() function then returns a Future object. (A Future represents a means for getting a value asynchronously.)

Import dart:async in your app_component.dart to be able to use Future:

import 'dart:async';

Add the following code to the AppComponent class:

Future<int> updateDatabase(Function update) async {
  var transaction = await ref.transaction((current) {
    if (current != null) {
      current = update(current);
    }
    return current;
  });

  return transaction.snapshot.val();
}

You call the transaction() function on your DatabaseReference property. It asynchronously updates the current value using the update function and returns the new value of current.

Once we have the Transaction value, we want to get the current value in the counter from the snapshot and return it, so we can show it in our counter. To do so, our method has to return a Future object. Because the retrieved value from the snapshot is int, our method returns a Future<int>.

Optional: Typedef

This small step is completely optional and is intended for better code quality. Feel free to skip it and continue to the next step.

The updateDatabase() method takes a Function—any function—as a parameter. But it'd be better to restrict what arguments the function can have, and what it can return. Dart's typedef can help with this.

Create a definition (typedef) for the function outside the AppComponent class (typedefs cannot be parts of a class). The function should take only one parameter (of type T) and return an object of the same type (T). Call the typedef UpdateFunction:

typedef T UpdateFunction<T>(T value);

Now use the typedef in the updateDatabase() method:

Future<int> updateDatabase(UpdateFunction<int> update) async {
  var transaction = await ref.transaction((current) {
    if (current != null) {
      current = update(current);
    }
    return current;
  });

  return transaction.snapshot.val();
}

We now have our updateDatabase() function prepared, and the only thing we need to do now is to call this method with the appropriate function parameter in the dislike() and like() methods.

Recall that the updateDatabase() function returns a Future, which means we need to use await and mark the dislike() method as async.

Update dislike() to look like this:

dislike() async {
  count = await updateDatabase((c) => c - 1);
}

What's happening here? We call updateDatabase() with (c) => c - 1 as the only parameter. That's a function that takes a value and decrements it by one. We than await until updateDatabase returns the latest value of the counter. Remember, we're working with a live, real-time database, so the value could have changed in unexpected ways.

Update like() to look like this:

like() async {
  count = await updateDatabase((c) => c + 1);
}

Now refresh the app in the web browser.

You should now be able to change the value in the counter. You can open multiple browser windows with the app and see how the value changes in all windows to the same value. This is because the Firebase database is real-time.

SEE THE SOURCE CODE

We could stop here and say that our app is done. But let's make it even nicer and use some material design components instead of our buttons!

In this step, you add material design buttons with icons to your app. There is no need to change the logic; only the button component needs to change.

The material design library is already present in the pubspec.yaml file's dependencies section:

dependencies:
  ...
  angular_components: ^0.5.1

The AppComponent already contains the needed material design providers and directives.

@Component(
  ...
  directives: const [CORE_DIRECTIVES, materialDirectives, TodoListComponent],
  providers: const [materialProviders],
)
class AppComponent implements OnInit {

The only thing you need to do is use the material components—specifically, the material floating action button (fab) with icon.

Change your template code in lib/app_component.html :

  1. Use <material-fab> components instead of <button> elements.
  2. Use trigger events instead of click events.
  3. Put a <glyph> component inside of each <material-fab> component, instead of text.

Your code should look like this:

<material-fab (trigger)="dislike()">
    <glyph icon="thumb_down"></glyph>
</material-fab>

<span>{{count}}</span>

<material-fab (trigger)="like()">
    <glyph icon="thumb_up"></glyph>
</material-fab>

The advantage of using trigger instead of click is in accessibility — the trigger event combines clicks, keyboard events, and taps.

The <glyph> components have an icon attribute that's set to thumb_down or thumb_up. You can find icons and their corresponding names at material.io/icons. This app loads its icons using a web font that's already included in web/styles.css:

Finally let's add some styling to our component.

Update stylesheets for your app component in lib/app_component.css to be:

span {
 font-size: 300%;
 padding: 0 1rem;
}

Refresh the app in the web browser.

You should see the material design buttons in action. And that's it!

SEE THE SOURCE CODE

or

TRY IT

Congratulations! You have successfully implemented a simple shared counter using the AngularDart framework and the Firebase Realtime Database.

Interested in learning more?

Explore other Dart codelabs

Resources