تیک۴

نحوه کار کردن با پایگاه داده (Firebase Database)

پایگاه داده زمان واقعی فایربیس یک پایگاه داده میزبانی شده به صورت ابری است که از چند پلتفرم اندروید، ios ، و وب پشتیبانی می کند. تمام داده ها در فایل json ذخیره می شوند و تمام تغییرات در داده‌ها فورًا در تمام پلتفرمها و دستگاها همزمان سازی می‌شوند. این مساله به ما اجازه می‌دهد که برنامه های زمان واقعی منعطف تر با تلاش کمتر بسازیم.

در مقاله نحوه کار کردن با فایربیس و پایگاه داده (Firebase Database)؛ ما یکپارچه سازی پایگاه داده لحظه ای فایربیس را به طور پایه و کامل پوشش خواهیم داد. همچنین مفاهیم دیگری مثل عملیات crud، اعتبارسنجی داده، قوانین دسترسی فایربیس آموزش خواهیم داد. اگر شما در زمینه‌ی فایربیس تازه کار هستید، پیشنهاد می کنم که مقالات دیگر تیک۴ در زمینه‌ی احراز هویت در فایربیس و فایربیس آنالیتیکس را مشاهده کنید.

کار کردن با فایربیس

چگونه داده ذخیره می‌شود ساختار json

پایگاه داده زمان واقعی فایربیس یک پایگاه داده ی بدون طراحی و ساختار است که داده را به صورت json ذخیره می کند. اساساً همه ی پایگاه داده یک درخت json با تعداد زیادی گره است. بنابراین وقتی که شما پایگاه داده‌ی خود را طراحی می کنید، شما باید یک ساختار json به طوری که داده به شکلی راحتتری در دسترس باشد با پرهیز از گره های فرزند تو در تو.

این جا مثالی از لیست تعدادی حساب کاربریی و پستهای مربوطه به صورت مرتب است. شما می توانید با پیروی از راهنمای طراحی ساختار پایگاه داده ی فایربیس بهترین روشهای تعریف ساختار پایگاه داده را بفهمید .

{
  "users": [
    {
      "name": "tik4",
      "email": "user@tik4.com",
      "address": "XXX, XXXX, 1234"
    }
  ],
  "posts": [
    {
      "id": 100,
      "author": "tik4",
      "content": "This is awesome firebase realtime database...",
      "timestamp": "13892733894"
    }
  ]
}
  1. داده های آفلاین

فایربیس پشتیبانی خوبی از داده های آفلاین دارد. آن به طور خودکار داده های آفلاین را ذخیره می کند وقتی که شما اتصال اینترنت ندارید. وقتی که دستگاه به اینترنت متصل میشود، همه ی داده ها به پایگاه داده ی لحظه ای وارد میشوند. اگر چه فعال سازی disk persistence داده های آفلاین را ذخیره می کند  حتی اگر دستگاه دچار شروع مجدد (ریستارت)بشود. disk persistence با کد یک خطی زیر قابل فعالسازی است. در اینجا راهنمای کاملی درباره‌ی قابلیت های آفلاین فایربیس وجود دارد. با ادامه مقاله کار کردن با فایربیس و پایگاه داده (Firebase Database) همراه ما باشید.

FirebaseDatabase.getInstance().setPersistenceEnabled(true);
  1. انجام عملیات crud

پیش از اینکه برنامه‌ی اندروید را درست کنیم دوست دارم که اطلاعات پایه درباره‌ی عملیات crud روی پایگاه داده‌ی زمان واقعی به شما بدهم. بعد ما تمام مفاهیم را برای ساخت یک برنامه فایربیس با پایگاه داده ی زمان واقعی به عنوان بک اند با هم ترکیب خواهیم کرد. برای انجام هر نوع عملیاتی برروی پایگاه داده چه خواندن و چه نوشتن نیاز دارید که اول بک ارجاع به پایگاه داده داشته باشید. کد زیر به شما یک ارجاع به بالاترین گره نود json می دهد. از اینجا شما می توانید از نام گره فرزند برای پیمایش بیشتر استفاده کنید.

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

3.1 درج داده

برای درج داده می توانید از متد setValue() بر روی مسیر ارجاء پایگاه داده استفاده کنید. این مقدار مورد نظر را در مسیر ساخته شده ایجاد یا بروزرسانی می‌کند. برای مثال کد زیر یک گره با نام «copyright» در بالاترین سطح json ایجاد خواهد کرد.

DatabaseReference mRef = mDatabase.getReference("copyright");
 
mRef.setValue("©2016 tik4. All rights Reserved");

پایگاه داده زمان واقعی چند نوع داده String، Long، Double ، Boolean، Map<String,Object>،List<Object>  برای ذخیره ی  داده می پذیرد. به علاوه می توانید از اشیای اختصاصی جاوا که هنگام ذخیره ی کلاس مدل به طور مستقیم درپایگاه داده بسیار مفید هستند برای ذخیره ی داده استفاده کنید.

تصور کنید که می خواهید پروفایل کاربر را در پایگاه داده ذخیره کنید. ابتدا شما نیاز به ایجاد مدل کاربر با یک سازنده ی خالی (constructor) و خصوصیات(properties)  های دیگر دارید.

@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;
    }
}

از آنجا که هر کاربری نیاز به یک شناسه‌ی اختصاصی دارد شما می توانید متد push() که یک گره خالی ایجاد می کند را فراخوانی کنید. سپس به گره «users»  با استفاده از متد child() ارجاع دهید. نهایتاً از متد setValue() برای ذخیره ی داده های کاربر استفاده کنید.

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("tik4", "user@tik4.com");
 
// pushing user to 'users' node using the userId
mDatabase.child(userId).setValue(user);

با اجرای کد بالا یک گره کاربر جدید با یک شناسه ی منحصر به فرد در پایگاه داده درج می‌شود. به طور کلی با با پیاده‌سازی Firebase Auth  باید شناسه‌ی کاربر در برنامه‌ی شما ایجاد شود که authId را به شما می‌دهد که به عنوان user id عمل می‌کند.

{
  "users": [
    "-KTYWvZG4Qn9ZYTc47O6" : {
      "email" : "user@tik4.com",
      "name" : "tik4"
    },
    {
      ...
    }
  ]
}

3.2 خواندن داده

برای خواندن داده باید ValueEventListener()  را به ارجاع پایگاه داده وصل کنید.  این رویداد وقتی که تغییری در داده ها اتفاق بیفتد به طور لحظه‌ای(realtime) فعال سازی (trigger) می‌شود. در onDataChange() می توانید کاری که می خواهید برروی داده های جدید انجام دهید.

در زیر یک شنونده ی (listener) رویداد را می بینید که هر گاه تغییری در داده‌های حساب کاربری اتفاق بیفتد فعال سازی (trigger) می‌شود که قبلاً ایجاد کرده ایم.

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 بروزرسانی داده

برای بروزرسانی داده، می‌توانید از همان متد setValue() با ارسال مقدار جدید استفاده کنید. همچنین می‌توانید از متد updateChildren()  با ارسال مسیر برای بروزرسانی داده بدون به هم ریختن داده‌های گره‌های فرزند دیگر استفاده کنید.

برای مثال اگر می‌خواهید فقط ایمیل کاربر را بروزرسانی کنید می‌توانید از کد زیر استفاده کنید.

String newEmail = 'tik4@gmail.com';
 
mDatabase.child(userId).child("email").setValue(newEmail);

3.4حذف داده

برای حذف داده به سادگی متد removeValue() را برروی ارجاع پایگاه داده فراخوانی کنید. همچنین شما می‌توانید مقدار null  به متد setValue() ارسال کنید که همان کار را انجام می‌دهد.

  1. امنیت و قوانیت

قوانین فایربیس یک راه برای تشخیص نقش کاربر در هنگام انجام عملیات خواندن و نوشتن فراهم می‌کند. این قوانین به عنوان یک لایه‌ی امنیتی عمل قبل از انجام هرنوع عملیات crud برروی سرور عمل می‌کنند. به طور پیش‌فرض این قوانین فقط بعد از احراز هویت امکان عمل خواندن و نوشتن به کار می‌دهند.

قوانین زیر به کاربران احراز هویت شده فقط اجازه‌ی خواندن یا نوشتن داده می‌دهند. با ادامه مقاله نحوه کار کردن با فایربیس و پایگاه داده (Firebase Database) همراه ما باشید.

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

قوانین زیر به همه بدون احراز هویت اجازه‌ی خواندن و نوشتن داده می‌دهند

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

همچنین می‌توانید از این قوانین برای اعتبارسنجی داده‌ها پیش از ارسال آنها به پایگاه داده استفاده کنید. برای مثال قوانین زیر کمتر بودن تعداد کاراکترهای اسم از 50 و معتبر بودن شکل ایمیل را با استفاده از عبارات با قاعده بررسی می‌کنند

{
    "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)"
                }
            }
        }
    }
}

برای یادگیری بیشتر مفاهیم امنیت با راهنمای قوانین و امنیت فایربیس پیش بروید.

