12/08/2018, 14:00

Làm việc với Firebase Realtime Database

Firebase Realtime database là một cloud hosted database hỗ trợ đa nền tảng: Android, IOS và Web. Tất cả dữ liệu được lưu trữ ở định dạng JSON và với bất kể một sự thay đổi dữ liệu nào thì có sự phản hồi ngay lập tức, hiển thị đồng bồ trên các nền tảng và các thiết bị. Bài hướng dân này xây dựng ...

Firebase Realtime database là một cloud hosted database hỗ trợ đa nền tảng: Android, IOS và Web. Tất cả dữ liệu được lưu trữ ở định dạng JSON và với bất kể một sự thay đổi dữ liệu nào thì có sự phản hồi ngay lập tức, hiển thị đồng bồ trên các nền tảng và các thiết bị. Bài hướng dân này xây dựng nhằm thể hiện sự phản hồi theo thời gian tực của apps một cách đơn giản khi sử dụng Firebase Realtime database.

Phần 1. Giới thiệu những đặc điểm nổi bật của Firebase Realtime database

1) Dữ liệu được lưu trữ như nào - Định dạng Json

Firebase realtime database lưu trữ dữ liệu theo định dạng JSON. Về cơ bản thì toàn bộ dữ liệu là một JSON tree lớn cùng với nhiều điểm node. Nên khi bạn xây dựng dữ liệu, bạn cần chuẩn bị một cấu trúc json để dễ dàng cho việc truy cập tránh việc các node con bị lồng nhau

Dưới đây là một ví dụ về việc lưu trữ danh sách các hồ sơ người dùng và các bài viết trong cây json. Bạn có thể tìm hiểu về cấu trúc dữ liệu tại đây để thực hành

{
  "users": [
    {
      "name": "Ravi Tamada",
      "email": "ravi@androidhive.info",
      "address": "XXX, XXXX, 1234"
    }
  ],
  "posts": [
    {
      "id": 100,
      "author": "Ravi Tamada",
      "content": "This is awesome firebase realtime database...",
      "timestamp": "13892733894"
    }
  ]
}

2) Dữ liệu offline

Firebase cung cấp sự hỗ trợ tuyệt vời khi nói đến dữ liệu offline. Nó tự động lưu trữ offline khi không có kết nối internet. Tuy nhiên nó sẽ cho phép lưu trữ vào ổ đĩa persistence khi data offline thậm chí khi ứng dụng restart. Ổ đĩa persistence có thể gọi đến bởi dòng code ở phía dưới. Xem hướng dẫn data offline tại đây

FirebaseDatabase.getInstance().setPersistenceEnabled(true);

3) Thực hiện các thao tác CRUD

Trước khi thực hành. Tôi sẽ hướng dẫn các bạn những thông tin cơ bản về việc thực thi các phương thức CRUD

Để thực hiện bất kỳ phương thức nào trên cơ sở dữ liệu cho dù đó có thể được đọc hoặc ghi, bạn cần phải có được các tham chiếu đến cơ sở dữ liệu. Các mã dưới đây cho phép bạn tham chiếu đến nút trên cùng của cơ sở dữ liệu JSON. Từ đây bạn cần phải sử dụng các tên nút con phải đi qua các nút khác

private DatabaseReference mDatabase;

mDatabase = FirebaseDatabase.getInstance().getReference();

3.1. Inserting Data

Để thêm dữ liệu, bạn có thể sử dụng phương thức setValue() trên đường dẫn tham chiếu đến database. Nó sẽ tạo mới và cập nhật giá trị trên đường dẫn được cung cấp. Ví dụ ở dưới đã mã để thêm 1 nút được gọi là “copyright” trên cây json ở mức đỉnh

DatabaseReference mRef = mDatabase.getReference("copyright");

mRef.setValue("©2016 androidhive. All rights Reserved");

Realtime database chấp nhận nhiều loại dữ liệu: String, Long, Double, Boolean, Map<String, Object>, List<Object> để lưu trữ dữ liệu. Bạn cũng có thể sử dụng dữ liệu tùy biến của đối tượng để lưu trữ dữ liệu, điều này rất hữu dụng khi lưu trữ đối tượng vào database một cách trực tiếp

