Android

Firebase, Bài 2.1: Giới thiệu về Realtime Database.

Nhắc tới Firebase thì không thể không nhắc tới Realtime Database – tính năng khởi thủy của “Ngọn lửa”. Đây cũng là dịch vụ trung tâm trong hệ thống các dịch vụ khác của Firebase. Với ưu điểm cực kì lớn là tính realtime được cấu hình và thực hiện sẵn trong hậu cảnh (background), các lập trình viên chỉ còn mỗi công việc vận dụng các hàm có sẵn để dựng ứng dụng mà thôi. Cũng như các bài viết trước, chúng tôi sẽ giới thiệu sơ lược về Firebase Realtime Database trên Android, iOS và Web. Đối với các framework khác như React Native, chúng tôi sẽ trình bày sau nếu có dịp.FirebaseRealtimeDatabase

1. Firebase Realtime Database là gì?

Store and sync data with our NoSQL cloud database. Data is synced across all clients in realtime, and remains available when your app goes offline.

The Firebase Realtime Database is a cloud-hosted database. Data is stored as JSON and synchronized in realtime to every connected client. When you build cross-platform apps with our iOS, Android, and JavaScript SDKs, all of your clients share one Realtime Database instance and automatically receive updates with the newest data.

Có lẽ các bạn đã hiểu rất rõ nghĩa của từ database, tức là cơ sở dữ liệu. Tuy nhiên, realtime là gì? Bạn hẵn đã từng nghe qua realtime rendering, realtime notification, realtime checking. Vui lòng không nên dịch một cách “từng chữ” từ “realtime” ra “thời gian thực” vì đó là một từ khá vô nghĩa.

Hãy hình dung bạn đang trò chuyện với một hoặc nhiều người đối diện. Khi bạn nói, họ ngay lập tức tiếp nhận lời của bạn, và sau đó họ phản hồi lại, và bạn cũng lập tức tiếp nhận các thông tin được truyền tải trong lời của họ. Hai người luân phiên hỏi-đáp và tiếp nhận thông tin một cách tức thời. Ta nói quá trình đó được diễn ra một cách “realtime”. Như vậy, ta có thể hiểu nghĩa cúa “realtime” là “liên tục và ngay lập tức”.

Kết hợp “realtime” và “database”, ta biết đó là một cơ sở dữ liệu được cập nhật liên tục và không gián đoạn dựa trên các tương tác của thiết bị khách (client) và máy chủ (server, ở đây là Firebase server). Khi một thiết bị gửi các thông tin về sự thay đổi, bao gồm vị trí (position, tức là “key”) và nội dung (content, tức là “value”), Firebase Realtime Database ngay lập tức phân tích và áp dụng các thay đổi đó, và đồng thời gửi ngay các thay đổi đó xuống các thiết bị khác đang cùng lắng nghe (listen) cùng một cơ sở dữ liệu đó để chúng cập nhật dữ liệu ngay.

Ứng dụng cơ bản nhất, rõ ràng nhất của Firebase Realtime Database (FRD) là làm ứng dụng chat. Và hầu như, bạn nào khi tìm hiểu về FRD đều làm một ứng dụng chat để “cho biết”. Tuy nhiên, vẫn có đó những tiềm năng, những ứng dụng khác của FRD mà bạn có thể sáng tạo vận dụng.

2. Mô hình của Firebase Realtime Database.

Có lẽ các bạn đã ít nhiều quen thuộc với SQL Database. SQL Database được tổ chức dưới dạng các bảng, mỗi bảng bao gồm các hàng và các cột. Các cột đại diện cho các thuộc tính của các đối tượng, trong khi các hàng đại diện cho các đối tượng. Ví dụ, ta có bảng quản lí sinh viên, trong đó các cột là STT, Họ, Tên, Giới tính, Ngày sinh, Địa chỉ liên hệ, còn mỗi hàng là một sinh viên cụ thể. Có bao nhiêu sinh viên thì sẽ có bấy nhiêu hàng tương ứng.