حالا دانش کافی برای شروع یک پروژه‌ی اندروید داریم. بیاید یک پروژه ایجاد کنیم و ببینیم چطور می‌توانیم یک پایگاه داده‌ی لحظه‌ای را با یک برنامه‌ی نمونه یکپارچه کنیم.

  1. ایجاد یک پروژه‌ی اندروید
  2. اولین چیزی که باید انجام دهیم این است که به آدرس https://firebase.google.com/ برویم و برای دسترسی به کنسول یک حساب کاربری ایجاد کنیم. بعد از ایجاد حساب کاربری می‌توانین با ایجاد یک پروژه شروع کنیم.
  3. به بسته‌ی پروژه‌ای که می‌خواهید با فایربیس یکپارچه کنید یک نام اختصاص دهید(در مورد من این نام tik4.firebase است). اینجا وقتی که بر روی دکمه‌ی add کلیک می‌کنید فایل google-services.json دانلود خواهد شد.
  1. با File New Project یک پروژه در اندروید استودیو ایجاد کنید. در هنگام پرکردن جزئیات پروژه، مشخصاتی که در به پکیج درکنسول فایربیس وارد کردید را استفاده کنید. من از همان tik4.firebase استفاه می کنم.
  2. فایل google-services.json را داخل پوشه‌ی app پروژه وارد کنید. این گام مهم است چون بدون این فایل پروژه‌ی شما ایجاد نخواهد شد.
  3. حالا فایل build.gradle که در پوشه‌ی home پروژه‌ی شما قرار دارد باز کنید و وابستگی‌های گوگل پلی استور را به آن اضافه کنید.
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
    }

 

  1. فایل app/build.gradle را باز کنید و وابستگی‌های پایگاه داده‌ی فایربیس را به آن اضافه کنید. در انتهای فایل 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'
  1. برای ذخیره‌ی پروفایل کاربر به یک کلاس مدل به نام User.java احتیاج داریم. یک کلاس با نام User.java ایجاد می‌کنیم و خصوصیات کلاس زیر را به آن اضافه می‌کنیم. اگر می‌خواهید خصوصیات دیگری مثل آدرس، موبایل و … هم داشته باشید می‌توانید به این خصوصیات اضافه کنید.
User.java
package tik4.firebase;
 
import com.google.firebase.database.IgnoreExtraProperties;
 
/**
 * Created by tik4 on 07/10/16.
 * www.tik4.com
 */
 
@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;
    }
}

 

  1. فایل لایوت اکتیویتی (layout activity) اصلی activity_main.xml را باز کنید و لایه‌ی زیر را به آن اضافه کنید. این لایه یک فرم ساده ایجاد می‌کند که می‌توانید داده‌های پروفایل برای ذخیره در پایگاه داده را در آن وارد کنید. با ادامه مقاله نحوه کار کردن با فایربیس با ما همراه باشید.
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_width="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="tik4.firebase.MainActivity">
 
    <TextView
        android:id="@+id/txt_user"
        android:layout_width="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_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
 
        <android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
 
            <EditText
                android:id="@+id/name"
                android:layout_width="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_width="match_parent"
            android:layout_height="wrap_content">
 
            <EditText
                android:id="@+id/email"
                android:layout_width="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_width="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>
  1. فایل MainActivity.java را باز کنید و تغییرات ضروری زیر را برروی آن اعمال کنید. کد بسیار ساده و قابل فهم است

هدف ما ایجاد ساختار json به صورت زیر است به طوری که app_title عنوان برنامه را ذخیره کند. «users» حساب کاربری را به عنوان آرایه‌ای از گره‌ها ذخیره می‌کند.

{
  "app_title" : "Realtime Database",
  "users" : {
    "-KTYWvZG4Qn9ZYTc47O6" : {
      "email" : "user@tik4.com",
      "name" : "tik4"
    }
  }
}

> getReference(“app_title”) یک گره با نام app_title  ایجاد می‌کند که اطلاعات toolbar  را نگه می‌دارد

> getReference(“users”)  یک ارجاع به گره users  را نگه می دارد

> createUser()  این متد، کاربر جدید را در پایگاه داده‌ی لحظه‌ای ذخیره می‌کند

> updateUser() این متد، اطلاعات کاربر مثل نام و ایمیل را بروزرسانی ‌می‌کند

MainActivity.java
package tik4.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);
 
                // Check for null
                if (user == null) {
                    Log.e(TAG, "User data is null!");
                    return;
                }
 
                Log.e(TAG, "User data is changed!" + user.name + ", " + user.email);
 
                // Display newly updated name and email
                txtDetails.setText(user.name + ", " + user.email);
 
                // clear edit text
                inputEmail.setText("");
                inputName.setText("");
 
                toggleButton();
            }
 
            @Override
            public void onCancelled(DatabaseError error) {
                // Failed to read value
                Log.e(TAG, "Failed to read user", error.toException());
            }
        });
    }
 
    private void updateUser(String name, String email) {
        // updating the user via child nodes
        if (!TextUtils.isEmpty(name))
            mFirebaseDatabase.child(userId).child("name").setValue(name);
 
        if (!TextUtils.isEmpty(email))
            mFirebaseDatabase.child(userId).child("email").setValue(email);
    }
}

برنامه را یکبار اجرا و تست کنید. شما باید تغییرات را به طور لحظه‌ای در کنسول فایربیس ببینید.

  1. هزینه‌ها

برخلاف آنالیتیکس، پیامرسانی ابری، گزارشات مربوط به crash و خدمات دیگر، پایگاه داده‌ی زمان واقعی فایربیس به طور کامل رایگان نیست. محدودیتهای خاصّی در پلن رایگان وجود دارد. شما باید  هزینه چند دلاری برای استفاده از چند اتصال( مالتیپل کانکشن) ، فضای دیسک و استفاده از شبکه پرداخت کنید. برای اطلاعات بیشتر پلن هزینه‌های فایربیس را بررسی کنید.

امیدواریم از مقاله کار کردن با فایربیس و پایگاه داده (Firebase Database) بهره کافی را برده باشید. از قسمت دیدگاه ها باما در ارتباط باشید.

خروج از نسخه موبایل