Giả sử bạn muốn lưu trữ thông tin người dùng vào database. Đầu tiên bạn cần tạo User model cùng với constructor rỗng and những thuộc tính khác

@IgnoreExtraProperties
public class User {

    public String name;
    public String email;

    // Default constructor required for calls to
    // DataSnapshot.getValue(User.class)
    public User() {
    }

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

Mọi người dùng cần một ID duy nhất, bạn có thể tạo ra một bằng cách gọi phương thức push () để tạo ra một nút trống rỗng, với khóa duy nhất. Sau đó, được tham chiếu đến nút 'user' bằng phương thức child(). Cuối cùng sử dụng phương thức setValue() để lưu trữ dữ liệu người dùng.

DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference("users");

// Creating new user node, which returns the unique key value
// new user node would be /users/$userid/
String userId = mDatabase.push().getKey();

// creating user object
User user = new User("Ravi Tamada", "ravi@androidhive.info");

// pushing user to 'users' node using the userId
mDatabase.child(userId).setValue(user);

Bằng việc chạy đoạn code ở dưới, 1 nút người dùng mới sẽ được chèn vào database cùng với giá trị khóa duy nhất. Nhìn chung, user id nên được lấy lại bằng cách thực hiện xác thực Firebase trong ứng dụng mà bạn cung cấp AUTHID hoạt động như người sử dụng id.

{
  "users": [
    "-KTYWvZG4Qn9ZYTc47O6" : {
      "email" : "ravi@androidhive.info",
      "name" : "Ravi Tamada"
    },
    {
      ...
    }
  ]
}

3.2. Reading Data

Để đọc dữ liệu, bạn cần đính kèm phương thức ValueEventListener() để tham chiếu database. Sự kiện này sẽ được kích hoạt bất cứ khi nào có sự thay đổi trong dữ liệu trong thời gian thực. Trong onDataChange(), bạn có thể thực hiện các phương thức mong muốn vào dữ liệu mới.

Dưới đây là sự kiện lắng nghe được kích hoạt bất cứ khi nào có sự thay đổi trong dữ liệu hồ sơ người dùng mà đã tạo ra trước đó.

mDatabase.child(userId).addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {

        User user = dataSnapshot.getValue(User.class);

        Log.d(TAG, "User name: " + user.getName() + ", email " + user.getEmail());
    }

    @Override
    public void onCancelled(DatabaseError error) {
        // Failed to read value
        Log.w(TAG, "Failed to read value.", error.toException());
    }
});

3.3. Updating Data

Để cập nhật dữ liệu, bạn có thể sử dụng cùng phương pháp setValue() để passing giá trị mới. Bạn cũng có thể sử dụng phương thức updateChildren() để passing đường dẫn để cập nhật dữ liệu mà không làm ảnh hưởng đến các nút con khác.

Ví dụ, nếu bạn muốn cập nhật chỉ email của người dùng, bạn có thể sử dụng đoạn code bên

String newEmail = 'androidhive@gmail.com';

mDatabase.child(userId).child("email").setValue(newEmail);

3.4. Inserting Data

Để xóa dữ liệu, bạn có thể gọi phương thức removeValue() trong tham chiếu database. Bạn cũng có thể pass qua null để gọi phương thức setValue(), nó giống như phương thức xóa

Bạn có thể tìm hiểu nhiều hơn về CRUD thông qua tài liệu nâng cao ở tại đây

4) Tính bảo mật và quy định

Quy định của Firebase cung cấp một cách để xác định vai trò người dùng khi thực hiện đọc và ghi. Những quy định này sẽ đóng vai trò một lớp bảo mật trên máy chủ trước khi thực hiện bất kỳ hoạt động CRUD. Mặc định các quy định cho phép người dùng thực hiện các hoạt động đọc và ghi chỉ sau khi xác thực.

Các quy tắc dưới đây cho phép người sử dụng chứng thực chỉ để đọc hoặc ghi dữ liệu.

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

Dưới đây quy tắc cho phép tất cả mọi người để đọc và ghi dữ liệu mà không cần xác thực.

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

Bạn cũng có thể sử dụng các quy tắc để xác nhận dữ liệu trước khi chèn vào cơ sở dữ liệu. Ví dụ dưới đây quy tắc xác nhận tên được ít hơn 50 ký tự và gửi email có giá trị sử dụng email thường xuyên.

{
    "rules": {
        ".read": true,
        ".write": true,
        "users": {
            "$user": {
                "name": {
                    ".validate": "newData.isString() && newData.val().length < 50"
                },
                "email": {
                    ".validate": "newData.isString() && newData.val().matches(/^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,4}$/i)"
                }
            }
        }
    }
}

Go through firebase security & rules guide to learn more about the security concepts.

Tìm hiểu về tính bảo mật và quy định của Firebase tại đây để tìm hiểu thêm

Phần 2. Demo ứng dụng sử dụng Firebase Realtime database

Bước 1

Điều đầu tiên bạn cần làm là đi đến https://firebase.google.com/ và tạo một tài khoản để truy cập vào giao diện điều khiển. Sau khi bạn truy cập vào giao diện điều khiển, bạn có thể bắt đầu bằng cách tạo ra các dự án đầu tiên của bạn.

0.png

Bước 2

Cung cấp tên package của project và mã SHA-1. Sau đó file google-services.json sẽ được tự động được tải về sau khi ấn vào nút add app

1.png

Bước 3

Tạo một dự án mới trong Android Studio từ File ⇒ Dự án mới. Trong khi điền các thông tin chi tiết dự án, sử dụng cùng một tên gói mà bạn đã đưa vào giao diện của Firebase

Bước 4

Paste file google-services.json vào thư mục app của dự án của bạn. Bước này rất quan trọng như dự án của bạn sẽ không được xây dựng mà không có tập tin này.

Bước 5

Bây giờ mở build.gradle nằm trong thư mục chính của dự án và thêm đoạn mã google play

