انشاء كلاس قاعدة البيانات الـ Room

Mohammad Laifمنذ 5 سنوات

بسم الله الرحمن الرحيم
السلام عليكم ورحمة الله وبركاته

في هذا الدرس سنتعلم كيفية انشاء كلاس AppDatabase وهي الكلاس المسؤله عن فتح قاعدة البيانات للـ Room. وسوف نقوم بتصميمها باستخدام نمط الـ Singleton حتى لايصبح تسرب لها عند استخدامها في اكثر من مكان بالمشروع. وايضاً سنقوم بعمل كلاس Converter لتحويل انواع البيانات الغير متعارفه مع الـ SQLite.

 

ماذا سوف تقرئ في هذا الدرس؟

  • ماهي الـ Room.
  • ماهي فوائد الـ Room.
  • ماهي متطلبات الـ Room.
  • ماذا سننشئ.

 

ماهي الـ Room؟

https://www.youtube.com/watch?v=SKWh4ckvFPM

الـ Room هي مكتبة من مكتبات الـ  Android Jetpack لإنشاء قواعد بيانات الـ SQLite على نحو افضل واسرع. تستطيع استخدامها لإنشاء قاعدة بيانات لتطبيقك, او لإستخدامها لانشاء cache لتطبيقك عندما يكون تعاملك مع بيانات اتيه من الشبكة العنكبوتية.

 

ماهي فوائد الـ Room؟

  • كتابة اسطر برمجية اقل.
  • سهولة اداء الاختبارات.

 

ماهي متطلبات الـ Room؟

الروم تتطلب كلاس Entity وواجهة Dao ولقد قمنا بعملهم في الدروس السابقة.

 

ماذا سننشئ؟

  • كلاس AppDatabase.
  • كلاس محول للبيانات DateConverter.

 

انشاء كلاس الـ AppDatabase

نقوم بانشاء كلاس جافا جديدة باسم AppDatabase ثم نقوم بتعليمها بالنوتيشن Database لتصبح متوافقه مع الـ Room هكذا:

@Database(entities = {SubjectEntity.class, CardEntity.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {}

تعقيب:

  • استخدمنا الخيار entities حتى نعلم الـ Room بالجداول المراد عملها في قاعدة البيانات. لاحظ ان كلاسين الـ Entitiy للعناصر Subject و Card سوف يتم تحويلهم الى جداول قاعدة بيانات, بدون الحاجة لكتابة تلك الاكواد الكثيره كما كنا سابقاً نكتبهم في كلاسات الـ DatabaseContract و الـ DatabaseHelper (وكذلك واجهات الـ Dao فقد ساعدتنا للتخلص من كتابة كلاس DatabaseProvider).
  • اما الخيار version فهو لتعليم رقم الاصداره لقاعدة البيانات (انتبه انك سوف تحتاج الى تغييره في كل مره تقوم بالتعديل على الجداول - اي التعديلات التي تحدثها على الـ SubjectDao و CardDao).
  • والخيار exportSchema حتى نعلم الروم باننا لانريد تصدير ملف الـ SQLite Schema تحاشياً لبعض المشاكل (في حالة اردت تصديره لقرائة الـ Schema يجب اتباع خطوات معينه).

 

نقوم بكتابة الحقول لها على الشكل التالي:

private static final String TAG = AppDatabase.class.getSimpleName();
private static final Object LOCK = new Object();
private static final String DATABASE_NAME = "flashcardsdb";
private static AppDatabase sInstance;

لاحظ الحقل DATABASE_NAME وهو المسؤل عن تسمية قاعدة بيانات تطبيقك في اجهزة المستخدمين. اما الحقول الاخرى LOCK و sInstance فهم متطلبات لكتابة الكلاس بنمط الـ Singleton. اما TAG لإستخدامه في حالة عمل Debugging فقط لاغير.

 

ثم نقوم بكتابة الـ Construction للكلاس:

public static AppDatabase getInstance(Context context) {
    if (sInstance == null) {
        synchronized (LOCK) {
                Log.d(TAG, "getInstance: Creating a new database instance");
            sInstance = Room.databaseBuilder(
                    context.getApplicationContext(),
                    AppDatabase.class,
                    AppDatabase.DATABASE_NAME
            ).build();
        }
    }
    Log.d(TAG, "getInstance: Getting the database instance, no need to recreated it.");
    return sInstance;
}

تعتقيب:

مايهم هنا طريقة انشاء قاعدة البيانات للـ Room وهي طريقة عمل init للحقل sInstance باستخدام builder الـ Room. تستطيع اضافة اي من الخيارات عند احتياجها في عملية البناء. اما باقي الاشياء فهي خاصة بنمط الـ Singleton.

 

ثم نقوم بعمل عناصر من واجهات الـ Dao لدينا على النحو التالي:

public abstract SubjectDao subjectDao();
public abstract CardDao cardDao();

 

لتصبح الكلاس بهذا الشكل:

package com.mzdhr.flashcards.database;

import android.arch.persistence.room.Database;
import android.arch.persistence.room.Room;
import android.arch.persistence.room.RoomDatabase;
import android.content.Context;
import android.util.Log;

import com.mzdhr.flashcards.database.dao.CardDao;
import com.mzdhr.flashcards.database.dao.SubjectDao;
import com.mzdhr.flashcards.database.entity.CardEntity;
import com.mzdhr.flashcards.database.entity.SubjectEntity;

@Database(entities = {SubjectEntity.class, CardEntity.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {

    private static final String TAG = AppDatabase.class.getSimpleName();
    private static final Object LOCK = new Object();
    private static final String DATABASE_NAME = "flashcardsdb";
    private static AppDatabase sInstance;

    public static AppDatabase getInstance(Context context) {
        if (sInstance == null) {
            synchronized (LOCK) {
                Log.d(TAG, "getInstance: Creating a new database instance");
                sInstance = Room.databaseBuilder(
                        context.getApplicationContext(),
                        AppDatabase.class,
                        AppDatabase.DATABASE_NAME
                ).build();
            }
        }
        Log.d(TAG, "getInstance: Getting the database instance, no need to recreated it.");
        return sInstance;
    }


    public abstract SubjectDao subjectDao();
    public abstract CardDao cardDao();
}

 

مشكلة الـ Room مع انواع البيانات الغير مدعومة في الـ SQLite

عند تشغيل هذه الكلاس المسؤوله عن قاعدة بيانات الـ Room ستقوم بتحويل جميع كلاسات الـ Entity الى جداول في قاعدة البيانات, وايضاً حقولهم بشكل الي, ولكنها ستواجه مشكله في تحويل حقل الـ Date (في كلاسات الـ SubjectDao و CardDao). لماذا؟

لان الـ Room لاتستطيع تحويل الا فقط انواع البيانات المتوافق عليها مع الـ SQlite. لذلك سوف تحتاج انت الى كتابة كلاسات خاصة لكل نوع لاتستطيع الروم بتحويله. ففي مثالنا سوف نقوم بكتابة كلاس تحتوي على دالتين الاول تقوم بتحويل نوع Date الى Long لتخزينه في قاعدة البيانات, وداله اخرى تعكس ذلك في حالة اردنا قرائته من قاعدة البيانات. وللإطلاع على الانواع اللتي لاتحتاج الى تحويل اطلع على Datatypes in SQLite في المصادر.

 

كلاس الـ DateConverter:

نقوم بانشاء كلاس جديد ولنسميها DateConverter ونقوم بكتابة دالتين بها واحده لتحويل عنصر الـ Date الى عنصر Long حتى تتكمن الـ Room بتخزينه. والثانيه داله تقوم بعكس ذلك بتحويل الـ Long الى عنصر Date في حالة اردنا قرائة التواريخ وعرضها في مشروع تطبيقنا (للمزيد من الامثله اطلع على المقاله Room and Custom Types في المصادر).

package com.mzdhr.flashcards.database.converter;

import android.arch.persistence.room.TypeConverter;

import java.util.Date;

public class DateConverter {
    @TypeConverter
    public static Date toDate(Long timestamp) {
        if (timestamp != null) {
            return new Date(timestamp);
        } else {
            return null;
        }
    }

    @TypeConverter
    public static Long toTimestamp(Date date) {
        if (date != null) {
            return date.getTime();
        } else {
            return null;
        }
    }

}

لاحظ اننا استخدمنا النوتيشن TypeConverter ومن خلاله سوف تفهم الـ Room بأن هذه الدوال ستساعدها في تحويل العناصر التي لاتستطيع هي بنفسها تحويلها.

 

ربط الـ AppDatabase و DataConverter معا

الان نحتاج فقط لوضع نوتيشن جديد في كلاس قاعدة بيانات الـ Room على النحو التالي:

@TypeConverters(DateConverter.class)

 

لتصبح كلاس الـ AppDatabase على اكمل وجهه هكذا:

package com.mzdhr.flashcards.database;

import android.arch.persistence.room.Database;
import android.arch.persistence.room.Room;
import android.arch.persistence.room.RoomDatabase;
import android.arch.persistence.room.TypeConverters;
import android.content.Context;
import android.util.Log;

import com.mzdhr.flashcards.database.converter.DateConverter;
import com.mzdhr.flashcards.database.dao.CardDao;
import com.mzdhr.flashcards.database.dao.SubjectDao;
import com.mzdhr.flashcards.database.entity.CardEntity;
import com.mzdhr.flashcards.database.entity.SubjectEntity;

@Database(entities = {SubjectEntity.class, CardEntity.class}, version = 1, exportSchema = false)
@TypeConverters(DateConverter.class)
public abstract class AppDatabase extends RoomDatabase {

    private static final String TAG = AppDatabase.class.getSimpleName();
    private static final Object LOCK = new Object();
    private static final String DATABASE_NAME = "flashcardsdb";
    private static AppDatabase sInstance;

    public static AppDatabase getInstance(Context context) {
        if (sInstance == null) {
            synchronized (LOCK) {
                Log.d(TAG, "getInstance: Creating a new database instance");
                sInstance = Room.databaseBuilder(
                        context.getApplicationContext(),
                        AppDatabase.class,
                        AppDatabase.DATABASE_NAME
                ).build();
            }
        }
        Log.d(TAG, "getInstance: Getting the database instance, no need to recreated it.");
        return sInstance;
    }

    public abstract SubjectDao subjectDao();
    public abstract CardDao cardDao();
}

وهكذا قمنا بأنشاء قاعدة بيانات الـ Room. والى هنا نتوقف ونقوم بكتابة الاختبارات حتى نتأكد من ان قاعدة البيانات لدينا بالشكل الذي نريده وبدون bugs.

 

نهاية الدرس

فضلاً اذا اعجبك الدرس لاتنسى الضغظ على زر اعجبني ولنشر الفائدة قم بمشاركته مع من تحب. ولاتنسى تتبع الدرس حتى تطلع على التغييرات والتحديثات المتعلقه به مستقبلاً. وكذلك الامر بالنسبة للدورة من تتبع و اعجاب ومشاركة حتى يصلك جديد الدروس المتعلقه بها.

 

المصادر:

التعليقات (1)

AbdelRhman Hamdy:

جميل جدا تسلم ع المجهود الكبير وربنا يزيدك علم ويقدرك تواصل الشرح والافادة لنا 

لايوجد لديك حساب في عالم البرمجة؟

تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !