개요
이 튜토리얼에서는 Firebase 애플리케이션 플랫폼을 사용하여 대화형 지도를 만드는 방법을 설명합니다. 아래 지도에서 여러 위치를 클릭하여 히트맵을 만들어 보세요.
아래 섹션에는 이 튜토리얼에서 지도를 만드는 데 필요한 전체 코드가 표시되어 있습니다.
/** * 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>
직접 해보기
코드 창의 오른쪽 상단에 있는 <>
아이콘을 클릭하여 JSFiddle에서 이 코드를 실험할 수 있습니다.
<!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>
시작하기
이 튜토리얼의 코드를 사용하여 자신만의 Firebase 지도 버전을 개발할 수 있습니다. 이렇게 하려면 텍스트 편집기에서 새 파일을 만들어 index.html
로 저장하세요.
이 파일에 추가할 수 있는 코드를 이해하려면 다음 섹션을 읽어보세요.
기본 지도 만들기
이 섹션에서는 기본 지도를 설정하는 코드를 설명합니다. 이 방법은 Maps JavaScript API를 시작할 때 지도를 만든 방법과 비슷합니다.
아래 코드를 index.html
파일에 복사합니다. 이 코드는 Maps JavaScript API를 로드하고 지도를 전체 화면으로 만듭니다. 또한 나중에 튜토리얼에서 히트맵을 만드는 데 필요한 시각화 라이브러리도 로드합니다.
<!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>
코드 샘플에서 YOUR_API_KEY
를 클릭하거나 안내에 따라 API 키를 가져옵니다. YOUR_API_KEY
를 애플리케이션의 API 키로 바꾸세요.
다음 섹션에서는 Firebase 지도를 만드는 자바스크립트 코드를 설명합니다. 아래와 같이 코드를 복사하여 firebasemap.js
파일에 저장하고 스크립트 태그 간에 참조할 수 있습니다.
<script>firebasemap.js</script>
또는 이 튜토리얼 시작 부분의 전체 샘플 코드에서와 같이 스크립트 태그 안에 코드를 직접 삽입할 수도 있습니다.
아래 코드를 firebasemap.js
파일 또는 index.html
파일의 빈 스크립트 태그 사이에 추가합니다. 이는 지도 객체를 초기화하는 함수를 만들어 프로그램을 실행하는 시작점입니다.
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 }); }
클릭 가능한 히트맵을 더 쉽게 사용할 수 있도록 위의 코드에서는 지도 스타일 지정을 사용하여 (클릭되면 정보 창을 표시하는) 관심 장소 및 역/정류장을 사용 중지합니다. 또한 실수로 확대/축소되는 것을 방지하기 위해 더블클릭 시 확대/축소를 사용 중지합니다. 지도 스타일 지정에 대해 자세히 알아보세요.
API가 완전히 로드된 후 아래 스크립트 태그의 콜백 매개변수가 HTML 파일의 initMap()
함수를 실행합니다.
<script defer
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY
&libraries=visualization&callback=initMap">
</script>
다음 코드를 추가하여 지도 상단에 텍스트 컨트롤을 만듭니다.
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); }
다음 코드를 initMap
함수 내부의 var map
뒤에 추가하여 텍스트 컨트롤 상자를 로드합니다.
// 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);
코드로 만든 Google 지도를 보려면 웹브라우저에서 index.html
파일을 여세요.
Firebase 설정
이 애플리케이션을 공동 작업 애플리케이션으로 만들려면 클릭을 모든 사용자가 액세스할 수 있는 외부 데이터베이스에 저장해야 합니다. Firebase 실시간 데이터베이스가 이러한 목적에 적합하며 SQL 지식이 필요하지 않습니다.
먼저 무료로 Firebase 계정에 가입합니다.
Firebase를 처음 사용하는 경우 'My First App'이라는 이름의 새로운 앱이 표시됩니다. 새 앱을 만드는 경우 새 이름과 firebaseIO.com
으로 끝나는 맞춤 Firebase URL을 지정할 수 있습니다. 예를 들어 앱 이름을 'Jane's Firebase Map', URL을 https://janes-firebase-map.firebaseIO.com
으로 지정할 수 있습니다. 이 URL을 사용하여 데이터베이스를 자바스크립트 애플리케이션에 연결할 수 있습니다.
아래 행을 HTML 파일의 <head>
태그 뒤에 추가하여 Firebase 라이브러리를 가져옵니다.
<script src="www.gstatic.com/firebasejs/5.3.0/firebase.js"></script>
다음 행을 자바스크립트 파일에 추가합니다.
var firebase = new Firebase("<Your Firebase URL here>");
Firebase에 클릭 데이터 저장
이 섹션에서는 지도의 마우스 클릭에 대한 데이터를 Firebase에 저장하는 코드를 설명합니다.
지도의 모든 마우스 클릭에 대해 다음 코드는 전역 데이터 객체를 만들고 그 정보를 Firebase에 저장합니다. 이 객체는 클릭의 latLng 및 타임스탬프, 클릭을 생성한 브라우저의 고유 ID와 같은 데이터를 기록합니다.
/** * Data object to be written to Firebase. */ var data = { sender: null, timestamp: null, lat: null, lng: null };
다음 코드는 Firebase 보안 규칙을 준수하면서 지도에서 트래픽 속도를 관리하는 데 도움이 되는 각 클릭의 고유한 세션 ID를 기록합니다.
/** * 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. } }); }
아래 코드의 다음 섹션은 Firebase 데이터베이스에 '하위 요소'를 추가하는 지도의 클릭을 리슨합니다. 이 경우 snapshot.val()
함수는 항목의 데이터 값을 가져와 새 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); } );
아래 코드는 다음 작업을 실행하도록 Firebase를 설정합니다.
- 지도의 클릭 리슨. 클릭이 발생하면 앱에서 타임스탬프를 기록한 다음 Firebase 데이터베이스에 '하위 요소'를 추가합니다.
- 10분 이상 지난 지도의 모든 클릭을 실시간으로 삭제
/** * 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); }); }
이 섹션의 모든 자바스크립트 코드를 firebasemap.js
파일에 복사합니다.
히트맵 만들기
다음 단계에서는 지도의 여러 위치에서 발생한 상대적인 클릭 수를 그래픽으로 보여주는 히트맵을 표시합니다. 자세한 내용은 히트맵 가이드를 참고하세요.
initMap()
함수 내에 아래 코드를 추가하여 히트맵을 만듭니다.
// Create a heatmap. var heatmap = new google.maps.visualization.HeatmapLayer({ data: [], map: map, radius: 16 });
아래 코드는 initFirebase
, addToFirebase
, getTimestamp
함수를 트리거합니다.
initAuthentication(initFirebase.bind(undefined, heatmap));
히트맵을 클릭해도 지점은 아직 생성되지 않습니다. 지도에 지점을 만들려면 지도 리스너를 설정해야 합니다.
히트맵에 지점 만들기
아래 코드는 initMap()
내 지도를 만드는 코드 뒤에 리스너를 추가합니다. 이 코드는 각 클릭의 데이터를 리슨하고 클릭의 위치를 Firebase 데이터베이스에 저장하고 히트맵에 지점을 표시합니다.
// 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); });
지도에서 위치를 클릭하여 히트맵에 포인트를 만듭니다.
이제 Firebase 및 Maps JavaScript API를 사용하여 완벽하게 작동하는 실시간 애플리케이션이 생성되었습니다.
히트맵을 클릭하면 이제 클릭의 위도와 경도가 Firebase 데이터베이스에 표시됩니다. Firebase 계정에 로그인하고 앱의 데이터 탭으로 이동하면 이 정보를 볼 수 있습니다. 이제 다른 사람이 내 지도를 클릭하면 나와 그 사람이 지도에서 지점을 볼 수 있습니다. 사용자가 페이지를 닫은 후에도 클릭의 위치가 유지됩니다. 실시간 공동작업의 기능을 테스트하기 위해 두 개의 별도 창에서 페이지를 엽니다. 마커가 두 창 모두에 실시간으로 표시됩니다.
자세히 알아보기
Firebase는 데이터를 JSON으로 저장하고 연결된 모든 클라이언트와 실시간으로 동기화하는 애플리케이션 플랫폼입니다. Firebase는 앱이 오프라인 상태여도 사용할 수 있습니다. 이 튜토리얼에서는 Firebase의 실시간 데이터베이스를 사용합니다.