build.gradle
dependencies {
        classpath 'com.android.tools.build:gradle:2.2.0'
        classpath 'com.google.gms:google-services:3.0.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }

Bước 6

Mở app/build.gradle sau đó thêm firebase database dependency. Ở cuối file, thêm apply plugin: ‘com.google.gms.google-services’

app/build.gradle
dependencies {
    // Adding support library for this demo app
    compile 'com.android.support:design:24.2.1'

    compile 'com.google.firebase:firebase-database:9.6.1'
}

apply plugin: 'com.google.gms.google-services'

Bước 7

Để lưu trữ hồ sơ người dùng, chúng ta cần một lớp model được gọi User.java. Tạo một lớp có tên User.java và thêm vào dưới đây thuộc tính của lớp. Nếu bạn muốn bạn có thể thêm vài chi tiết tài sản như địa chỉ, điện thoại di động, vv,

User.java
package info.androidhive.firebase;

import com.google.firebase.database.IgnoreExtraProperties;

/**
 * Created by Ravi Tamada on 07/10/16.
 * www.androidhive.info
 */

@IgnoreExtraProperties
public class User {

    public String name;
    public String email;

    // Default constructor required for calls to
    // DataSnapshot.getValue(User.class)
    public User() {
    }

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

Bước 8

Mở file layout của activity_main.xml. Tạo ra layout đơn giản, để bạn có thể nhập dữ liệu hồ sơ để lưu trữ trong cơ sở dữ liệu.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_awidth="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="info.androidhive.firebase.MainActivity">

    <TextView
        android:id="@+id/txt_user"
        android:layout_awidth="wrap_content"
        android:layout_height="wrap_content"
        android:paddingBottom="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_horizontal_margin"
        android:textSize="20dp" />

    <LinearLayout
        android:layout_awidth="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <android.support.design.widget.TextInputLayout
            android:layout_awidth="match_parent"
            android:layout_height="wrap_content">

            <EditText
                android:id="@+id/name"
                android:layout_awidth="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/name"
                android:inputType="textCapWords"
                android:maxLines="1" />

        </android.support.design.widget.TextInputLayout>

        <android.support.design.widget.TextInputLayout
            android:layout_awidth="match_parent"
            android:layout_height="wrap_content">

            <EditText
                android:id="@+id/email"
                android:layout_awidth="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/email"
                android:inputType="textEmailAddress"
                android:maxLines="1" />

        </android.support.design.widget.TextInputLayout>

        <Button
            android:id="@+id/btn_save"
            style="?android:textAppearanceSmall"
            android:layout_awidth="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:background="@color/colorPrimary"
            android:text="@string/action_save"
            android:textColor="@android:color/white"
            android:textStyle="bold" />

    </LinearLayout>

</LinearLayout>

Bước 9

Mở MainActivity.java và thêm đoạn mã ở dưới cần thiết. Mã này là rất đơn giản và dễ hiểu.

Mục tiêu của chúng tôi là tạo ra các cấu trúc JSON như dưới đây, trong đó các 'APP_TITLE' lưu trữ tên ứng dụng.'users' lưu trữ hồ sơ người dùng như là một mảng của các nút.

{
  "app_title" : "Realtime Database",
  "users" : {
    "-KTYWvZG4Qn9ZYTc47O6" : {
      "email" : "ravi@androidhive.info",
      "name" : "Ravi Tamada"
    }
  }
}

GetReference("APP_TITLE") tạo ra một nút tên APP_TITLE mà các cửa hàng tiêu đề trên thanh công cụ.

GetReference("users") được tham chiếu đến nút người sử dụng.

CreateUser() phương pháp cửa hàng một người dùng mới trong cơ sở dữ liệu thời gian thực

UpdateUser() cập nhật phương pháp sử dụng các thông tin như tên và email.

MainActivity.java
package info.androidhive.firebase;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();
    private TextView txtDetails;
    private EditText inputName, inputEmail;
    private Button btnSave;
    private DatabaseReference mFirebaseDatabase;
    private FirebaseDatabase mFirebaseInstance;

    private String userId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Displaying toolbar icon
        getSupportActionBar().setDisplayShowHomeEnabled(true);
        getSupportActionBar().setIcon(R.mipmap.ic_launcher);

        txtDetails = (TextView) findViewById(R.id.txt_user);
        inputName = (EditText) findViewById(R.id.name);
        inputEmail = (EditText) findViewById(R.id.email);
        btnSave = (Button) findViewById(R.id.btn_save);

        mFirebaseInstance = FirebaseDatabase.getInstance();

        // get reference to 'users' node
        mFirebaseDatabase = mFirebaseInstance.getReference("users");

        // store app title to 'app_title' node
        mFirebaseInstance.getReference("app_title").setValue("Realtime Database");

        // app_title change listener
        mFirebaseInstance.getReference("app_title").addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                Log.e(TAG, "App title updated");

                String appTitle = dataSnapshot.getValue(String.class);

                // update toolbar title
                getSupportActionBar().setTitle(appTitle);
            }

            @Override
            public void onCancelled(DatabaseError error) {
                // Failed to read value
                Log.e(TAG, "Failed to read app title value.", error.toException());
            }
        });

        // Save / update the user
        btnSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String name = inputName.getText().toString();
                String email = inputEmail.getText().toString();

                // Check for already existed userId
                if (TextUtils.isEmpty(userId)) {
                    createUser(name, email);
                } else {
                    updateUser(name, email);
                }
            }
        });

        toggleButton();
    }

    // Changing button text
    private void toggleButton() {
        if (TextUtils.isEmpty(userId)) {
            btnSave.setText("Save");
        } else {
            btnSave.setText("Update");
        }
    }

    /**
     * Creating new user node under 'users'
     */
    private void createUser(String name, String email) {
        // TODO
        // In real apps this userId should be fetched
        // by implementing firebase auth
        if (TextUtils.isEmpty(userId)) {
            userId = mFirebaseDatabase.push().getKey();
        }

        User user = new User(name, email);

        mFirebaseDatabase.child(userId).setValue(user);

        addUserChangeListener();
    }

    /**
     * User data change listener
     */
    private void addUserChangeListener() {
        // User data change listener
        mFirebaseDatabase.child(userId).addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                User user = dataSnapshot.getValue(User.class);

                 
0