Firebase Realtime Database không được tổ chức như vậy. Nó được tổ chức theo dạng cây (trees), giống như dạng cây thư mục (folder tree) mà các bạn đã quá quen thuộc trong Windows Explorer. Tuy nhiên, một nhánh (branch) không được chứa đồng thời nhiều dữ liệu khác nhau. Trong Windows Explorer 1 thư mục mẹ có thể chứa nhiều thư mục con và các tập tin nằm ngang hàng với các thư mục con kia, trong thư mục con lại có các thư mục cháu và các tập tin cùng hàng với thư mục cháu. Trong Firebase Realtime Database, mỗi nhánh giống như một container, chỉ chứa hoặc là dữ liệu ứng với nhánh đó (tức là value tương ứng với key), hoặc một tập hợp các nhánh con cũng được tổ chức theo một cách tương tự.

FirebaseRealtimeDatabase-Example

Mỗi branch được đại diện bởi một key và value tương ứng. Luôn luôn, các keys luôn có dạng là (NS)String. Còn các values thì, như tôi đã trình bày phía trên, có thể trực tiếp là một đối tượng String, một đối tượng Int, hay là một Object chứa nhiều thông tin bên trong, được thể hiện dưới dạng một nhánh con. Và nhánh con đó được coi như là một value ứng với key đó. Do được tổ chức theo hình thức này, mà dữ liệu được gửi về các thiết bị khách sẽ đương nhiên có dạng là JSON.

Các key cùng tầng phải là duy nhất. Các key khác tầng có thể được phép giống nhau. Để đảm bảo các key cùng tầng là duy nhất, thì “Ngọn lửa” cung cấp cho chúng ta các hàm để yêu cầu Firebase server tạo ngẫu nhiên các giá trị key duy nhất, chẳng hạn như các “KI2” keys trong hình minh họa bên trên. Đó là push() trong Android và Web, hay childByAutoId() trên iOS.

3. Nguyên tắc cập nhật các values.

Khi bạn thêm một bộ dữ liệu mới, tức bao gồm 1 bộ key – value mới, thì sẽ xảy ra hai trường hợp sau.

  • Key chưa tồn tại: Firebase Database sẽ tạo một Object mới tại vị trí mà bạn muốn lưu, với K-V là các giá trị bạn đã định nghĩa. Event này được gọi là “add”, tức là thêm.
  • Key đã tồn tại: Firebase Database sẽ sửa value tại vị trí của key đó theo value mới. Event này được gọi là “change”.

Ví dụ, đối với cơ sở dữ liệu trong hình trên, bạn sẽ thấy là các keys KI2 trong users/users/ có giá trị hoàn toàn là duy nhất. Nếu tôi add 1 Object có key là KI2H0sKk5A6Ny1plBly thì nguyên cả bộ fieldName: “messageField” và text:”this is my third message” sẽ bị thay đổi thành giá trị mới. Tóm lại, khi bạn đưa 1 object lên FRD, nếu chưa có key đó thì FRD sẽ tạo một Object có key mà bạn muốn và add value. Còn nếu đã tồn tại key rồi thì nó sẽ sửa đổi value tại vị trí key đó. Trường hợp bạn dùng hàm push() hoặc childByAutoId() thì CSDL của “Ngọn lửa” sẽ gián tiếp tạo một Object có key là một (NS)String ngẫu nhiên (và dài dòng) và value là cái Object của bạn.

4. Cái hay và hạn chế của Firebase Realtime Database:

Như bạn đã thấy, FRD được tổ chức theo dạng cây – nhánh chứ không phải dạng hàng – cột và đây có thể là một vấn đề hết sức nan giải cho những lập trình viên đã và đang thiết kế cơ sở dữ liệu dạng bảng. Thực ra không hẳn là một hạn chế, vì FRD được tạo ra dựa trên ý tưởng này ngay từ đầu. Và họ cũng không có ý định cho ra mắt thêm một dạng cơ sở dữ liệu dạng bảng, ít nhất là cho tới thời điểm hiện tại.

