Tạo Cloud Backend Cho Ứng Dụng Android trong Firebase
Với Firebase, bạn có thể lưu trữ và đồng bộ hóa dữ liệu lên NoSQL cloud database. Dữ liệu được lưu trữ ở dạng JSON, được đồng bộ hóa với tất cả client được kết nối theo thời gian thật, và vẫn dùng được khi ứng dụng offline. Firebase cung cấp các API cho phép bạn xác minh người dùng thông qua email ...
Với Firebase, bạn có thể lưu trữ và đồng bộ hóa dữ liệu lên NoSQL cloud database. Dữ liệu được lưu trữ ở dạng JSON, được đồng bộ hóa với tất cả client được kết nối theo thời gian thật, và vẫn dùng được khi ứng dụng offline. Firebase cung cấp các API cho phép bạn xác minh người dùng thông qua email và password, Facebook, Twitter, GitHub, Google, xác minh ẩn danh, hoặc liên kết với các hệ thống xác minh đã có. Bên cạnh khả năng xác minh cùng Realtime Database, Firebase còn “ăn nằm với” nhiều dịch vụ khác như Cloud Messaging, Storage, Hosting, Remote Config, Test Lab, Crash Reporting, Notification, App Indexing, Dynamic Links, Invites, AdWords, AdMob.
Bài viết này sẽ hướng dẫn các bạn cách tạo lập một ứng dụng đơn giản, giúp bạn làm quen với phương pháp lưu-nhận dữ liệu từ Firebase, cũng như cách xác minh người dùng, trao quyền đọc/viết dữ liệu và phê duyệt dữ liệu trên server.
Bạn có thể tìm đoạn code của project này trên GitHub.
Setting up the Project
Để bắt đầu, bạn hãy tạo project tên “To Do”. Ở cửa sổ tiếp theo, set Minimum SDK thành API 15, và lựa chọn Basic Activity ở cửa sổ kế tiếp nữa. Nhấp Finish tại cửa sổ cuối cùng, để tất cả setting như mặc định.
Trước khi khởi động dự án Android, hãy truy cập vào firebase.google.com và tạo tài khoản. Sau khi đăng nhập vào tài khoản, tìm đến Firebase console và tạo project mới (project này sẽ lưu trữ tất cả dữ liệu của bạn).
Nhập tên quốc gia/khu vực của project.
Phần country/region đại diện cho quốc gia/khu vực của công ty/tổ chức bạn đang làm việc. Lựa chọn của bạn tại đây cũng sẽ thiết lập giá trị tiền tệ thích hợp để sử dụng trong các bản báo cáo doanh thu. Sau khi đặt tên (Tôi dùng SPToDoApp) và khu vực cho project, nhấp vào Create Project. Như vậy, bạn đã tạo được project, và console của project sẽ mở lên. Từ console của project, nhấn vào tùy chọn Add Firebase to your Android App.
Nhập tên package của Android project vào cửa sổ pop up. Nếu ứng dụng của bạn dự định dùng các dịch vụ Google như Google Sign-In, App Invites, Dynamic Links,… thì bạn sẽ phải cung vấp SHA-1 của signing certificate. Vì ứng dụng trong bài viết không dùng đến các dịch vụ này, nên ta sẽ để trống. Khi bạn muốn thêm những dịch vụ này vào ứng dụng, truy cập vào đây lấy thông tin bằng keytool để có SHA1-1 hash bạn cần.
Nhấp vào Add App, một file google-services.json sẽ được tải xuống máy tính. trang tiếp theo của hộp hội thoại sẽ hiển thị các chỉ dẫn thay thế file JSON đã tải xuống. Định vị file và chuyển file này vào thư mục root mô-đun ứng dụng của project Android.
File JSON chứa các tùy chọn cài đặt cần thiết để giao tiếp với server Firebase, với các thông tin như URL đến Firebase project, API key,… Trong phiên bản Firebase trước, bạn phải lưu trữ thủ công những thông tin này trong code của ứng dụng. Nhưng giờ đây, quy trình này đã được đơn giản hóa bằng một file thông tin duy nhất.
Nếu đang dùng version control và lưu trữ code trong public repo, bạn nên xem xét đặt file google-services.json ngay trong file .gitignore, để tránh public thông tin này.
Khi đã hoàn thành, tiếp tục nhấp Continue trên cửa sổ hội thoại và chuyển sang các bước tiếp theo.
Vào file build.gradle cấp project, thêm đoạn sau vào node buildscript > dependencies. Bước này nhằm thêm plugin dịch vụ Google cho Gradle, và load file google-services.json bạn tải trước đó.
classpath 'com.google.gms:google-services:3.0.0'
Hãy kiểm tra kỹ bạn đã edit đúng file gradle chưa.
Sau đó, trong file build.gradle cấp ứng dụng, thêm đoạn sau vào cuối file để kích hoạt Gradle plugin.
apply plugin: 'com.google.gms.google-services'
Một lần nữa, kiểm tra xem có đúng file build.gradle hay không.
Sau đó, thêm các dependency sau vào cùng file đó. Firebase dùng các SDK khác nhau cho từng tính năng khác nhau. Đến đây, bạn hãy thêm thư viện cần có để dùng được Realtime Database và một thư viện nữa để Xác minh.
compile 'com.google.firebase:firebase-database:9.4.0' compile 'com.google.firebase:firebase-auth:9.4.0'
Chọn menu File -> New -> Activity -> Empty Activity để tạo activity trống, và đặt tên là LogInActivity. Tạo một activity với tên SignUpActivity.
Thay đổi nội dung trong file layout activity_log_in.xml thành:
< ?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_awidth="match_parent" android:layout_height="match_parent" 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=".LoginActivity" > <edittext android:id="@+id/emailField" android:layout_awidth="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:ems="10" android:inputType="textEmailAddress" android:hint="@string/email_hint" > <requestfocus></requestfocus> </edittext> <edittext android:id="@+id/passwordField" android:layout_awidth="match_parent" android:layout_height="wrap_content" android:layout_alignLeft="@+id/emailField" android:layout_below="@+id/emailField" android:ems="10" android:hint="@string/password_hint" android:inputType="textPassword"></edittext> <button android:id="@+id/loginButton" android:layout_awidth="match_parent" android:layout_height="wrap_content" android:layout_alignLeft="@+id/passwordField" android:layout_below="@+id/passwordField" android:text="@string/login_button_label"></button> <textview android:id="@+id/signUpText" android:layout_awidth="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/loginButton" android:layout_centerHorizontal="true" android:layout_marginTop="69dp" android:text="@string/sign_up_text"></textview> </relativelayout>
Thay đổi nội dung trong file layout activity_sign_up.xml thành:
< ?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_awidth="match_parent" android:layout_height="match_parent" 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=".SignUpActivity" > <edittext android:id="@+id/emailField" android:layout_awidth="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:ems="10" android:inputType="textEmailAddress" android:hint="@string/email_hint" > <requestfocus></requestfocus> </edittext> <edittext android:id="@+id/passwordField" android:layout_awidth="match_parent" android:layout_height="wrap_content" android:layout_alignLeft="@+id/emailField" android:layout_below="@+id/emailField" android:ems="10" android:inputType="textPassword" android:hint="@string/password_hint"></edittext> <button android:id="@+id/signupButton" android:layout_awidth="match_parent" android:layout_height="wrap_content" android:layout_alignLeft="@+id/passwordField" android:layout_below="@+id/passwordField" android:text="@string/sign_up_button_label"></button> </relativelayout>
Những layout này sẽ dùng để theo dõi Login và Signup.
Thêm đoạn dưới đây vào values/strings.xml.
<string name="password_hint">Password</string> <string name="email_hint">Email</string> <string name="sign_up_button_label">Sign Up</string> <string name="signup_error_message">Please make sure you enter an email address and password!</string> <string name="signup_error_title">Error!</string> <string name="signup_success">Account successfully created! You can now Login.</string> <string name="login_error_message">Please make sure you enter an email address and password!</string> <string name="login_error_title">Error!</string> <string name="login_button_label">Login</string> <string name="sign_up_text">Sign Up!</string> <string name="title_activity_login">Sign in</string> <string name="add_item">Add New Item</string> <string name="action_logout">Logout</string>
Trong activity_main.xml, xóa bỏ markup FloatingActionButton.
<android .support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_awidth="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="@dimen/fab_margin" android:src="@android:drawable/ic_dialog_email"></android>
Và trong MainActivity.java, hãy xóa luôn đoạn code FAB.
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } });
Trong res/menu/menu_main.xml, thay thế settings item bằng Logout item sau:
<item android:id="@+id/action_logout" android:orderInCategory="100" android:title="@string/action_logout" app:showAsAction="never"></item>
Mở MainActivity và thay thế id action_settings với id action_logout trong onOptionsItemSelected(MenuItem).
if (id == R.id.action_logout) { return true; }
Menu item này sẽ đăng thoát người dùng.
Thay đổi content_main.xml thành:
< ?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_awidth="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".MainActivity" tools:showIn="@layout/activity_main" android:orientation="vertical"> <listview android:id="@+id/listView" android:layout_awidth="match_parent" android:layout_height="0dp" android:layout_weight="1"> </listview> </linearlayout><linearlayout android:layout_awidth="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:layout_gravity="bottom"> <edittext android:id="@+id/todoText" android:layout_awidth="match_parent" android:layout_height="wrap_content"></edittext> <button android:id="@+id/addButton" android:layout_awidth="match_parent" android:layout_height="wrap_content" android:text="@string/add_item"></button> </linearlayout>
activity_main.xml có tag include, chỉ đến và load content_main.xml. Ứng dụng sẽ hiển thị các To Do item trong một list view (giao diện danh sách) chiếm dụng phần lớn màn hình. Ở cuối màn hình, bạn sẽ thấy một trường edit text và nút nhấn để thêm item vào danh sách.
Khi đã thiết đặt xong UI của ứng dụng, bạn đến đây sẽ phải tìm cách lưu trữ và truy xuất dữ liệu đến/từ Firebase.
Bảo mật và Quy luật
Trước khi có thể truy xuất và lưu trữ dữ liệu từ server Firebase, bạn cần phải thiết đặt các bước xác minh và thêm quy luật để giới hạn truy cập đến dữ liệu và phê duyệt input của người dùng trước khi lưu.
Authentication (Xác minh)
API của Firebase có các phương thức xác minh built-in như sau: email/password, Facebook, Twitter, GitHub, Google and xác minh ẩn danh. Trong bài viết, ta sẽ xét đến xác minh email và password.
Vào Firebase console, nhấn vào project để mở Dashboard của project.
Nếu lựa chọn Database từ panel bên trái, bạn có thể thấy được dữ liệu của project theo định dạnh JSON.
Firebase lưu trữ tất cả dữ liệu database dưới dạng JSON object, nên sẽ không có table hay record. Khi thêm dữ liệu vào cây JSON, nó sẽ trở thành một key trong cấu trúc JSON đã có.
Lúc này, bạn chỉ thấy được node root.
Nếu bạn rơ chuột trên một node, bạn sẽ thấy các điều khiển + và x mà bạn có thể sử dụng để thêm dữ liệu vào cây hay để chỉ xóa riêng node đó.
Từ panel bên trái, nhấp vào Auth và sau đó lựa chọn tab Sign In Method phía bên phải. Kích hoạt xác minh Email/Password từ các nhà cung cấp được hỗ trợ.
Trong Android Studio, thêm method sau vào MainActivity.
private void loadLogInView() { Intent intent = new Intent(this, LogInActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); }
Đoạn lệnh này sẽ điều hướng đến giao diện Login và làm sạch activity stack. Đồng thời ngăn người dùng quay lại activity chính khi họ nhấn Back button từ giao diện login.
Thêm hai biến sau vào class:
private FirebaseAuth mFirebaseAuth; private FirebaseUser mFirebaseUser;
Sau đó, thêm đoạn code sau vào cuối onCreate().
// Initialize Firebase Auth mFirebaseAuth = FirebaseAuth.getInstance(); mFirebaseUser = mFirebaseAuth.getCurrentUser(); if (mFirebaseUser == null) { // Not logged in, launch the Log In activity loadLogInView(); }
ùng đăng nhập được, kết quả sẽ là một object FirebaseUser chứa các chi tiết về người dùng đó. Nếu người dùng không đăng nhập được, getCurrentUser() sẽ trả kết quả null, đồng thời loadLogInView() được call ra để điều hướng người dùng trở lại giao diện đăng nhập.
Chạy ứng dụng và bạn sẽ được điều hướng sang trang đăng nhập.
Thêm đoạn sau vào LogInActivity.
protected EditText emailEditText; protected EditText passwordEditText; protected Button logInButton; protected TextView signUpTextView; private FirebaseAuth mFirebaseAuth;
Thay đổi method onCreate() của class LogInActivity sang:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_log_in); // Initialize FirebaseAuth mFirebaseAuth = FirebaseAuth.getInstance(); signUpTextView = (TextView) findViewById(R.id.signUpText); emailEditText = (EditText) findViewById(R.id.emailField); passwordEditText = (EditText) findViewById(R.id.passwordField); logInButton = (Button) findViewById(R.id.loginButton); signUpTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(LogInActivity.this, SignUpActivity.class); startActivity(intent); } }); logInButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String email = emailEditText.getText().toString(); String password = passwordEditText.getText().toString(); email = email.trim(); password = password.trim(); if (email.isEmpty() || password.isEmpty()) { AlertDialog.Builder builder = new AlertDialog.Builder(LogInActivity.this); builder.setMessage(R.string.login_error_message) .setTitle(R.string.login_error_title) .setPositiveButton(android.R.string.ok, null); AlertDialog dialog = builder.create(); dialog.show(); } else { mFirebaseAuth.signInWithEmailAndPassword(email, password) .addOnCompleteListener(LogInActivity.this, new OnCompleteListener<authresult>() { @Override public void onComplete(@NonNull Task</authresult><authresult> task) { if (task.isSuccessful()) { Intent intent = new Intent(LogInActivity.this, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); } else { AlertDialog.Builder builder = new AlertDialog.Builder(LogInActivity.this); builder.setMessage(task.getException().getMessage()) .setTitle(R.string.login_error_title) .setPositiveButton(android.R.string.ok, null); AlertDialog dialog = builder.create(); dialog.show(); } } }); } } }); }
Thay đổi trên sẽ khởi động view elements (thành tố giao diện) và object FirebaseAuth, entry point của Firebase Authentication SDK. Một event listener sẽ thêm vào Sign Up text view, và mở Sign Up activity khi được nhấp vào. Một event listener nữa trên nút Login sẽ thực hiện phê duyệt input của người dùng, để đảm bảo người dùng đã nhập text cho cả hai trường. Event listener sau đó sẽ tiếp tục call Firebase server với signInWithEmailAndPassword(). Hàm này tiếp nhận email và password của người dùng và trả kết quả Task của AuthResult. Bạn sẽ kiểm tra xem liệu Task có thành công hay không và điều hướng người dùng về MainActivity, nếu không thành công thì error message sẽ hiển thị. Dưới đây là error message xuất hiện khi đăng nhập user chưa đăng ký.
Bạn hoàn toàn có thể thay đổi message do task.getException().getMessage() hiển thị. Bạn có thể sử dụng các kiểu exception sau nếu quá trình xác minh thất bại:
- FirebaseAuthInvalidUserException được thrown nếu tài khoản người dùng có email không tồn tại hoặc đã bị vô hiệu hóa.
- FirebaseAuthInvalidCredentialsException được thrown nếu sai password.
Bênh cạnh signInWithEmailAndPassword(), các bạn có thể đăng nhập người dùng bằng các cách thức sau:
- signInWithCredential(AuthCredential)– Đăng nhập người dùng với AuthCredential. Sử dụng method này để đăng nhập người dùng vào hệ thống Xác minh của Firebase. Trước hết, thu nhận credential (thông tin xác nhận) trực tiếp từ người dùng nếu dùng EmailAuthCredential, hoặc từ các SDK xác minh được hỗ trợ, như Google Sign-In hay Facebook.
- signInAnonymously() – Đăng nhập người dùng ẩn danh mà không yêu cầu bất cứ credential nào. Method này sẽ tạo tài khoản mới trong hệ thống Xác minh Firebase của bạn, trừ trường hợp đã có người dùng ẩn danh đăng nhập vào ứng dụng này.
- signInWithCustomToken(String) – Đăng nhập người dùng bằng Custom Token nhận được. Sử dụng method này sau khi bạn nhận Firebase Auth Custom Token từ server để đăng nhập người dùng vào hệ thống Xác minh của Firebase.
Khi đã thiết lập xong chức năng đăng nhập, ta sẽ tiếp tục tiếng tới Sign Up (đăng ký).
Điều chỉnh SignUpActivity như sau:
package com.echessa.todo; import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.EditText; import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.Task; import com.google.firebase.auth.AuthResult; import com.google.firebase.auth.FirebaseAuth; public class SignUpActivity extends AppCompatActivity { protected EditText passwordEditText; protected EditText emailEditText; protected Button signUpButton; private FirebaseAuth mFirebaseAuth; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sign_up); // Initialize FirebaseAuth mFirebaseAuth = FirebaseAuth.getInstance(); passwordEditText = (EditText)findViewById(R.id.passwordField); emailEditText = (EditText)findViewById(R.id.emailField); signUpButton = (Button)findViewById(R.id.signupButton); signUpButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String password = passwordEditText.getText().toString(); String email = emailEditText.getText().toString(); password = password.trim(); email = email.trim(); if (password.isEmpty() || email.isEmpty()) { AlertDialog.Builder builder = new AlertDialog.Builder(SignUpActivity.this); builder.setMessage(R.string.signup_error_message) .setTitle(R.string.signup_error_title) .setPositiveButton(android.R.string.ok, null); AlertDialog dialog = builder.create(); dialog.show(); } else { mFirebaseAuth.createUserWithEmailAndPassword(email, password) .addOnCompleteListener(SignUpActivity.this, new OnCompleteListener</authresult><authresult>() { @Override public void onComplete(@NonNull Task</authresult><authresult> task) { if (task.isSuccessful()) { Intent intent = new Intent(SignUpActivity.this, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); } else { AlertDialog.Builder builder = new AlertDialog.Builder(SignUpActivity.this); builder.setMessage(task.getException().getMessage()) .setTitle(R.string.login_error_title) .setPositiveButton(android.R.string.ok, null); AlertDialog dialog = builder.create(); dialog.show(); } } }); } } }); } }
Method createUserWithEmailAndPassword() sẽ tạo tài khoản người dùng mới với địa chỉ email và password được cung cấp. Nếu thành công, method này còn tự động đăng nhập người dùng mới vào ứng dụng. Task của AuthResult được trả lại với kết quả của thao tác. Bạn sẽ kiểm tra xem quá trình đăng ký, nếu thành công người dùng sẽ được điều hướng đến MainActivity, nếu không error message sẽ xuất hiện. Trong ứng dụng, bạn có thể xem trước exception được thrown để quyêt định error message hiển thị đến người dùng.
FirebaseAuthWeakPasswordException được thrown nếu password không đủ mạnh
FirebaseAuthInvalidCredentialsException được throw nếu địa chỉ email bị sai lệch
FirebaseAuthUserCollisionException được thrown nếu đã tồn tại tài khoản cho email được cung cấp
Chạy ứng dụng. Nếu bạn nhấn vào Sign Up text view trên Log In view, Sign Up view sẽ mở ra.
Đăng ký một tài khoản. Theo mặc định, firebase sẽ thực hiện một số bước xác minh lên dữ liệu. Ví dụ, password của bạn phải dài ít nhất 6 ký tự.
Nếu vẫn cứng đầu, bạn sẽ thấy lỗi sau.
Nếu đăng ký thành công, bạn sẽ được điều hướng về MainActivity. Nếu nhìn vào Firebase console, bạn có thể thấy được người đùng được tạo dưới Auth > Users.
Firebase team đã xây dựng một thư viện mang tên FirebaseUI giúp đơn giản quá trình thêm Xác minh và kết nối các nhân tố UI vào Firebase database.
Xác minh và xác nhận dữ liệu
Xác định người dùng chỉ là một phần trong bảo mật mà thôi. Khi bạn đã biết được người dùng là ai, bạn cần tìm cách để kiểm soát khả năng truy cập trong Firebase database.
Firebase có một loại ngôn ngữ khai báo cho một số quy luật cụ thể trú trong Firebase server và xác định bảo mật của ứng dụng. Bạn có thể edit các quy luật này trong tab Database > Rules.
Quy luật bảo mật cho phép bạn kiểm soát truy cập vào mỗi phần của database. Theo mặc định, Firebase có cung cấp một số luật quy luật yêu cầu user phải được xác minh trước đó.
{ "rules": { ".read": "auth != null", ".write": "auth != null" } }
Firebase Database Rules có cú pháp giống JavaScript và tuân theo 4 loại:
- .read – Miêu tả liệu người dùng có được đọc dữ liệu hay không và đọc khi nào
- .write – Miêu tả liệu có được việt dữ liệu hay không và viết khi nào
- .validate – Xác định hình thái của giá trị được định dạng đúng cách, kể cả có thuộc tính con, và kiểu dữ liệu.
- .indexOn – Chỉ định một thuộc tính con để index và để hỗ trợ sắp xếp và truy vấn
Các quy luật .read và .write xếp theo tầng, vì vậy ruleset sau sẽ cấp quyền đọc đến bất cứ dữ liệu này tại đường dẫn /foo/ (cũng có thể gọi là node foo) cũng như các đường dẫn sâu hơn như /foo/bar/baz. Nên lưu ý, các quy luật .read và .write nông hơn trong database sẽ override các quy luật sâu hơn, vậy nên trong ví dụ này, quyền đọc đến /foo/bar/baz vẫn sẽ được cấp ngay cả khi quy luật tại đường dẫn /foo/bar/baz được đánh giá là false.
{ "rules": { "foo": { ".read": true, ".write": false } } }
Các luật .validate không phân lớp.
Tinh chỉnh các quy luật như dưới đây và nhấn Publish.
{ "rules": { "users": { "$uid": { ".read": "auth != null && auth.uid == $uid", ".write": "auth != null && auth.uid == $uid", "items": { "$item_id": { "title": { ".validate": "newData.isString() && newData.val().length > 0" } } } } } } }
Ở các quy luật trên, auth != null && auth.uid == $uid giới hạn quyền đọc viết dữ liệu trên node user (cũng như các node con) đến người dùng có uid khớp với id của người dùng đã login (auth.uid). $uid là biến giữ giá trị tại node đó mà không phải chính tên node. Với quy luật này, người dùng sẽ không chỉ cần được xác minh để đọc và viết bất cứ dữ liệu nào lên node và node con, mà còn chỉ có tiếp cận và dữ liệu của chính mình.
Firebase Database Rules bao gồm các biến built-in và functions cho phép bạn chỉ đến các đường dẫn, server-side timestamps, thông tin xác minh khác,… Bạn có thể sử dụng những biến và hàm này để xây dựng các quy luật phức tạp. Những biến và hàm này mang lại sự mạnh mẽ và linh hoạt, cho phép bạn chỉ đến các đường dẫn, server-side timestamps khác,…
Trong apps rules, hãy sử dụng biến built-in auth. Biến này được populate sau khi người dùng xác minh. Nó có chứa các dữ liệu về người dùng, bao gồm auth.uid (identifier chữ số đơn nhất, hoạt động khắp các provider).
Các biến dùng được:
- now – Thời gian hiện tại tính từ thời đại Linux, theo mili giây; rất phù hợp để xác nhận timestamps được tạo với firebase.database.ServerValue.TIMESTAMP của SDK.
- root – RuleDataSnapshot thể hiện root path trong Firebase database dúng như trước khi thao tác.
- newData – RuleDataSnapshot thể hiện dữ liệu đúng như trước khi thao tác. Nó bao gồm dữ liệu mới viết và dữ liệu đã có.
- data – RuleDataSnapshot thể hiện dữ liệu đúng như trước khi thực hiện thao tác.
- $variables – đường dẫn wildcard dùng để thể hiện ids và dynamic child keys.
- auth – Thể hiện token payload của người dùng đã xác minh.
Firebase lưu trữ dữ liệu theo định dạng JSON. Trong database của ta, mõi người dùng sẽ có một dãy to-do items mang tên items. Mỗi item sẽ có một title. Ở trên, bạn đảm bảo dữ liệu được viết vào /users//items bằng cách thêm data validation là string có nhiều hơn 0 ký tự. Bởi vậy, một item có title trống sẽ không được lưu.
Hãy chắc chắn bạn đã nhấp vào nút Publish, nếu không các quy luật sẽ không được lưu. Bạn sẽ thấy một message “Rules published”.
Validation rules rất hay nhưng vẫn chưa thế thay thế data validation code trong ứng dụng. Bạn vẫn nên xác minh input trong ứng dụng để cải thiện hiệu năng.
Lưu nhận dữ liệu
Thêm các biến sau vào MainActivity:
private DatabaseReference mDatabase; private String mUserId;
Sau đó thay đổi onCreate() như sau:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); // Khới động Firebase Auth và Database Reference mFirebaseAuth = FirebaseAuth.getInstance(); mFirebaseUser = mFirebaseAuth.getCurrentUser(); mDatabase = FirebaseDatabase.getInstance().getReference(); if (mFirebaseUser == null) { // Chưa đăng nhập, khới động Log In activity loadLogInView(); } else { mUserId = mFirebaseUser.getUid(); // Set up ListView final ListView listView = (ListView) findViewById(R.id.listView); final ArrayAdapter<string> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, android.R.id.text1); listView.setAdapter(adapter); // Thêm items thông qua Button và EditText ở cuối view. final EditText text = (EditText) findViewById(R.id.todoText); final Button button = (Button) findViewById(R.id.addButton); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { mDatabase.child("users").child(mUserId).child("items").push().child("title").setValue(text.getText().toString()); text.setText(""); } }); // Dùng Firebase để populate list. mDatabase.child("users").child(mUserId).child("items").addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String s) { adapter.add((String) dataSnapshot.child("title").getValue()); } @Override public void onChildChanged(DataSnapshot dataSnapshot, String s) { } @Override public void onChildRemoved(DataSnapshot dataSnapshot) { adapter.remove((String) dataSnapshot.child("title").getValue()); } @Override public void onChildMoved(DataSnapshot dataSnapshot, String s) { } @Override public void onCancelled(DatabaseError databaseError) { } }); } }
Kế tiếp, hãy tạo một reference đến root node của database với FirebaseDatabase.getInstance().getReference(). Rồi set listener lên nút Add New Item để lưu dữ liệu đến Firebase khi được nhấp vào.
Có bốn method viết dữ liệu vào Firebase Realtime Database:
- setValue() – Viết hoặc thay thế đến một đường dẫn đã xác định, như users/<user -id>/<username>.
- push() – Thêm vào list dữ liệu. Mỗi khi bạn call push(), Firebase sẽ tạo một unique key, còn có thể sử dùng làm identifier đơn nhất, như user-posts/<user -id>/<unique -post-id>.
- updateChildren() – Cập nhật một vài key cho đường dẫn đã xác định mà không cần phải thay thế tất cả dữ liệu.
- runTransaction() – Cập nhật dữ liệu phức tạp có nguy cơ bị corrupt bởi concurrent updates (cập nhật trùng lấp).
Trong code, dữ liệu được lưu với :
Kế tiếp, hãy tạo một reference đến root node của database với FirebaseDatabase.getInstance().getReference(). Rồi set listener lên nút Add New Item để lưu dữ liệu đến Firebase khi được nhấp vào. Có bốn method viết dữ liệu vào Firebase Realtime Database: setValue() – Viết hoặc thay thế đến một đường dẫn đã xác định, như users/<user -id>/<username>. push() – Thêm vào list dữ liệu. Mỗi khi bạn call push(), Firebase sẽ tạo một unique key, còn có thể sử dùng làm identifier đơn nhất, như user-posts/<user -id>/<unique -post-id>. updateChildren() – Cập nhật một vài key cho đường dẫn đã xác định mà không cần phải thay thế tất cả dữ liệu. runTransaction() – Cập nhật dữ liệu phức tạp có nguy cơ bị corrupt bởi concurrent updates (cập nhật trùng lấp). Trong code, dữ liệu được lưu với :
.child sẽ nhận reference đến node xác định nếu đã có, hoặc tạo mới nếu không. Đoạn code trên sẽ lưu đoạn text đã nhập tại đường dẫn /users/<user id>/items/<item id>/title. .push() dùng key đơn nhất để tạo vị trí con mới. Bạn sẽ dùng .push() để tạo key đơn nhất cho mỗi item được thêm vào. .setValue() viết hoặc thay thế dữ liệu đến đường dẫn đã xác định.
Để nhận dữ liệu từ Firebase, hãy thêm một listener vào database reference với addChildEventListener(). Bạn có thể listen các kiểu event nhận dữ liệu sau:
- ValueEventListener: onDataChange() Đọc và listen các thay đổi đến toàn bộ nội dung của một đường dẫn.
- ChildEventListener: onChildAdded() –nhận một list item hoặc listen các phần thêm vào list items. Dùng với onChildAdded() and onChildRemoved() dể theo dõi các thay đổi lên list.
- ChildEventListener: onChildRemoved() – Listen các item được xóa khỏi một list. Dùng với onChildAdded() and onChildChanged() để theo dõi các thay đổi đến list.
- ChildEventListener: onChildMoved() – Listen các thay đổi đến thứ tự của item trong list đã sắp xếp. Các event onChildMoved() luôn đi theo event onChildChanged() khiến thứ tự của item thay đổi (dựa trên cách thức sắp xếp mà bạn đang sử dụng).
Ứng dụng mẫu của chúng ta chỉ cho phép item được loại bỏ hoặc thêm vào list, nên hãy dùng riêng onChildRemoved() và onChildAdded().
Listener nhận một DataSnapshot(snapshot của data). Snapshot là một image của data tại một vị trí cụ thể trong Firebase database tại một điểm thời gian. Call getValue() lên một snapshot sẽ trả kết quả là object Java đại diện cho dữ liệu. Các kiểu mà getValue() trả được gồm Boolean, String, Long, Double, Map<string , Object>, và List<object>. Nếu không tồn tại dữ liệu tại vị trí, sapshot sẽ trả kết quả null và bạn nên kiểm tra xem có null hay không trước khi sử dụng dữ liệu. Cuối cùng, hãy thêm phần nhận dữ liệu vào list view.
Nếu bạn chạy ứng dụng và thêm một số item, chúng sẽ được thêm vào list và vào database.
Bạn có thể thêm data từ Firebase console bằng cách nhấp vào green + control trên một node và nhập dữ liệu vào. Hãy đảm bảo bạn nhập dữ liệu theo đúng định dạng, nếu không ứng dụng Android sẽ crash khi cố đọc những dữ liệu này. Trong production app, bạn nên thêm validation để đảm bảo ứng dụng không crash khi nhận sai dữ liệu.
Để thêm một item từ Console, nhấp the + on node items và thêm đoạn sau vào trường name. Bạn phải nhập đường dẫn đầy dủ //title.
/123/title
Thêm “This was added using the console” vào trường value và nhấp Save.
Item sẽ được thêm vào dữ liệu và nếu bạn nhìn vào ứng dụng Android, danh sách sẽ tự cập nhật item vào đó.
Ứng dụng mẫu ta đang xây dựng sẽ gửi một String đến server. Cũng tốt, nhưng với ứng dụng phức tạp hơn, model objects của bạn sẽ phức tạp hơn.
Firebase cho phép bạn chuyển custom Java object sang DatabaseReference.setValue() đọc dữ liệu vào một object với DataSnapshot.getValue(), giả sử class xác định object này có constructor mặc định không nhận argument và public getter để properties được assign.
Để xét rõ hơn trong thực tế, tạo một class tên Item và chuyển thành:
package com.echessa.todo; /** * Created by echessa on 8/27/16. */ public class Item { private String title; public Item() {} public Item(String title) { this.title = title; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }
Thay đổi nút Add New Item trên click listener sang:
button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { Item item = new Item(text.getText().toString()); mDatabase.child("users").child(mUserId).child("items").push().setValue(item); text.setText(""); } });
Thay đổi này sử dụng object Item để lưu dữ liệu vào database. Nội dung của Item được map vào các vị trí con theo dạng lưới. Chạy ứng dụng và bạn sẽ vẫn có thể thêm dữ liệu vào list và xem dữ liệu đã lưu trên console server.
Xóa dữ liệu
Ứng dụng của bạn giờ đây có thể lưu trữ và truy xuất dữ liệu để populate một list view. Kế tiếp ứng dụng cần cho phép người dùng xóa item từ list và Firebase.
Thêm đoạn sau vào cuối block else tron onCreate() trong class MainActivity:
// Xóa item khi nhấp listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView< ?> parent, View view, int position, long id) { mDatabase.child("users").child(mUserId).child("items") .orderByChild("title") .equalTo((String) listView.getItemAtPosition(position)) .addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { if (dataSnapshot.hasChildren()) { DataSnapshot firstChild = dataSnapshot.getChildren().iterator().next(); firstChild.getRef().removeValue(); } } @Override public void onCancelled(DatabaseError databaseError) { } }); } });
Đoạn code sẽ set onclick listener lên list view và truy vấn database khi một item được nhấp vào. Đồng thời tìm kiếm item trong Firebase database với title giống với string tại vị trí được nhấp. Với ứng dụng phức tạp hơn, bạn nên tìm kiếm theo tiêu chuẩn đặc trưng hơn làn object, như id chẳng hạn. Nó sau đó sẽ loại bỏ lần xuất hiện đầu tiên của item từ database. Listview được cập nhật tự.
Chạy ứng dụng và bạn sẽ có thể xóa item từ Firebase bằng cách nhấp lên đó trên ứng dụng Android.
Logging Users Out
Việc call signout() sẽ làm mất hiệu lực của token của người dùng, và đăng thoát họ khỏi ứng dụng. Thay đổi các dòng sau trong onOptionsItemSelected(), làm đăng thoát người dùng và điều hướng về Login view.
if (id == R.id.action_logout) { mFirebaseAuth.signOut(); loadLogInView(); }