Omówienie
W tym samouczku pokazujemy, jak utworzyć interaktywną mapę za pomocą platformy aplikacji Firebase. Kliknij różne lokalizacje na poniższej mapie, aby utworzyć mapę termiczną.
W sekcji poniżej znajdziesz cały kod potrzebny do utworzenia mapy w tym samouczku.
/** * Firebase config block. */ var config = { apiKey: 'AIzaSyDX-tgWqPmTme8lqlFn2hIsqwxGL6FYPBY', authDomain: 'maps-docs-team.firebaseapp.com', databaseURL: 'https://maps-docs-team.firebaseio.com', projectId: 'maps-docs-team', storageBucket: 'maps-docs-team.appspot.com', messagingSenderId: '285779793579' }; firebase.initializeApp(config); /** * Data object to be written to Firebase. */ var data = {sender: null, timestamp: null, lat: null, lng: null}; function makeInfoBox(controlDiv, map) { // Set CSS for the control border. var controlUI = document.createElement('div'); controlUI.style.boxShadow = 'rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px'; controlUI.style.backgroundColor = '#fff'; controlUI.style.border = '2px solid #fff'; controlUI.style.borderRadius = '2px'; controlUI.style.marginBottom = '22px'; controlUI.style.marginTop = '10px'; controlUI.style.textAlign = 'center'; controlDiv.appendChild(controlUI); // Set CSS for the control interior. var controlText = document.createElement('div'); controlText.style.color = 'rgb(25,25,25)'; controlText.style.fontFamily = 'Roboto,Arial,sans-serif'; controlText.style.fontSize = '100%'; controlText.style.padding = '6px'; controlText.textContent = 'The map shows all clicks made in the last 10 minutes.'; controlUI.appendChild(controlText); } /** * Starting point for running the program. Authenticates the user. * @param {function()} onAuthSuccess - Called when authentication succeeds. */ function initAuthentication(onAuthSuccess) { firebase.auth().signInAnonymously().catch(function(error) { console.log(error.code + ', ' + error.message); }, {remember: 'sessionOnly'}); firebase.auth().onAuthStateChanged(function(user) { if (user) { data.sender = user.uid; onAuthSuccess(); } else { // User is signed out. } }); } /** * Creates a map object with a click listener and a heatmap. */ function initMap() { var map = new google.maps.Map(document.getElementById('map'), { center: {lat: 0, lng: 0}, zoom: 3, styles: [{ featureType: 'poi', stylers: [{ visibility: 'off' }] // Turn off POI. }, { featureType: 'transit.station', stylers: [{ visibility: 'off' }] // Turn off bus, train stations etc. }], disableDoubleClickZoom: true, streetViewControl: false, }); // Create the DIV to hold the control and call the makeInfoBox() constructor // passing in this DIV. var infoBoxDiv = document.createElement('div'); makeInfoBox(infoBoxDiv, map); map.controls[google.maps.ControlPosition.TOP_CENTER].push(infoBoxDiv); // Listen for clicks and add the location of the click to firebase. map.addListener('click', function(e) { data.lat = e.latLng.lat(); data.lng = e.latLng.lng(); addToFirebase(data); }); // Create a heatmap. var heatmap = new google.maps.visualization.HeatmapLayer({ data: [], map: map, radius: 16 }); initAuthentication(initFirebase.bind(undefined, heatmap)); } /** * Set up a Firebase with deletion on clicks older than expiryMs * @param {!google.maps.visualization.HeatmapLayer} heatmap The heatmap to */ function initFirebase(heatmap) { // 10 minutes before current time. var startTime = new Date().getTime() - (60 * 10 * 1000); // Reference to the clicks in Firebase. var clicks = firebase.database().ref('clicks'); // Listen for clicks and add them to the heatmap. clicks.orderByChild('timestamp').startAt(startTime).on('child_added', function(snapshot) { // Get that click from firebase. var newPosition = snapshot.val(); var point = new google.maps.LatLng(newPosition.lat, newPosition.lng); var elapsedMs = Date.now() - newPosition.timestamp; // Add the point to the heatmap. heatmap.getData().push(point); // Request entries older than expiry time (10 minutes). var expiryMs = Math.max(60 * 10 * 1000 - elapsedMs, 0); // Set client timeout to remove the point after a certain time. window.setTimeout(function() { // Delete the old point from the database. snapshot.ref.remove(); }, expiryMs); } ); // Remove old data from the heatmap when a point is removed from firebase. clicks.on('child_removed', function(snapshot, prevChildKey) { var heatmapData = heatmap.getData(); var i = 0; while (snapshot.val().lat != heatmapData.getAt(i).lat() || snapshot.val().lng != heatmapData.getAt(i).lng()) { i++; } heatmapData.removeAt(i); }); } /** * Updates the last_message/ path with the current timestamp. * @param {function(Date)} addClick After the last message timestamp has been updated, * this function is called with the current timestamp to add the * click to the firebase. */ function getTimestamp(addClick) { // Reference to location for saving the last click time. var ref = firebase.database().ref('last_message/' + data.sender); ref.onDisconnect().remove(); // Delete reference from firebase on disconnect. // Set value to timestamp. ref.set(firebase.database.ServerValue.TIMESTAMP, function(err) { if (err) { // Write to last message was unsuccessful. console.log(err); } else { // Write to last message was successful. ref.once('value', function(snap) { addClick(snap.val()); // Add click with same timestamp. }, function(err) { console.warn(err); }); } }); } /** * Adds a click to firebase. * @param {Object} data The data to be added to firebase. * It contains the lat, lng, sender and timestamp. */ function addToFirebase(data) { getTimestamp(function(timestamp) { // Add the new timestamp to the record data. data.timestamp = timestamp; var ref = firebase.database().ref('clicks').push(data, function(err) { if (err) { // Data was not written to firebase. console.warn(err); } }); }); }
<div id="map"></div>
/* Always set the map height explicitly to define the size of the div * element that contains the map. */ #map { height: 100%; } /* Optional: Makes the sample page fill the window. */ html, body { height: 100%; margin: 0; padding: 0; }
<!-- Replace the value of the key parameter with your own API key. --> <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&libraries=visualization&callback=initMap" defer></script> <script src="https://www.gstatic.com/firebasejs/5.3.0/firebase.js"></script>
Wypróbuj
Możesz poeksperymentować z tym kodem w JSFiddle, klikając ikonę <>
w
w prawym górnym rogu okna kodu.
<!DOCTYPE html>
<html>
<head>
<style>
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html, body {
height: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="map"></div>
<script src="https://www.gstatic.com/firebasejs/5.3.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.3.0/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.3.0/firebase-database.js"></script>
<script>
/**
* Firebase config block.
*/
var config = {
apiKey: 'AIzaSyDX-tgWqPmTme8lqlFn2hIsqwxGL6FYPBY',
authDomain: 'maps-docs-team.firebaseapp.com',
databaseURL: 'https://maps-docs-team.firebaseio.com',
projectId: 'maps-docs-team',
storageBucket: 'maps-docs-team.appspot.com',
messagingSenderId: '285779793579'
};
firebase.initializeApp(config);
/**
* Data object to be written to Firebase.
*/
var data = {sender: null, timestamp: null, lat: null, lng: null};
function makeInfoBox(controlDiv, map) {
// Set CSS for the control border.
var controlUI = document.createElement('div');
controlUI.style.boxShadow = 'rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px';
controlUI.style.backgroundColor = '#fff';
controlUI.style.border = '2px solid #fff';
controlUI.style.borderRadius = '2px';
controlUI.style.marginBottom = '22px';
controlUI.style.marginTop = '10px';
controlUI.style.textAlign = 'center';
controlDiv.appendChild(controlUI);
// Set CSS for the control interior.
var controlText = document.createElement('div');
controlText.style.color = 'rgb(25,25,25)';
controlText.style.fontFamily = 'Roboto,Arial,sans-serif';
controlText.style.fontSize = '100%';
controlText.style.padding = '6px';
controlText.textContent =
'The map shows all clicks made in the last 10 minutes.';
controlUI.appendChild(controlText);
}
/**
* Starting point for running the program. Authenticates the user.
* @param {function()} onAuthSuccess - Called when authentication succeeds.
*/
function initAuthentication(onAuthSuccess) {
firebase.auth().signInAnonymously().catch(function(error) {
console.log(error.code + ', ' + error.message);
}, {remember: 'sessionOnly'});
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
data.sender = user.uid;
onAuthSuccess();
} else {
// User is signed out.
}
});
}
/**
* Creates a map object with a click listener and a heatmap.
*/
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
center: {lat: 0, lng: 0},
zoom: 3,
styles: [{
featureType: 'poi',
stylers: [{ visibility: 'off' }] // Turn off POI.
},
{
featureType: 'transit.station',
stylers: [{ visibility: 'off' }] // Turn off bus, train stations etc.
}],
disableDoubleClickZoom: true,
streetViewControl: false,
});
// Create the DIV to hold the control and call the makeInfoBox() constructor
// passing in this DIV.
var infoBoxDiv = document.createElement('div');
makeInfoBox(infoBoxDiv, map);
map.controls[google.maps.ControlPosition.TOP_CENTER].push(infoBoxDiv);
// Listen for clicks and add the location of the click to firebase.
map.addListener('click', function(e) {
data.lat = e.latLng.lat();
data.lng = e.latLng.lng();
addToFirebase(data);
});
// Create a heatmap.
var heatmap = new google.maps.visualization.HeatmapLayer({
data: [],
map: map,
radius: 16
});
initAuthentication(initFirebase.bind(undefined, heatmap));
}
/**
* Set up a Firebase with deletion on clicks older than expiryMs
* @param {!google.maps.visualization.HeatmapLayer} heatmap The heatmap to
*/
function initFirebase(heatmap) {
// 10 minutes before current time.
var startTime = new Date().getTime() - (60 * 10 * 1000);
// Reference to the clicks in Firebase.
var clicks = firebase.database().ref('clicks');
// Listen for clicks and add them to the heatmap.
clicks.orderByChild('timestamp').startAt(startTime).on('child_added',
function(snapshot) {
// Get that click from firebase.
var newPosition = snapshot.val();
var point = new google.maps.LatLng(newPosition.lat, newPosition.lng);
var elapsedMs = Date.now() - newPosition.timestamp;
// Add the point to the heatmap.
heatmap.getData().push(point);
// Request entries older than expiry time (10 minutes).
var expiryMs = Math.max(60 * 10 * 1000 - elapsedMs, 0);
// Set client timeout to remove the point after a certain time.
window.setTimeout(function() {
// Delete the old point from the database.
snapshot.ref.remove();
}, expiryMs);
}
);
// Remove old data from the heatmap when a point is removed from firebase.
clicks.on('child_removed', function(snapshot, prevChildKey) {
var heatmapData = heatmap.getData();
var i = 0;
while (snapshot.val().lat != heatmapData.getAt(i).lat()
|| snapshot.val().lng != heatmapData.getAt(i).lng()) {
i++;
}
heatmapData.removeAt(i);
});
}
/**
* Updates the last_message/ path with the current timestamp.
* @param {function(Date)} addClick After the last message timestamp has been updated,
* this function is called with the current timestamp to add the
* click to the firebase.
*/
function getTimestamp(addClick) {
// Reference to location for saving the last click time.
var ref = firebase.database().ref('last_message/' + data.sender);
ref.onDisconnect().remove(); // Delete reference from firebase on disconnect.
// Set value to timestamp.
ref.set(firebase.database.ServerValue.TIMESTAMP, function(err) {
if (err) { // Write to last message was unsuccessful.
console.log(err);
} else { // Write to last message was successful.
ref.once('value', function(snap) {
addClick(snap.val()); // Add click with same timestamp.
}, function(err) {
console.warn(err);
});
}
});
}
/**
* Adds a click to firebase.
* @param {Object} data The data to be added to firebase.
* It contains the lat, lng, sender and timestamp.
*/
function addToFirebase(data) {
getTimestamp(function(timestamp) {
// Add the new timestamp to the record data.
data.timestamp = timestamp;
var ref = firebase.database().ref('clicks').push(data, function(err) {
if (err) { // Data was not written to firebase.
console.warn(err);
}
});
});
}
</script>
<script defer
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=visualization&callback=initMap">
</script>
</body>
</html>
Pierwsze kroki
Możesz utworzyć własną wersję mapy Firebase, korzystając z kodu w tym samouczku. Aby rozpocząć
w tym celu utwórz nowy plik w edytorze tekstu i zapisz go jako index.html
.
Przeczytaj poniższe sekcje, aby dowiedzieć się, jaki kod możesz dodać do tego pliku.
Tworzenie mapy podstawowej
W tej sekcji omawiamy kod służący do konfigurowania podstawowej mapy. Może on być podobny do tego, mapy, gdy korzystania z interfejsu Maps JavaScript API.
Skopiuj poniższy kod do pliku index.html
. Ten kod wczytuje
Maps JavaScript API i wyświetla mapę w trybie pełnoekranowym. Wczytuje też wizualizację,
która będzie potrzebna w dalszej części samouczka do utworzenia mapy termicznej.
<!DOCTYPE html>
<html>
<head>
<style>
#map {
height: 100%;
}
html, body {
height: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="map"></div>
<script defer
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY
&libraries=visualization&callback=initMap">
</script>
<script>
// The JavaScript code that creates the Firebase map goes here.
</script>
</body>
</html>
Kliknij YOUR_API_KEY
w przykładowym kodzie lub postępuj zgodnie z instrukcjami, aby
uzyskać klucz interfejsu API. Zastąp
YOUR_API_KEY
za pomocą klucza interfejsu API Twojej aplikacji.
W sekcjach poniżej opisujemy kod JavaScriptu tworzący mapę Firebase. Możesz skopiować
i zapisać kod w pliku firebasemap.js
, a następnie odnosić się do niego między tagami skryptu jako
poniżej.
<script>firebasemap.js</script>
Możesz też wstawić kod bezpośrednio między tagami skryptu, jak w tagu pełny przykładowy kod na początku samouczka.
Dodaj poniższy kod do pliku firebasemap.js
lub między pustymi tagami skryptu w
pliku index.html
. Punktem wyjścia przy tworzeniu programu jest utworzenie
, która inicjuje obiekt mapy.
function initMap() { var map = new google.maps.Map(document.getElementById('map'), { center: {lat: 0, lng: 0}, zoom: 3, styles: [{ featureType: 'poi', stylers: [{ visibility: 'off' }] // Turn off points of interest. }, { featureType: 'transit.station', stylers: [{ visibility: 'off' }] // Turn off bus stations, train stations, etc. }], disableDoubleClickZoom: true, streetViewControl: false }); }
Aby ułatwić korzystanie z klikalnej mapy termicznej, w powyższym kodzie stylu mapy, aby wyłączyć ciekawe miejsca i stacje transportu publicznego (które wyświetlają okno informacyjne). Wyłącza też powiększenie po dwukrotnym kliknięciu, aby aby zapobiec przypadkowemu powiększaniu. Więcej informacji o określeniu stylu mapy.
Po całkowitym wczytaniu interfejsu API parametr wywołania zwrotnego w tagu skryptu
poniżej wykonuje funkcję initMap()
w pliku HTML.
<script defer
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY
&libraries=visualization&callback=initMap">
</script>
Dodaj poniższy kod, aby utworzyć element sterujący tekstem u góry mapy.
function makeInfoBox(controlDiv, map) { // Set CSS for the control border. var controlUI = document.createElement('div'); controlUI.style.boxShadow = 'rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px'; controlUI.style.backgroundColor = '#fff'; controlUI.style.border = '2px solid #fff'; controlUI.style.borderRadius = '2px'; controlUI.style.marginBottom = '22px'; controlUI.style.marginTop = '10px'; controlUI.style.textAlign = 'center'; controlDiv.appendChild(controlUI); // Set CSS for the control interior. var controlText = document.createElement('div'); controlText.style.color = 'rgb(25,25,25)'; controlText.style.fontFamily = 'Roboto,Arial,sans-serif'; controlText.style.fontSize = '100%'; controlText.style.padding = '6px'; controlText.innerText = 'The map shows all clicks made in the last 10 minutes.'; controlUI.appendChild(controlText); }
Dodaj poniższy kod do funkcji initMap
, po var map
,
w celu wczytania pola tekstowego.
// Create the DIV to hold the control and call the makeInfoBox() constructor // passing in this DIV. var infoBoxDiv = document.createElement('div'); var infoBox = new makeInfoBox(infoBoxDiv, map); infoBoxDiv.index = 1; map.controls[google.maps.ControlPosition.TOP_CENTER].push(infoBoxDiv);
Aby wyświetlić mapę Google utworzoną przez kod, otwórz index.html
w przeglądarce.
Konfiguruję Firebase
Aby aplikacja mogła współpracować, musisz zapisać kliknięcia w zewnętrznym do bazy danych dostępnej dla wszystkich użytkowników. Do tego celu nadaje się Baza danych czasu rzeczywistego Firebase. Nie wymaga też żadnej znajomości SQL.
Najpierw zarejestruj się bezpłatnie w Firebase.
Jeśli jesteś nowym użytkownikiem Firebase, zobaczysz nową aplikację o nazwie „Moja pierwsza aplikacja”. Jeśli
jeśli tworzysz nową aplikację, możesz nadać jej nową nazwę oraz niestandardowy adres URL Firebase kończący się znakami
firebaseIO.com
Możesz na przykład nazwać swoją aplikację „Mapa Firebase, którą Anna” z adresem URL
https://janes-firebase-map.firebaseIO.com
Możesz użyć tego adresu URL, aby połączyć bazę danych
do aplikacji JavaScript.
Dodaj poniższy wiersz za tagami <head>
do pliku HTML
aby zaimportować bibliotekę Firebase.
<script src="www.gstatic.com/firebasejs/5.3.0/firebase.js"></script>
Dodaj ten wiersz do pliku JavaScript:
var firebase = new Firebase("<Your Firebase URL here>");
Przechowywanie danych o kliknięciach w Firebase
W tej sekcji opisujemy kod, który przechowuje w Firebase dane o kliknięciach myszą na mapie.
Każde kliknięcie myszą mapy powoduje utworzenie globalnego obiektu danych za pomocą poniższego kodu, w Firebase. Ten obiekt rejestruje dane, takie jak długość i szerokość geograficzna oraz znacznik czasu kliknięcie oraz unikalny identyfikator przeglądarki, która je utworzyła.
/** * Data object to be written to Firebase. */ var data = { sender: null, timestamp: null, lat: null, lng: null };
Poniższy kod rejestruje unikalny identyfikator sesji dla każdego kliknięcia, co pomaga kontrolować natężenie ruchu na mapie zgodnie z zabezpieczeniami Firebase reguł.
/** * Starting point for running the program. Authenticates the user. * @param {function()} onAuthSuccess - Called when authentication succeeds. */ function initAuthentication(onAuthSuccess) { firebase.auth().signInAnonymously().catch(function(error) { console.log(error.code + ", " + error.message); }, {remember: 'sessionOnly'}); firebase.auth().onAuthStateChanged(function(user) { if (user) { data.sender = user.uid; onAuthSuccess(); } else { // User is signed out. } }); }
Następna sekcja kodu poniżej nasłuchuje kliknięć na mapie, co powoduje dodanie elementu podrzędnego. do
w bazie danych Firebase. W takim przypadku funkcja snapshot.val()
pobiera wartości danych wpisu i tworzy nowy obiekt LatLng.
// Listen for clicks and add them to the heatmap. clicks.orderByChild('timestamp').startAt(startTime).on('child_added', function(snapshot) { var newPosition = snapshot.val(); var point = new google.maps.LatLng(newPosition.lat, newPosition.lng); heatmap.getData().push(point); } );
Poniższy kod skonfiguruje Firebase, aby:
- Wykrywaj kliknięcia mapy. Po kliknięciu aplikacja rejestruje timestamp, a następnie „child” do bazy danych Firebase.
- Usunąć wszystkie kliknięcia na mapie starsze niż 10 minut w w czasie rzeczywistym.
/** * Set up a Firebase with deletion on clicks older than expirySeconds * @param {!google.maps.visualization.HeatmapLayer} heatmap The heatmap to * which points are added from Firebase. */ function initFirebase(heatmap) { // 10 minutes before current time. var startTime = new Date().getTime() - (60 * 10 * 1000); // Reference to the clicks in Firebase. var clicks = firebase.database().ref('clicks'); // Listen for clicks and add them to the heatmap. clicks.orderByChild('timestamp').startAt(startTime).on('child_added', function(snapshot) { // Get that click from firebase. var newPosition = snapshot.val(); var point = new google.maps.LatLng(newPosition.lat, newPosition.lng); var elapsedMs = Date.now() - newPosition.timestamp; // Add the point to the heatmap. heatmap.getData().push(point); // Request entries older than expiry time (10 minutes). var expiryMs = Math.max(60 * 10 * 1000 - elapsed, 0); // Set client timeout to remove the point after a certain time. window.setTimeout(function() { // Delete the old point from the database. snapshot.ref.remove(); }, expiryMs); } ); // Remove old data from the heatmap when a point is removed from firebase. clicks.on('child_removed', function(snapshot, prevChildKey) { var heatmapData = heatmap.getData(); var i = 0; while (snapshot.val().lat != heatmapData.getAt(i).lat() || snapshot.val().lng != heatmapData.getAt(i).lng()) { i++; } heatmapData.removeAt(i); }); }
Skopiuj cały kod JavaScript z tej sekcji do pliku firebasemap.js
.
Tworzenie mapy termicznej
Następnym krokiem jest wyświetlenie mapy termicznej, która prezentuje widzom w sposób graficzny względną liczbę kliknięć w różnych lokalizacjach na mapie. Aby dowiedzieć się więcej, przeczytaj przewodnika po mapach termicznych.
Dodaj poniższy kod w funkcji initMap()
, aby utworzyć mapę termiczną.
// Create a heatmap. var heatmap = new google.maps.visualization.HeatmapLayer({ data: [], map: map, radius: 16 });
Poniższy kod aktywuje: initFirebase
, addToFirebase
i getTimestamp
.
initAuthentication(initFirebase.bind(undefined, heatmap));
Zwróć uwagę, że jeśli klikniesz mapę termiczną, nie zostaną one jeszcze utworzone. Do tworzenia punktów na mapie, trzeba skonfigurować detektor mapy.
Tworzenie punktów na mapie termicznej
Poniższy kod dodaje detektor w elemencie initMap()
, po fragmencie
który tworzy mapę. Ten kod nasłuchuje danych po każdym kliknięciu,
zapisuje lokalizację kliknięcia w bazie danych Firebase i wyświetla
punktów na mapie termicznej.
// Listen for clicks and add the location of the click to firebase. map.addListener('click', function(e) { data.lat = e.latLng.lat(); data.lng = e.latLng.lng(); addToFirebase(data); });
Kliknij lokalizacje na mapie, aby utworzyć punkty na mapie termicznej.
Masz teraz w pełni funkcjonalną aplikację w czasie rzeczywistym, która korzysta z Firebase Maps JavaScript API.
Po kliknięciu mapy termicznej szerokość i długość geograficzna kliknięcia powinny być pojawią się w bazie danych Firebase. Możesz to sprawdzić, logując się na swoje konto Firebase i otwórz kartę danych swojej aplikacji. W tym momencie jeśli ktoś inny kliknie Twoją mapę, zarówno Ty, jak i ta osoba zobaczycie punkty na mapie. Lokalizacja kliknięć pozostaje bez zmian nawet po tym, jak użytkownik powoduje zamknięcie strony. Aby przetestować funkcje współpracy w czasie rzeczywistym, otworzyć tę stronę w 2 osobnych oknach. Znaczniki powinny znajdować się w obu tych miejscach w czasie rzeczywistym.
Więcej informacji
Firebase to platforma aplikacji, która przechowuje dane w formacie JSON i synchronizuje się ze wszystkimi z innymi klientami w czasie rzeczywistym. Jest on dostępny nawet wtedy, gdy aplikacja przejdzie w tryb offline. W tym samouczku używana jest baza danych czasu rzeczywistego.