Rõ ràng, cơ cấu tổ chức của FRD tỏ ra có nhiều lợi thế rõ rệt hơn so với dạng bảng của SQLDatabase (SQLD). Chẳng hạn, FRD thích hợp hơn hẳn để làm ứng dụng chat. Hơn thế nữa, FRD tỏ ra linh hoạt (flexible) hơn so với SQLD, chẳng hạn như việc bạn thêm một cột mới vào CSDL SQLD đã có là cả một vấn đề, đặc biệt nhất là vấn đề về sự tương thích giữa nhiều phiên bản ứng dụng chạy trên thiết bị khách (client), chưa kể đến việc các câu Schema phải chuẩn nữa. Trong khi đó, đối với FRD, bạn chỉ cần thêm một trường mới vào Object của mình. Đối với các Object cũ, các trường không có dữ liệu sẽ đơn giản là được trả về kết quả null (Android, web) hoặc nil (iOS, macOS).

Ví dụ, bạn đang có một class Student(String name, String birthday). Đối với SQLD, bạn tạo một CSDL có hai cột là NAME và BIRTHDAY. Đối với FRD, bạn chỉ cần 2 nhánh name và birthday trong một Object làm value là được. Sau khi đã sử dụng CSDL đó để nhập liệu được gần 2 năm thì bấy giờ, bạn được yêu cầu bổ sung thêm một trường nữa là lớp học và cột này phải là NON NULL. Vậy, bạn làm cách nào? Đối với SQLD, bạn sẽ ALTER TABLE để thêm một cột. Ố kề, xong câu Schema rồi và đã có cột mới. Ơ, nhưng có cái gì đó sai sai, chẳng hạn như những học sinh đầu tiên đã có thông tin về lớp học đâu? Khi fetch thông tin về thì chắc chắn sẽ xảy ra mâu thuẫn, và đối với một lập trình viên thuộc dạng hay lấy cụm từ “em mới học” ra để bào biện thì nước biển sẽ mặn lắm.

Còn đối với FRD thì bạn chỉ đơn giản là bổ sung thêm một trường mới vào Student object của bạn, tức là Student(String name, String birthday, String class). Đối với các học sinh không có thông tin về class, thì khi bạn fetch các thông tin về, String class chỉ đơn giản là bị null/nil và bạn chỉ cần xét if (class != null) là được.

Tuy nhiên, nếu bạn cần lắm sự kết nối giữa các hàng trong một bảng, chẳng hạn như việc quản lí tiền lương thì SQLD cũ kĩ vẫn sẽ tỏ ra vượt trội hơn, mặc dù bạn vẫn có khả năng xoay sở đối với FRD. Còn nếu bạn phải quản lí các dữ liệu theo kiểu liên bảng, tức là bạn phải thực hiện thao tác JOIN các dữ liệu trong SQLD, có thể việc thực hiện các thao tác thay thế trên FRD trên thực tế sẽ khó khăn hơn là bạn nghĩ.

Một vấn đề quan trọng hơn nữa, là PRIMARY KEY. Các bạn ít nhiều đã biết rồi, PRIMARY KEY là một trong những điều cần thiết để quản lí hiệu quả CSDL SQLD. Tuy nhiên trong FRD thì hoàn toàn không tồn tại khái niệm PRIMARY KEY, do đó bạn sẽ cần cẩn thận trong việc quản lí các objects để tránh bị cập nhật sai các dữ liệu đã có sẵn.

Tóm lại, Firebase Realtime Database ngoài ưu điểm mang tính realtime thì còn có tính linh hoạt rất cao, dễ dàng trong việc bổ sung hoặc cắt bớt các trường trong một Object. Tuy nhiên, sự liên hệ tương ứng giữa các đối tượng trong FRD sẽ là một bài toán không hề dễ và cách xử lí phụ thuộc rất nhiều vào trình độ và kinh nghiệm của lập trình viên, cũng như bản chất và mục đích của bản thân CSDL được tạo.

5. Tích hợp Firebase Realtime Database vào App project và sơ lược về các Value Events.

Như thường lệ, chúng tôi sẽ hướng dẫn các bạn tích hợp Firebase Realtime Database vào các dự án ứng dụng cho Android, iOS (Swift) và Web. Nhưng trước hết, hãy đảm bảo là bạn đã tích hợp Firebase vào project của mình. Chúng tôi cũng khuyến nghị bạn nên sử dụng Authenticator để bảo vệ hệ thống dữ liệu. Xin vui lòng xem các bài viết trước để có thêm thông tin. Nếu bạn muốn tạm thời bỏ qua Authentication, hãy cập nhật Database Rules thông qua Firebase Console như sau:

5.1. Đối với Android:

Để thông báo cho Gradle compile thêm bộ thư viện Realtime Database, bạn chỉ việc thêm dòng sau vào tập tin build.gradle:app như các lần trước:

Ta tiếp tục khai báo các instance như bên dưới. Các event sẽ được handle bởi DatabaseReference myRef.

Trong đó, path là đường dẫn tới tới khu vực mà bạn muốn thao tác trong FRD. Mặc định là FRD sẽ “thả” bạn ở vị trí gốc, tức là “brilliant-fire-3159” như trong hình minh họa.

FirebaseRealtimeDatabase-Example

Nếu tôi muốn truy cập vào “users” ngay dưới “brilliant-fire-3159” thì tôi chỉ việc gán cho path là “users” mà thôi, nghĩa là:

Còn nếu tôi muốn truy cập trực tiếp vào “b797bdd4-a193…”, tức là tôi chỉ thao tác từ đó trở vào bên trong của nó thì thôi sẽ gán giá trị cho path như sau:

Nhưng tốt hơn hết là bạn nên dùng hàm child(String child), và hàm này thì tôi sẽ trình bày trong các bài viết tới. Dưới đây là ví dụ:

Sau khi đã trỏ tới địa chỉ mong muốn thì bạn có thể setValue tương ứng với key đó, chẳng hạn như tôi sẽ setValue là một String “This is my value” thì tôi làm như sau.

Còn nếu tôi muốn tạo một Object có key ngẫu nhiên để bọc lại Object(fieldName, text) của tôi với hàm push() thì tôi làm như sau:

Đối với các ValueEventListener thì bạn cũng gán listener vào myRef, cụ thể:

5.2. Đối với iOS:

Bạn đã quá quen thuộc với việc sử dụng CocoaPods để thêm các thư viện Firebase vào Xcode project của mình rồi, và lần này cũng không là ngoại lệ:

Tiếp tục, ta tạo các instances như sau:

Mặc định là FRD sẽ “thả” bạn ở vị trí gốc, tức là “brilliant-fire-3159” như trong hình minh họa dưới đây. Nếu bạn muốn truy cập vào bên trong thì dùng hàm child(). Tôi sẽ trình bày về child() trong các bài viết sau.

FirebaseRealtimeDatabase-Example

Nếu tôi muốn truy cập vào “users” ngay dưới “brilliant-fire-3159” thì tôi chỉ việc gán ref như bên dưới:

Còn nếu tôi muốn truy cập trực tiếp vào “b797bdd4-a193…”, tức là tôi chỉ thao tác từ đó trở vào bên trong, thì ngoài hàm child(), tôi có thể dùng referenceWithPath(path) cho ref như sau:

Sau khi đã trỏ tới địa chỉ mong muốn thì bạn có thể setValue tương ứng với key đó, chẳng hạn như tôi sẽ setValue là một String “This is my value” thì tôi làm như sau.

Còn nếu tôi muốn tạo một Object có key ngẫu nhiên để bọc lại Object(fieldName, text) của tôi với hàm childByAutoId() thì tôi làm như sau:

Để quan sát sự thay đổi của value, ta cũng sẽ thao tác với biến ref. Cụ thể là như bên dưới:

5.3. Đối với Web:

Việc thêm Firebase Realtime Database SDK hoàn toàn tương tự và đơn giản như các sản phẩm khác. Trong JS, các bạn chuẩn bị các instances như sau, lưu ý là bạn phải điều chỉnh các giá trị cho đúng với Firebase project của bạn:

Mặc định là FRD sẽ “thả” bạn ở vị trí gốc, tức là “brilliant-fire-3159” như trong hình minh họa dưới đây. Nếu bạn muốn truy cập vào bên trong thì bạn cho ghép String hoặc dùng hàm child(String child).

FirebaseRealtimeDatabase-Example

Nếu tôi muốn truy cập vào “users” ngay dưới “brilliant-fire-3159” thì tôi chỉ việc gán cho path là “users” mà thôi, nghĩa là:

Còn nếu tôi muốn truy cập trực tiếp vào “b797bdd4-a193…”, tức là tôi chỉ thao tác từ đó trở vào bên trong của nó thì thôi sẽ gán giá trị cho path như sau:

Hoặc nếu dùng hàm child(String child) thì tôi có hàm bên dưới:

Sau khi đã trỏ tới địa chỉ mong muốn thì bạn có thể setValue tương ứng với key đó, chẳng hạn như tôi sẽ setValue là một String “This is my value” thì tôi làm như sau.

Còn nếu tôi muốn tạo một Object có key ngẫu nhiên để bọc lại Object(fieldName, text) của tôi với hàm push() thì tôi làm như sau:

Để lắng nghe sự thay đổi của value, ta cũng sẽ thao tác với biến ref. Cụ thể là như sau:

Vậy là bạn đã xong các bước cơ bản để thao tác với Firebase Realtime Database rồi đó. Hẹn gặp lại trong các bài viết sắp tới.

9 thoughts on “Firebase, Bài 2.1: Giới thiệu về Realtime Database.”

  1. cảm ơn bạn bài viết rất bổ ích.
    xin cho mình hỏi thăm. mình viết 1 ứng dụng nhỏ cho 1 nhóm xài.
    làm sao khi có 1 thành viên cập nhật dữ liệu thì. app có thể phát hiện là dữ liệu đang thay đổi và thay đổi ở đâu không.
    xin cảm ơn bạn rất nhiều

    1. Cảm ơn bạn đã ghé thăm trang.
      Khi bạn thiết lập các instances của Firebase xong thì ứng dụng tự “lắng nghe”/”quan sát” sự thay đổi dữ liệu. Các thiết bị đang cùng trỏ vào một node dữ liệu sẽ thay đổi dữ liệu ngay lập tức, bạn hãy tham khảo bài tiếp theo.
      Vd bạn có 1 lúc cả 3 thiết bị là Android, iOS và trình duyệt web cùng đang “lắng nghe”/”quan sát” một node dữ liệu, thì khi bạn thay đổi trên một nơi thì các nơi khác cũng thay đổi theo.

    2. Còn thay đổi ở đâu, thì bạn có thể dùng hàm snapshot.getKey() với Android hay snapshot.key với iOS và Web, nó sẽ định vị được vị trí của đối tượng, yêu cầu là bạn phải trỏ đúng vào node bạn muốn.

  2. Chào bạn, hiện mình muốn tạo một thông báo như facebook, kho có thành viên comment lên bài viết đó thì tất cả các thành viên đã comment bài viết trước điều nhận được thông báo.

    Hiện tại phần thông báo chúng mình đã làm được nhưng muốn phân riêng ra, chỉ những ai comment bài đó mới nhận được thông báo đó.

    1. Bạn sẽ cần 1 cái server riêng để quản lí các deviceId/tokens của các thiết bị sẽ nhận được thông báo, từ server đó sẽ yêu cầu firebase cloud messaging gửi message xuống các thiết bị tương ứng đó. Nếu hiện tại, bạn có hướng xử lí khác mà không cần server riêng, tuy nhiên về sau, khi bạn thêm thắt các tính năng mới thì cũng sẽ phải cần tới server riêng của bạn. Nên mình khuyên bạn nên đầu tư 1 cái máy chủ ngay bây giờ luôn.

  3. Ở ví dụ trên thì làm sao đổ ra màn hình giá trị fileName,và text theo đúng cái lớp chứa nó ạ.
    Ví dụ nó phân biệt fileName, và text của lớp nào ấy ạ.

  4. tui muốn truy vấn thì làm như thế nào ban?…. đại loại như tìm thấy 1 tài khoản… sao đó tôi có thể thao tác những công việc khác hoặc không…. Web php

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.