একটি ভিউ সহ অ্যান্ড্রয়েড রুম - কোটলিন

আর্কিটেকচার কম্পোনেন্টের উদ্দেশ্য হল অ্যাপ আর্কিটেকচারের নির্দেশিকা প্রদান করা, লাইব্রেরি সহ জীবনচক্র ব্যবস্থাপনা এবং ডেটা স্থিরতার মতো সাধারণ কাজগুলির জন্য। আর্কিটেকচার উপাদানগুলি আপনাকে আপনার অ্যাপকে এমনভাবে গঠন করতে সাহায্য করে যা শক্তিশালী, পরীক্ষাযোগ্য এবং কম বয়লারপ্লেট কোড সহ রক্ষণাবেক্ষণযোগ্য। আর্কিটেকচার কম্পোনেন্ট লাইব্রেরিগুলি Android Jetpack- এর অংশ।

এটি কোডল্যাবের কোটলিন সংস্করণ। জাভা প্রোগ্রামিং ভাষার সংস্করণটি এখানে পাওয়া যাবে।

আপনি যদি এই কোডল্যাবের মাধ্যমে কাজ করার সময় কোনো সমস্যায় পড়েন (কোড বাগ, ব্যাকরণগত ত্রুটি, অস্পষ্ট শব্দ, ইত্যাদি), তাহলে অনুগ্রহ করে কোডল্যাবের নীচের বাম কোণে একটি ভুল প্রতিবেদন করুন লিঙ্কের মাধ্যমে সমস্যাটি রিপোর্ট করুন৷

পূর্বশর্ত

আপনাকে কোটলিন, অবজেক্ট-ওরিয়েন্টেড ডিজাইন ধারণা এবং অ্যান্ড্রয়েড ডেভেলপমেন্টের মৌলিক বিষয়গুলির সাথে পরিচিত হতে হবে, বিশেষ করে:

এটি সফ্টওয়্যার আর্কিটেকচারাল প্যাটার্নগুলির সাথে পরিচিত হতেও সাহায্য করে যা ব্যবহারকারীর ইন্টারফেস থেকে ডেটা আলাদা করে, যেমন MVP বা MVC। এই কোডল্যাব অ্যাপ আর্কিটেকচারের গাইডে সংজ্ঞায়িত আর্কিটেকচার প্রয়োগ করে।

এই কোডল্যাবটি অ্যান্ড্রয়েড আর্কিটেকচার উপাদানগুলির উপর দৃষ্টি নিবদ্ধ করে৷ অফ-টপিক কনসেপ্ট এবং কোড প্রদান করা হয়েছে আপনাকে সহজভাবে কপি এবং পেস্ট করার জন্য।

আপনি যদি কোটলিনের সাথে পরিচিত না হন তবে এই কোডল্যাবের একটি সংস্করণ এখানে জাভা প্রোগ্রামিং ভাষায় প্রদান করা হয়েছে।

আপনি কি করবেন

এই কোডল্যাবে, আপনি শিখবেন কিভাবে আর্কিটেকচার কম্পোনেন্ট রুম, ভিউমডেল এবং লাইভডেটা ব্যবহার করে একটি অ্যাপ ডিজাইন ও নির্মাণ করতে হয় এবং নিম্নলিখিতগুলি করে এমন একটি অ্যাপ তৈরি করতে হয়:

  • Android আর্কিটেকচার উপাদান ব্যবহার করে আমাদের প্রস্তাবিত আর্কিটেকচার প্রয়োগ করে।
  • ডেটা পেতে এবং সংরক্ষণ করতে একটি ডাটাবেসের সাথে কাজ করে এবং কিছু শব্দ দিয়ে ডাটাবেসকে প্রাক-পপুলেট করে।
  • MainActivity একটি RecyclerView এ সমস্ত শব্দ প্রদর্শন করে।
  • ব্যবহারকারী যখন + বোতামে ট্যাপ করে তখন একটি দ্বিতীয় কার্যকলাপ খোলে। যখন ব্যবহারকারী একটি শব্দ প্রবেশ করে, শব্দটি ডাটাবেস এবং তালিকায় যোগ করে।

অ্যাপটি নো-ফ্রিলস, কিন্তু যথেষ্ট জটিল যে আপনি এটিকে টেমপ্লেট হিসেবে ব্যবহার করতে পারেন। এখানে একটি পূর্বরূপ:

আপনি কি প্রয়োজন হবে

  • অ্যান্ড্রয়েড স্টুডিও 3.0 বা তার পরে এবং এটি কীভাবে ব্যবহার করতে হয় সে সম্পর্কে জ্ঞান। নিশ্চিত করুন যে Android স্টুডিও আপডেট হয়েছে, সেইসাথে আপনার SDK এবং Gradle।
  • একটি অ্যান্ড্রয়েড ডিভাইস বা এমুলেটর।

এই কোডল্যাবটি সম্পূর্ণ অ্যাপ তৈরি করতে আপনার প্রয়োজনীয় সমস্ত কোড সরবরাহ করে।

আর্কিটেকচার কম্পোনেন্ট ব্যবহার করতে এবং প্রস্তাবিত আর্কিটেকচার বাস্তবায়নের জন্য অনেকগুলো ধাপ রয়েছে। সবচেয়ে গুরুত্বপূর্ণ বিষয় হল কী ঘটছে তার একটি মানসিক মডেল তৈরি করা, টুকরাগুলি কীভাবে একত্রে ফিট করে এবং কীভাবে ডেটা প্রবাহিত হয় তা বোঝা। আপনি এই কোডল্যাবের মাধ্যমে কাজ করার সময়, কোডটি কপি এবং পেস্ট করবেন না, তবে সেই অভ্যন্তরীণ বোঝাপড়া তৈরি করার চেষ্টা করুন।

পরিভাষাটি পরিচয় করিয়ে দেওয়ার জন্য, এখানে স্থাপত্যের উপাদানগুলির একটি সংক্ষিপ্ত ভূমিকা এবং কীভাবে তারা একসাথে কাজ করে। মনে রাখবেন যে এই কোডল্যাবটি উপাদানগুলির একটি উপসেটের উপর ফোকাস করে, যথা LiveData, ViewModel এবং Room। আপনি এটি ব্যবহার করার সাথে সাথে প্রতিটি উপাদান আরও ব্যাখ্যা করা হয়েছে।

এই চিত্রটি স্থাপত্যের একটি মৌলিক রূপ দেখায়:

সত্তা : টীকাযুক্ত শ্রেণী যা রুমের সাথে কাজ করার সময় একটি ডাটাবেস টেবিল বর্ণনা করে।

SQLite ডাটাবেস: ডিভাইস স্টোরেজে। রুম পারসিসটেন্স লাইব্রেরি আপনার জন্য এই ডাটাবেস তৈরি এবং রক্ষণাবেক্ষণ করে।

DAO : ডেটা অ্যাক্সেস অবজেক্ট। ফাংশনগুলিতে এসকিউএল কোয়েরির ম্যাপিং। আপনি যখন একটি DAO ব্যবহার করেন, আপনি পদ্ধতিগুলিকে কল করেন এবং রুম বাকিগুলির যত্ন নেয়।

রুম ডাটাবেস : ডাটাবেসের কাজকে সহজ করে এবং অন্তর্নিহিত SQLite ডাটাবেসের অ্যাক্সেস পয়েন্ট হিসেবে কাজ করে ( SQLiteOpenHelper) । রুম ডাটাবেস SQLite ডাটাবেসে প্রশ্ন জারি করতে DAO ব্যবহার করে।

সংগ্রহস্থল: আপনি তৈরি করেন এমন একটি ক্লাস যা প্রাথমিকভাবে একাধিক ডেটা উত্স পরিচালনা করতে ব্যবহৃত হয়।

ViewModel : রিপোজিটরি (ডেটা) এবং UI-এর মধ্যে যোগাযোগ কেন্দ্র হিসেবে কাজ করে। UI এর আর ডেটার উৎপত্তি নিয়ে চিন্তা করার দরকার নেই। ভিউমডেল দৃষ্টান্তগুলি বেঁচে থাকে কার্যকলাপ/খণ্ডের বিনোদন।

লাইভডেটা : একটি ডেটা হোল্ডার ক্লাস যা পর্যবেক্ষণ করা যায়। সর্বদা ডেটার সর্বশেষ সংস্করণটি ধরে রাখে/ক্যাশ করে, এবং ডেটা পরিবর্তিত হলে তার পর্যবেক্ষকদের অবহিত করে। LiveData হল জীবনচক্র সচেতন। UI উপাদানগুলি কেবল প্রাসঙ্গিক ডেটা পর্যবেক্ষণ করে এবং পর্যবেক্ষণ বন্ধ বা পুনরায় শুরু করে না। লাইভডেটা স্বয়ংক্রিয়ভাবে এই সমস্তগুলি পরিচালনা করে কারণ এটি পর্যবেক্ষণ করার সময় প্রাসঙ্গিক জীবনচক্রের অবস্থার পরিবর্তন সম্পর্কে সচেতন।

রুম ওয়ার্ডস্যাম্পল আর্কিটেকচার ওভারভিউ

নিম্নলিখিত চিত্রটি অ্যাপটির সমস্ত অংশ দেখায়। প্রতিটি আবদ্ধ বাক্স (SQLite ডাটাবেস ব্যতীত) একটি ক্লাস উপস্থাপন করে যা আপনি তৈরি করবেন।

  1. অ্যান্ড্রয়েড স্টুডিও খুলুন এবং একটি নতুন অ্যান্ড্রয়েড স্টুডিও প্রকল্প শুরু করুন ক্লিক করুন।
  2. নতুন প্রকল্প তৈরি করুন উইন্ডোতে, খালি কার্যকলাপ নির্বাচন করুন এবং পরবর্তী ক্লিক করুন।
  3. পরবর্তী স্ক্রিনে, অ্যাপটির নাম দিন RoomWordSample, এবং Finish এ ক্লিক করুন।

এর পরে, আপনাকে আপনার গ্রেডল ফাইলগুলিতে উপাদান লাইব্রেরিগুলি যুক্ত করতে হবে।

  1. অ্যান্ড্রয়েড স্টুডিওতে, প্রকল্প ট্যাবে ক্লিক করুন এবং গ্রেডল স্ক্রিপ্ট ফোল্ডারটি প্রসারিত করুন।

build.gradle খুলুন ( মডিউল: অ্যাপ )।

  1. আপনার build.gradle ( মডিউল: অ্যাপ ) ফাইলের উপরে সংজ্ঞায়িত অন্যান্য প্লাগইনগুলির পরে এটি যোগ করে kapt টীকা প্রসেসর কোটলিন প্লাগইনটি প্রয়োগ করুন৷
apply plugin: 'kotlin-kapt'
  1. প্যাকেজ থেকে পারমাণবিক ফাংশন মডিউল বাদ দিতে এবং সতর্কতা প্রতিরোধ করতে android ব্লকের ভিতরে packagingOptions ব্লক যোগ করুন।
android {
    // other configuration (buildTypes, defaultConfig, etc.)

    packagingOptions {
        exclude 'META-INF/atomicfu.kotlin_module'
    }
}
  1. dependencies ব্লকের শেষে নিম্নলিখিত কোড যোগ করুন।
// Room components
implementation "androidx.room:room-runtime:$rootProject.roomVersion"
kapt "androidx.room:room-compiler:$rootProject.roomVersion"
androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"

// Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.archLifecycleVersion"
kapt "androidx.lifecycle:lifecycle-compiler:$rootProject.archLifecycleVersion"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.archLifecycleVersion"

// Kotlin components
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$rootProject.coroutines"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$rootProject.coroutines"

// Material design
implementation "com.google.android.material:material:$rootProject.materialVersion"

// Testing
testImplementation 'junit:junit:4.12'
androidTestImplementation "androidx.arch.core:core-testing:$rootProject.coreTestingVersion"
  1. আপনার build.gradle ( Project: RoomWordsSample ) ফাইলে, ফাইলের শেষে সংস্করণ নম্বর যোগ করুন, নিচের কোডে দেওয়া হয়েছে।
ext {
    roomVersion = '2.2.5'
    archLifecycleVersion = '2.2.0'
    coreTestingVersion = '2.1.0'
    materialVersion = '1.1.0'
    coroutines = '1.3.4'
}

এই অ্যাপের ডেটা হল শব্দ, এবং সেই মানগুলি ধরে রাখতে আপনার একটি সাধারণ টেবিলের প্রয়োজন হবে:

রুম আপনাকে একটি সত্তার মাধ্যমে টেবিল তৈরি করতে দেয়। এখন এটা করা যাক.

  1. Word ডেটা ক্লাস ধারণকারী একটি নতুন Kotlin ক্লাস ফাইল তৈরি করুন Word
    এই শ্রেণীটি আপনার শব্দের জন্য সত্তা (যা SQLite টেবিলের প্রতিনিধিত্ব করে) বর্ণনা করবে। ক্লাসের প্রতিটি সম্পত্তি টেবিলের একটি কলাম প্রতিনিধিত্ব করে। রুম শেষ পর্যন্ত এই বৈশিষ্ট্যগুলি ব্যবহার করবে টেবিল তৈরি করতে এবং ডাটাবেসের সারি থেকে বস্তুগুলিকে তাৎক্ষণিকভাবে তৈরি করতে।

এখানে কোড আছে:

data class Word(val word: String)

একটি রুম ডাটাবেসের জন্য Word শ্রেণীকে অর্থবহ করতে, আপনাকে এটি টীকা করতে হবে। টীকাগুলি সনাক্ত করে কিভাবে এই শ্রেণীর প্রতিটি অংশ ডাটাবেসের একটি এন্ট্রির সাথে সম্পর্কিত। রুম কোড তৈরি করতে এই তথ্য ব্যবহার করে।

আপনি যদি নিজেই টীকা টাইপ করেন (পেস্ট করার পরিবর্তে), Android স্টুডিও টীকা ক্লাসগুলি স্বয়ংক্রিয়ভাবে আমদানি করবে।

  1. এই কোডে দেখানো হিসাবে টীকা সহ আপনার Word ক্লাস আপডেট করুন:
@Entity(tableName = "word_table")
class Word(@PrimaryKey @ColumnInfo(name = "word") val word: String)

আসুন দেখি এই টীকাগুলি কি করে:

  • @Entity(tableName = "word_table" )
    প্রতিটি @Entity ক্লাস একটি SQLite টেবিল প্রতিনিধিত্ব করে। এটি একটি সত্তা নির্দেশ করতে আপনার ক্লাস ঘোষণা টীকা করুন। আপনি টেবিলের নাম নির্দিষ্ট করতে পারেন যদি আপনি এটি ক্লাসের নামের থেকে ভিন্ন হতে চান। এটি টেবিলটির নাম দেয় "শব্দ_সারণী"।
  • @PrimaryKey
    প্রতিটি সত্তার একটি প্রাথমিক কী প্রয়োজন। জিনিসগুলি সহজ রাখতে, প্রতিটি শব্দ তার নিজস্ব প্রাথমিক কী হিসাবে কাজ করে।
  • @ColumnInfo(name = "word" )
    আপনি যদি সদস্য ভেরিয়েবলের নামের থেকে আলাদা হতে চান তবে টেবিলে কলামের নাম নির্দিষ্ট করে। এটি কলামটির নাম দেয় "শব্দ"।
  • ডাটাবেসে সংরক্ষিত প্রতিটি সম্পত্তির সর্বজনীন দৃশ্যমানতা থাকা প্রয়োজন, যা কোটলিন ডিফল্ট।

আপনি রুম প্যাকেজ সারাংশ রেফারেন্সে টীকাগুলির একটি সম্পূর্ণ তালিকা খুঁজে পেতে পারেন।

DAO কি?

DAO (ডেটা অ্যাক্সেস অবজেক্ট) তে, আপনি এসকিউএল কোয়েরিগুলি নির্দিষ্ট করুন এবং সেগুলিকে মেথড কলের সাথে যুক্ত করুন। কম্পাইলার SQL চেক করে এবং সাধারণ প্রশ্নের জন্য সুবিধার টীকা থেকে প্রশ্ন তৈরি করে, যেমন @Insert । রুম আপনার কোডের জন্য একটি পরিষ্কার API তৈরি করতে DAO ব্যবহার করে।

DAO অবশ্যই একটি ইন্টারফেস বা বিমূর্ত শ্রেণী হতে হবে।

ডিফল্টরূপে, সমস্ত প্রশ্ন একটি পৃথক থ্রেডে কার্যকর করা আবশ্যক।

রুমে কোরোটিন সমর্থন রয়েছে, আপনার প্রশ্নগুলিকে suspend মডিফায়ারের সাথে টীকা করার অনুমতি দেয় এবং তারপরে কোরোটিন বা অন্য সাসপেনশন ফাংশন থেকে কল করা হয়।

DAO বাস্তবায়ন করুন

আসুন একটি DAO লিখি যা এর জন্য প্রশ্ন প্রদান করে:

  • সমস্ত শব্দ বর্ণানুক্রমিকভাবে ক্রম করা হচ্ছে
  • একটি শব্দ সন্নিবেশ
  • সব শব্দ মুছে ফেলা হচ্ছে
  1. WordDao নামে একটি নতুন Kotlin ক্লাস ফাইল তৈরি করুন।
  2. WordDao এ নিম্নলিখিত কোডটি অনুলিপি করুন এবং পেস্ট করুন এবং এটিকে কম্পাইল করার জন্য প্রয়োজনীয় আমদানিগুলি ঠিক করুন।
@Dao
interface WordDao {

    @Query("SELECT * from word_table ORDER BY word ASC")
    fun getAlphabetizedWords(): List<Word>

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insert(word: Word)

    @Query("DELETE FROM word_table")
    suspend fun deleteAll()
}

এর মধ্য দিয়ে চলুন:

  • WordDao একটি ইন্টারফেস; DAO গুলি অবশ্যই ইন্টারফেস বা বিমূর্ত ক্লাস হতে হবে।
  • @Dao টীকা এটিকে রুমের জন্য একটি DAO ক্লাস হিসাবে চিহ্নিত করে।
  • suspend fun insert(word: Word) : একটি শব্দ সন্নিবেশ করার জন্য একটি সাসপেন্ড ফাংশন ঘোষণা করে।
  • @Insert টীকা হল একটি বিশেষ DAO পদ্ধতির টীকা যেখানে আপনাকে কোনো SQL প্রদান করতে হবে না! (সারি মুছে ফেলা এবং আপডেট করার জন্য @Delete এবং @Update টীকাও আছে, কিন্তু আপনি এই অ্যাপে সেগুলি ব্যবহার করছেন না।)
  • onConflict = OnConflictStrategy.IGNORE : নির্বাচিত onConflict কৌশলটি একটি নতুন শব্দকে উপেক্ষা করে যদি এটি তালিকায় থাকা শব্দের মতোই হয়। উপলব্ধ দ্বন্দ্ব কৌশল সম্পর্কে আরও জানতে, ডকুমেন্টেশন দেখুন।
  • suspend fun deleteAll() : সমস্ত শব্দ মুছে ফেলার জন্য একটি সাসপেন্ড ফাংশন ঘোষণা করে।
  • একাধিক সত্তা মুছে ফেলার জন্য কোন সুবিধাজনক টীকা নেই, তাই এটি জেনেরিক @Query দিয়ে টীকা করা হয়েছে।
  • @Query ("DELETE FROM word_table") : @Query জন্য প্রয়োজন যে আপনি টীকাটিতে একটি স্ট্রিং প্যারামিটার হিসাবে একটি SQL কোয়েরি প্রদান করুন, জটিল পঠিত প্রশ্ন এবং অন্যান্য ক্রিয়াকলাপগুলির জন্য অনুমতি দেয়৷
  • fun getAlphabetizedWords(): List<Word> : একটি পদ্ধতি যাতে সব শব্দ পাওয়া যায় এবং এটিকে Words একটি List প্রদান করা হয়।
  • @Query( "SELECT * from word_table ORDER BY word ASC" ) : কোয়েরি যা ঊর্ধ্বে ক্রমানুসারে সাজানো শব্দের তালিকা প্রদান করে।

যখন ডেটা পরিবর্তন হয় তখন আপনি সাধারণত কিছু পদক্ষেপ নিতে চান, যেমন UI এ আপডেট করা ডেটা প্রদর্শন করা। এর মানে আপনাকে ডেটা পর্যবেক্ষণ করতে হবে যাতে এটি পরিবর্তন হলে আপনি প্রতিক্রিয়া জানাতে পারেন।

ডেটা কীভাবে সংরক্ষণ করা হয় তার উপর নির্ভর করে, এটি কঠিন হতে পারে। আপনার অ্যাপের একাধিক উপাদান জুড়ে ডেটার পরিবর্তনগুলি পর্যবেক্ষণ করা উপাদানগুলির মধ্যে স্পষ্ট, কঠোর নির্ভরতার পথ তৈরি করতে পারে। এটি অন্যান্য জিনিসগুলির মধ্যে পরীক্ষা এবং ডিবাগিংকে কঠিন করে তোলে।

LiveData , ডেটা পর্যবেক্ষণের জন্য একটি জীবনচক্র লাইব্রেরি ক্লাস, এই সমস্যার সমাধান করে। আপনার পদ্ধতির বিবরণে LiveData প্রকারের একটি রিটার্ন মান ব্যবহার করুন এবং ডাটাবেস আপডেট হলে রুম LiveData আপডেট করার জন্য সমস্ত প্রয়োজনীয় কোড তৈরি করে।

WordDao এ, getAlphabetizedWords() পদ্ধতি স্বাক্ষর পরিবর্তন করুন যাতে ফেরত List<Word> LiveData দিয়ে মোড়ানো হয়।

   @Query("SELECT * from word_table ORDER BY word ASC")
   fun getAlphabetizedWords(): LiveData<List<Word>>

পরে এই কোডল্যাবে, আপনি MainActivity এ একজন Observer মাধ্যমে ডেটা পরিবর্তনগুলি ট্র্যাক করেন।

একটি রুম ডাটাবেস কি ?

  • রুম হল একটি SQLite ডাটাবেসের উপরে একটি ডাটাবেস স্তর।
  • রুম জাগতিক কাজগুলির যত্ন নেয় যা আপনি একটি SQLiteOpenHelper দিয়ে পরিচালনা করতেন।
  • রুম তার ডাটাবেসে প্রশ্ন জারি করতে DAO ব্যবহার করে।
  • ডিফল্টরূপে, দুর্বল UI কর্মক্ষমতা এড়াতে, রুম আপনাকে মূল থ্রেডে প্রশ্নগুলি ইস্যু করার অনুমতি দেয় না। যখন রুম কোয়েরিগুলি LiveData ফেরত দেয়, তখন প্রশ্নগুলি স্বয়ংক্রিয়ভাবে একটি পটভূমি থ্রেডে অ্যাসিঙ্ক্রোনাসভাবে চালানো হয়।
  • রুম SQLite স্টেটমেন্টের কম্পাইল-টাইম চেক প্রদান করে।

রুম ডাটাবেস বাস্তবায়ন

আপনার রুম ডাটাবেস ক্লাস বিমূর্ত হতে হবে এবং RoomDatabase প্রসারিত করতে হবে। সাধারণত, পুরো অ্যাপের জন্য আপনার শুধুমাত্র একটি রুম ডাটাবেসের একটি উদাহরণ প্রয়োজন।

এখন একটি করা যাক.

  1. WordRoomDatabase নামে একটি Kotlin ক্লাস ফাইল তৈরি করুন এবং এতে এই কোড যোগ করুন:
// Annotates class to be a Room Database with a table (entity) of the Word class
@Database(entities = arrayOf(Word::class), version = 1, exportSchema = false)
public abstract class WordRoomDatabase : RoomDatabase() {

   abstract fun wordDao(): WordDao

   companion object {
        // Singleton prevents multiple instances of database opening at the
        // same time. 
        @Volatile
        private var INSTANCE: WordRoomDatabase? = null

        fun getDatabase(context: Context): WordRoomDatabase {
            val tempInstance = INSTANCE
            if (tempInstance != null) {
                return tempInstance
            }
            synchronized(this) {
                val instance = Room.databaseBuilder(
                        context.applicationContext,
                        WordRoomDatabase::class.java, 
                        "word_database"
                    ).build()
                INSTANCE = instance
                return instance
            }
        }
   }
}

আসুন কোডের মাধ্যমে চলুন:

  • রুমের জন্য ডাটাবেস ক্লাসটি অবশ্যই abstract হতে হবে এবং RoomDatabase প্রসারিত করতে হবে
  • আপনি @Database সাথে একটি রুম ডাটাবেস হওয়ার জন্য ক্লাসটিকে টীকা করুন এবং ডাটাবেসের অন্তর্গত সত্তা ঘোষণা করতে এবং সংস্করণ নম্বর সেট করতে টীকা প্যারামিটার ব্যবহার করুন। প্রতিটি সত্তা একটি টেবিলের সাথে মিলে যায় যা ডাটাবেসে তৈরি করা হবে। ডেটাবেস স্থানান্তরগুলি এই কোডল্যাবের সুযোগের বাইরে, তাই বিল্ড সতর্কতা এড়াতে আমরা exportSchema এখানে মিথ্যা সেট করেছি। একটি বাস্তব অ্যাপে, আপনার স্কিমা এক্সপোর্ট করার জন্য রুমের জন্য একটি ডিরেক্টরি সেট করার কথা বিবেচনা করা উচিত যাতে আপনি আপনার সংস্করণ নিয়ন্ত্রণ সিস্টেমে বর্তমান স্কিমা পরীক্ষা করতে পারেন।
  • ডাটাবেস প্রতিটি @Dao-এর জন্য একটি বিমূর্ত "গেটার" পদ্ধতির মাধ্যমে DAOগুলিকে প্রকাশ করে।
  • একই সময়ে ডাটাবেসের একাধিক দৃষ্টান্ত খোলা হওয়া প্রতিরোধ করার জন্য আমরা একটি singleton , WordRoomDatabase,
  • getDatabase সিঙ্গলটন রিটার্ন করে। WordRoomDatabase ক্লাস থেকে অ্যাপ্লিকেশন প্রসঙ্গে একটি RoomDatabase অবজেক্ট তৈরি করতে রুমের ডাটাবেস নির্মাতা ব্যবহার করে এটি প্রথমবার অ্যাক্সেস করার সময় এটি ডাটাবেস তৈরি করবে এবং এটিকে "word_database" নাম দেবে।

ভান্ডার কি?

একটি রিপোজিটরি ক্লাস অ্যাবস্ট্রাক্ট করে একাধিক ডেটা সোর্স অ্যাক্সেস করে। সংগ্রহস্থলটি আর্কিটেকচার কম্পোনেন্ট লাইব্রেরির অংশ নয়, তবে কোড বিভাজন এবং আর্কিটেকচারের জন্য এটি একটি প্রস্তাবিত সেরা অনুশীলন। একটি রিপোজিটরি ক্লাস বাকি অ্যাপ্লিকেশনে ডেটা অ্যাক্সেসের জন্য একটি পরিষ্কার API প্রদান করে।

কেন একটি সংগ্রহস্থল ব্যবহার?

একটি সংগ্রহস্থল প্রশ্নগুলি পরিচালনা করে এবং আপনাকে একাধিক ব্যাকএন্ড ব্যবহার করার অনুমতি দেয়। সবচেয়ে সাধারণ উদাহরণে, রিপোজিটরি একটি নেটওয়ার্ক থেকে ডেটা আনতে বা স্থানীয় ডাটাবেসে ক্যাশে করা ফলাফলগুলি ব্যবহার করার সিদ্ধান্ত নেওয়ার জন্য যুক্তি প্রয়োগ করে।

সংগ্রহস্থল বাস্তবায়ন

WordRepository নামে একটি Kotlin ক্লাস ফাইল তৈরি করুন এবং এতে নিম্নলিখিত কোড পেস্ট করুন:

// Declares the DAO as a private property in the constructor. Pass in the DAO
// instead of the whole database, because you only need access to the DAO
class WordRepository(private val wordDao: WordDao) {

    // Room executes all queries on a separate thread.
    // Observed LiveData will notify the observer when the data has changed.
    val allWords: LiveData<List<Word>> = wordDao.getAlphabetizedWords()
 
    suspend fun insert(word: Word) {
        wordDao.insert(word)
    }
}

প্রধান টেকওয়ে:

  • DAO পুরো ডাটাবেসের বিপরীতে রিপোজিটরি কনস্ট্রাক্টরে পাস করা হয়। এর কারণ হল এটি শুধুমাত্র DAO-তে অ্যাক্সেসের প্রয়োজন, যেহেতু DAO-তে ডাটাবেসের জন্য সমস্ত পঠন/লেখার পদ্ধতি রয়েছে। সংগ্রহস্থলে সমগ্র ডাটাবেস প্রকাশ করার কোন প্রয়োজন নেই।
  • শব্দ তালিকা একটি পাবলিক সম্পত্তি. এটি রুম থেকে শব্দের LiveData তালিকা পাওয়ার মাধ্যমে শুরু করা হয়েছে; আমরা "The LiveData ক্লাস" ধাপে LiveData ফেরত দেওয়ার জন্য getAlphabetizedWords পদ্ধতিটিকে কীভাবে সংজ্ঞায়িত করেছি তার কারণে আমরা এটি করতে পারি। রুম একটি পৃথক থ্রেডে সমস্ত প্রশ্ন নির্বাহ করে। তারপর পর্যবেক্ষণ করা LiveData ডেটা পরিবর্তিত হলে প্রধান থ্রেডে পর্যবেক্ষককে অবহিত করবে।
  • suspend মডিফায়ার কম্পাইলারকে বলে যে এটি একটি কোরোটিন বা অন্য সাসপেন্ডিং ফাংশন থেকে কল করা দরকার।

একটি ViewModel কি?

ViewModel এর ভূমিকা হল UI-তে ডেটা প্রদান করা এবং কনফিগারেশন পরিবর্তনগুলিকে বাঁচিয়ে রাখা। একটি ViewModel রিপোজিটরি এবং UI-এর মধ্যে যোগাযোগ কেন্দ্র হিসেবে কাজ করে। আপনি টুকরোগুলির মধ্যে ডেটা ভাগ করার জন্য একটি ViewModel ব্যবহার করতে পারেন। ViewModel হল জীবনচক্র লাইব্রেরির অংশ।

এই বিষয়ে একটি পরিচায়ক গাইডের জন্য, দেখুন ViewModel Overview or the ViewModels: A Simple Example blog post.

কেন একটি ViewModel ব্যবহার করবেন?

একটি ViewModel আপনার অ্যাপের UI ডেটাকে লাইফসাইকেল-সচেতন উপায়ে ধারণ করে যা কনফিগারেশন পরিবর্তনগুলি থেকে বাঁচে। আপনার অ্যাপ্লিকেশানের UI ডেটা আপনার Activity এবং Fragment ক্লাসগুলি থেকে আলাদা করা আপনাকে একক দায়িত্ব নীতিটি আরও ভালভাবে অনুসরণ করতে দেয়: আপনার ক্রিয়াকলাপ এবং খণ্ডগুলি স্ক্রীনে ডেটা আঁকার জন্য দায়ী, যখন আপনার ViewModel UI এর জন্য প্রয়োজনীয় সমস্ত ডেটা ধারণ এবং প্রক্রিয়াকরণের যত্ন নিতে পারে৷

ViewModel এ, পরিবর্তনযোগ্য ডেটার জন্য LiveData ব্যবহার করুন যা UI ব্যবহার করবে বা প্রদর্শন করবে। LiveData ব্যবহার করার বেশ কিছু সুবিধা রয়েছে:

  • আপনি ডেটাতে একজন পর্যবেক্ষক রাখতে পারেন (পরিবর্তনের জন্য ভোট দেওয়ার পরিবর্তে) এবং শুধুমাত্র আপডেট করতে পারেন
    UI যখন ডেটা আসলে পরিবর্তিত হয়।
  • রিপোজিটরি এবং UI সম্পূর্ণরূপে ViewModel দ্বারা পৃথক করা হয়েছে।
  • ViewModel থেকে কোন ডাটাবেস কল নেই (এটি সমস্ত রিপোজিটরিতে পরিচালনা করা হয়), কোডটিকে আরও পরীক্ষাযোগ্য করে তোলে।

দেখুন মডেলস্কোপ

কোটলিনে, সমস্ত করটিন একটি CoroutineScope মধ্যে চলে। একটি স্কোপ তার কাজের মাধ্যমে করোটিনের জীবনকাল নিয়ন্ত্রণ করে। আপনি যখন একটি সুযোগের কাজ বাতিল করেন, তখন এটি সেই সুযোগে শুরু হওয়া সমস্ত কোরোটিন বাতিল করে।

AndroidX lifecycle-viewmodel-ktx লাইব্রেরি ViewModel ক্লাসের একটি এক্সটেনশন ফাংশন হিসাবে একটি viewModelScope যোগ করে, যা আপনাকে স্কোপের সাথে কাজ করতে সক্ষম করে।

ভিউমডেলে কোরোটিনগুলির সাথে কাজ করার বিষয়ে আরও জানতে, আপনার অ্যান্ড্রয়েড অ্যাপ কোডল্যাবে কোটলিন কোরটিন ব্যবহার করার ধাপ 5 বা অ্যান্ড্রয়েডের সহজ কোরোটিনগুলি দেখুন: viewModelScope ব্লগপোস্ট

ভিউ মডেল বাস্তবায়ন করুন

WordViewModel এর জন্য একটি Kotlin ক্লাস ফাইল তৈরি করুন এবং এতে এই কোড যোগ করুন:

class WordViewModel(application: Application) : AndroidViewModel(application) {

    private val repository: WordRepository
    // Using LiveData and caching what getAlphabetizedWords returns has several benefits:
    // - We can put an observer on the data (instead of polling for changes) and only update the
    //   the UI when the data actually changes.
    // - Repository is completely separated from the UI through the ViewModel.
    val allWords: LiveData<List<Word>>

    init {
        val wordsDao = WordRoomDatabase.getDatabase(application).wordDao()
        repository = WordRepository(wordsDao)
        allWords = repository.allWords
    }

    /**
     * Launching a new coroutine to insert the data in a non-blocking way
     */
    fun insert(word: Word) = viewModelScope.launch(Dispatchers.IO) {
        repository.insert(word)
    }
}

এখানে আমরা করেছি:

  • WordViewModel নামে একটি ক্লাস তৈরি করা হয়েছে যা Application একটি প্যারামিটার হিসাবে পায় এবং AndroidViewModel প্রসারিত করে।
  • সংগ্রহস্থলের একটি রেফারেন্স ধরে রাখতে একটি ব্যক্তিগত সদস্য ভেরিয়েবল যোগ করা হয়েছে।
  • শব্দের তালিকা ক্যাশে করার জন্য একটি সর্বজনীন LiveData সদস্য ভেরিয়েবল যোগ করা হয়েছে।
  • একটি init ব্লক তৈরি করা হয়েছে যা WordRoomDatabase থেকে WordDao এর একটি রেফারেন্স পায়।
  • init ব্লকে, WordRoomDatabase এর উপর ভিত্তি করে WordRepository তৈরি করা হয়েছে।
  • init ব্লকে, রিপোজিটরি ব্যবহার করে allWords LiveData শুরু করুন।
  • একটি wrapper insert() পদ্ধতি তৈরি করা হয়েছে যা Repository এর insert() পদ্ধতিকে কল করে। এইভাবে, insert() এর বাস্তবায়ন UI থেকে encapsulated হয়। আমরা মূল থ্রেডটি ব্লক করতে সন্নিবেশ করতে চাই না, তাই আমরা একটি নতুন কোরোটিন চালু করছি এবং সংগ্রহস্থলের সন্নিবেশকে কল করছি, যা একটি সাসপেন্ড ফাংশন। উল্লিখিত হিসাবে, ViewModels-এর তাদের জীবনচক্রের উপর ভিত্তি করে একটি করুটিন সুযোগ রয়েছে যাকে বলা হয় viewModelScope , যা আমরা এখানে ব্যবহার করি।

এর পরে, আপনাকে তালিকা এবং আইটেমগুলির জন্য XML লেআউট যোগ করতে হবে।

এই কোডল্যাব অনুমান করে যে আপনি XML-এ লেআউট তৈরির সাথে পরিচিত, তাই আমরা আপনাকে কোডটি প্রদান করছি।

AppTheme প্যারেন্টটিকে Theme.MaterialComponents.Light.DarkActionBar এ সেট করে আপনার অ্যাপ্লিকেশনের থিম উপাদান তৈরি করুন। values/styles.xml এ তালিকা আইটেমগুলির জন্য একটি শৈলী যোগ করুন:

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <!-- The default font for RecyclerView items is too small.
    The margin is a simple delimiter between the words. -->
    <style name="word_title">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_marginBottom">8dp</item>
        <item name="android:paddingLeft">8dp</item>
        <item name="android:background">@android:color/holo_orange_light</item>
        <item name="android:textAppearance">@android:style/TextAppearance.Large</item>
    </style>
</resources>

একটি layout/recyclerview_item.xml লেআউট যোগ করুন:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" 
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/textView"
        style="@style/word_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_light" />
</LinearLayout>

layout/activity_main.xml এ, TextView একটি RecyclerView দিয়ে প্রতিস্থাপন করুন এবং একটি ফ্লোটিং অ্যাকশন বোতাম (এফএবি) যোগ করুন। এখন আপনার লেআউট এই মত হওয়া উচিত:

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="0dp"
        android:layout_height="0dp"
        tools:listitem="@layout/recyclerview_item"
        android:padding="@dimen/big_padding"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:contentDescription="@string/add_word"/>

</androidx.constraintlayout.widget.ConstraintLayout>

আপনার FAB এর উপস্থিতি উপলব্ধ কর্মের সাথে মিলিত হওয়া উচিত, তাই আমরা একটি '+' চিহ্ন দিয়ে আইকন প্রতিস্থাপন করতে চাই।

প্রথমত, আমাদের একটি নতুন ভেক্টর সম্পদ যোগ করতে হবে:

  1. ফাইল > নতুন > ভেক্টর সম্পদ নির্বাচন করুন।
  2. ক্লিপ আর্ট ফিল্ডে অ্যান্ড্রয়েড রোবট আইকনে ক্লিক করুন।
  3. "যোগ করুন" অনুসন্ধান করুন এবং '+' সম্পদ নির্বাচন করুন। ওকে ক্লিক করুন।
  4. এর পরে, Next এ ক্লিক করুন।
  5. আইকন পথটিকে main > drawable হিসেবে নিশ্চিত করুন এবং সম্পদ যোগ করতে Finish এ ক্লিক করুন।
  6. এখনও layout/activity_main.xml এ, নতুন অঙ্কনযোগ্য অন্তর্ভুক্ত করতে FAB আপডেট করুন:
<com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:contentDescription="@string/add_word"
        android:src="@drawable/ic_add_black_24dp"/>

আপনি একটি RecyclerView এ ডেটা প্রদর্শন করতে যাচ্ছেন, যা শুধুমাত্র একটি TextView এ ডেটা নিক্ষেপ করার চেয়ে একটু সুন্দর। এই কোডল্যাব ধরে নেয় যে আপনি জানেন কিভাবে RecyclerView , RecyclerView.LayoutManager , RecyclerView.ViewHolder , এবং RecyclerView.Adapter কাজ করে৷

লক্ষ্য করুন যে অ্যাডাপ্টারের ভেরিয়েবল words ডেটা ক্যাশে করে। পরবর্তী টাস্কে, আপনি কোডটি যোগ করুন যা স্বয়ংক্রিয়ভাবে ডেটা আপডেট করে।

WordListAdapter এর জন্য একটি Kotlin ক্লাস ফাইল তৈরি করুন যা RecyclerView.Adapter প্রসারিত করে। এখানে কোড আছে:

class WordListAdapter internal constructor(
        context: Context
) : RecyclerView.Adapter<WordListAdapter.WordViewHolder>() {

    private val inflater: LayoutInflater = LayoutInflater.from(context)
    private var words = emptyList<Word>() // Cached copy of words

    inner class WordViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val wordItemView: TextView = itemView.findViewById(R.id.textView)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder {
        val itemView = inflater.inflate(R.layout.recyclerview_item, parent, false)
        return WordViewHolder(itemView)
    }

    override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
        val current = words[position]
        holder.wordItemView.text = current.word
    }

    internal fun setWords(words: List<Word>) {
        this.words = words
        notifyDataSetChanged()
    }

    override fun getItemCount() = words.size
}

MainActivity এর onCreate() পদ্ধতিতে RecyclerView যোগ করুন।

setContentView পরে onCreate() পদ্ধতিতে:

   val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
   val adapter = WordListAdapter(this)
   recyclerView.adapter = adapter
   recyclerView.layoutManager = LinearLayoutManager(this)

সবকিছু কাজ করে তা নিশ্চিত করতে আপনার অ্যাপ চালান। কোন আইটেম নেই, কারণ আপনি এখনও ডেটা হুক আপ করেননি৷

ডাটাবেসে কোন তথ্য নেই। আপনি দুটি উপায়ে ডেটা যোগ করবেন: ডাটাবেস খোলা হলে কিছু ডেটা যোগ করুন এবং শব্দ যোগ করার জন্য একটি Activity যোগ করুন।

সমস্ত বিষয়বস্তু মুছে ফেলতে এবং যখনই অ্যাপটি শুরু হয় তখন ডাটাবেস পুনরুদ্ধার করতে, আপনি একটি RoomDatabase.Callback তৈরি করুন৷ কলব্যাক করুন এবং onOpen() কে ওভাররাইড করুন৷ যেহেতু আপনি UI থ্রেডে রুম ডাটাবেস অপারেশন করতে পারবেন না, তাই onOpen() IO ডিসপ্যাচারে একটি করুটিন চালু করে।

একটি coroutine চালু করতে আমাদের একটি CoroutineScope প্রয়োজন৷ WordRoomDatabase ক্লাসের getDatabase পদ্ধতি আপডেট করুন, এছাড়াও প্যারামিটার হিসাবে একটি coroutine সুযোগ পেতে:

fun getDatabase(
       context: Context,
       scope: CoroutineScope
  ): WordRoomDatabase {
...
}

স্কোপ পাস করতে WordViewModel এর init ব্লকে ডাটাবেস পুনরুদ্ধার ইনিশিয়ালাইজার আপডেট করুন:

val wordsDao = WordRoomDatabase.getDatabase(application, viewModelScope).wordDao()

WordRoomDatabase এ, আমরা RoomDatabase.Callback() এর একটি কাস্টম বাস্তবায়ন তৈরি করি, যা কনস্ট্রাক্টর প্যারামিটার হিসেবে একটি CoroutineScope ও পায়। তারপর, ডাটাবেস পপুলেট করার জন্য আমরা onOpen পদ্ধতিটি ওভাররাইড করি।

WordRoomDatabase ক্লাসের মধ্যে কলব্যাক তৈরি করার কোডটি এখানে রয়েছে:

private class WordDatabaseCallback(
    private val scope: CoroutineScope
) : RoomDatabase.Callback() {

    override fun onOpen(db: SupportSQLiteDatabase) {
        super.onOpen(db)
        INSTANCE?.let { database ->
            scope.launch {
                populateDatabase(database.wordDao())
            }
        }
    }

    suspend fun populateDatabase(wordDao: WordDao) {
        // Delete all content here.
        wordDao.deleteAll()

        // Add sample words.
        var word = Word("Hello")
        wordDao.insert(word)
        word = Word("World!")
        wordDao.insert(word)

        // TODO: Add your own words!
    }
}

অবশেষে, Room.databaseBuilder() .build() কল করার আগে ডাটাবেস বিল্ড সিকোয়েন্সে কলব্যাক যোগ করুন :

.addCallback(WordDatabaseCallback(scope))

এখানে চূড়ান্ত কোড দেখতে কেমন হওয়া উচিত:

@Database(entities = arrayOf(Word::class), version = 1, exportSchema = false)
abstract class WordRoomDatabase : RoomDatabase() {

   abstract fun wordDao(): WordDao

   private class WordDatabaseCallback(
       private val scope: CoroutineScope
   ) : RoomDatabase.Callback() {

       override fun onOpen(db: SupportSQLiteDatabase) {
           super.onOpen(db)
           INSTANCE?.let { database ->
               scope.launch {
                   var wordDao = database.wordDao()

                   // Delete all content here.
                   wordDao.deleteAll()

                   // Add sample words.
                   var word = Word("Hello")
                   wordDao.insert(word)
                   word = Word("World!")
                   wordDao.insert(word)

                   // TODO: Add your own words!
                   word = Word("TODO!")
                   wordDao.insert(word)
               }
           }
       }
   }

   companion object {
       @Volatile
       private var INSTANCE: WordRoomDatabase? = null

       fun getDatabase(
           context: Context,
           scope: CoroutineScope
       ): WordRoomDatabase {
            // if the INSTANCE is not null, then return it,
            // if it is, then create the database
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                        context.applicationContext,
                        WordRoomDatabase::class.java,
                        "word_database"
                )
                 .addCallback(WordDatabaseCallback(scope))
                 .build()
                INSTANCE = instance
                // return instance
                instance
        }
     }
   }
}

values/strings.xml এ এই স্ট্রিং রিসোর্স যোগ করুন:

<string name="hint_word">Word...</string>
<string name="button_save">Save</string>
<string name="empty_not_saved">Word not saved because it is empty.</string>

value/colors.xml এ এই রঙের সংস্থান যোগ করুন:

<color name="buttonLabel">#FFFFFF</color>

একটি নতুন মাত্রা সম্পদ ফাইল তৈরি করুন:

  1. প্রজেক্ট উইন্ডোতে অ্যাপ মডিউলে ক্লিক করুন।
  2. ফাইল > নতুন > অ্যান্ড্রয়েড রিসোর্স ফাইল নির্বাচন করুন
  3. Available Qualifiers থেকে, Dimension নির্বাচন করুন
  4. ফাইলের নাম সেট করুন: মাত্রা

values/dimens.xml এ এই মাত্রার সম্পদ যোগ করুন:

<dimen name="small_padding">8dp</dimen>
<dimen name="big_padding">16dp</dimen>

খালি কার্যকলাপ টেমপ্লেট দিয়ে একটি নতুন খালি Android Activity তৈরি করুন:

  1. ফাইল > নতুন > কার্যকলাপ > খালি কার্যকলাপ নির্বাচন করুন
  2. কার্যকলাপ নামের জন্য NewWordActivity লিখুন।
  3. Android ম্যানিফেস্টে নতুন কার্যকলাপ যোগ করা হয়েছে তা যাচাই করুন।
<activity android:name=".NewWordActivity"></activity>

নিম্নলিখিত কোড সহ লেআউট ফোল্ডারে activity_new_word.xml ফাইল আপডেট করুন:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/edit_word"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="@dimen/min_height"
        android:fontFamily="sans-serif-light"
        android:hint="@string/hint_word"
        android:inputType="textAutoComplete"
        android:layout_margin="@dimen/big_padding"
        android:textSize="18sp" />

    <Button
        android:id="@+id/button_save"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        android:text="@string/button_save"
        android:layout_margin="@dimen/big_padding"
        android:textColor="@color/buttonLabel" />

</LinearLayout>

কার্যকলাপের জন্য কোড আপডেট করুন:

class NewWordActivity : AppCompatActivity() {

    private lateinit var editWordView: EditText

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_new_word)
        editWordView = findViewById(R.id.edit_word)

        val button = findViewById<Button>(R.id.button_save)
        button.setOnClickListener {
            val replyIntent = Intent()
            if (TextUtils.isEmpty(editWordView.text)) {
                setResult(Activity.RESULT_CANCELED, replyIntent)
            } else {
                val word = editWordView.text.toString()
                replyIntent.putExtra(EXTRA_REPLY, word)
                setResult(Activity.RESULT_OK, replyIntent)
            }
            finish()
        }
    }

    companion object {
        const val EXTRA_REPLY = "com.example.android.wordlistsql.REPLY"
    }
}

চূড়ান্ত পদক্ষেপ হল ব্যবহারকারীর প্রবেশ করা নতুন শব্দ সংরক্ষণ করে এবং RecyclerView এ শব্দ ডাটাবেসের বর্তমান বিষয়বস্তু প্রদর্শন করে UI-কে ডাটাবেসের সাথে সংযুক্ত করা।

ডাটাবেসের বর্তমান বিষয়বস্তু প্রদর্শন করতে, একটি পর্যবেক্ষক যোগ করুন যেটি ViewModelLiveData পর্যবেক্ষণ করে।

যখনই ডেটা পরিবর্তিত হয়, onChanged() কলব্যাক আহ্বান করা হয়, যা অ্যাডাপ্টারের setWords() পদ্ধতিকে অ্যাডাপ্টারের ক্যাশে করা ডেটা আপডেট করতে এবং প্রদর্শিত তালিকাটি রিফ্রেশ করতে কল করে।

MainActivity এ, ViewModel জন্য একটি সদস্য ভেরিয়েবল তৈরি করুন:

private lateinit var wordViewModel: WordViewModel

আপনার Activity সাথে আপনার ViewModel যুক্ত করতে ViewModelProvider ব্যবহার করুন।

যখন আপনার Activity প্রথম শুরু হয়, ViewModelProviders ViewModel তৈরি করবে। যখন কার্যকলাপটি ধ্বংস হয়ে যায়, উদাহরণস্বরূপ একটি কনফিগারেশন পরিবর্তনের মাধ্যমে, ViewModel টিকে থাকে। যখন কার্যকলাপটি পুনরায় তৈরি করা হয়, ViewModelProviders বিদ্যমান ViewModel ফিরিয়ে দেয়। আরও তথ্যের জন্য, ViewModel দেখুন।

RecyclerView কোড ব্লকের নিচে onCreate() এ, ViewModelProvider থেকে একটি ViewModel পান:

wordViewModel = ViewModelProvider(this).get(WordViewModel::class.java)

এছাড়াও onCreate() এ, WordViewModel থেকে allWords LiveData সম্পত্তির জন্য একজন পর্যবেক্ষক যোগ করুন।

onChanged() পদ্ধতি (আমাদের Lambda-এর জন্য ডিফল্ট পদ্ধতি) যখন পর্যবেক্ষণ করা ডেটা পরিবর্তিত হয় এবং কার্যকলাপটি অগ্রভাগে থাকে তখন ফায়ার হয়:

wordViewModel.allWords.observe(this, Observer { words ->
            // Update the cached copy of the words in the adapter.
            words?.let { adapter.setWords(it) }
})

FAB-এ ট্যাপ করার সময় আমরা NewWordActivity খুলতে চাই এবং, একবার আমরা MainActivity এ ফিরে এলে, হয় ডাটাবেসে নতুন শব্দ সন্নিবেশ করাতে বা একটি Toast দেখাতে। এটি অর্জন করতে, আসুন একটি অনুরোধ কোড সংজ্ঞায়িত করে শুরু করি:

private val newWordActivityRequestCode = 1

MainActivity এ, NewWordActivity জন্য onActivityResult() কোড যোগ করুন।

যদি কার্যকলাপটি RESULT_OK সাথে ফিরে আসে, তাহলে WordViewModel এর insert() পদ্ধতিতে কল করে প্রত্যাবর্তিত শব্দটিকে ডাটাবেসে প্রবেশ করান:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    if (requestCode == newWordActivityRequestCode && resultCode == Activity.RESULT_OK) {
        data?.getStringExtra(NewWordActivity.EXTRA_REPLY)?.let {
            val word = Word(it)
            wordViewModel.insert(word)
        }
    } else {
        Toast.makeText(
            applicationContext,
            R.string.empty_not_saved,
            Toast.LENGTH_LONG).show()
    }
}

MainActivity, ব্যবহারকারী যখন FAB-এ ট্যাপ করে তখন NewWordActivity শুরু করুন। MainActivity onCreate এ, FAB খুঁজুন এবং এই কোডের সাথে একটি onClickListener যোগ করুন:

val fab = findViewById<FloatingActionButton>(R.id.fab)
fab.setOnClickListener {
  val intent = Intent(this@MainActivity, NewWordActivity::class.java)
  startActivityForResult(intent, newWordActivityRequestCode)
}

আপনার সমাপ্ত কোড এই মত হওয়া উচিত:

class MainActivity : AppCompatActivity() {

   private const val newWordActivityRequestCode = 1
   private lateinit var wordViewModel: WordViewModel

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)
       setSupportActionBar(toolbar)

       val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
       val adapter = WordListAdapter(this)
       recyclerView.adapter = adapter
       recyclerView.layoutManager = LinearLayoutManager(this)

       wordViewModel = ViewModelProvider(this).get(WordViewModel::class.java)
       wordViewModel.allWords.observe(this, Observer { words ->
           // Update the cached copy of the words in the adapter.
           words?.let { adapter.setWords(it) }
       })

       val fab = findViewById<FloatingActionButton>(R.id.fab)
       fab.setOnClickListener {
           val intent = Intent(this@MainActivity, NewWordActivity::class.java)
           startActivityForResult(intent, newWordActivityRequestCode)
       }
   }

   override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
       super.onActivityResult(requestCode, resultCode, data)

       if (requestCode == newWordActivityRequestCode && resultCode == Activity.RESULT_OK) {
           data?.getStringExtra(NewWordActivity.EXTRA_REPLY)?.let {
               val word = Word(it)
               wordViewModel.insert(word)
           }
       } else {
           Toast.makeText(
               applicationContext,
               R.string.empty_not_saved,
               Toast.LENGTH_LONG).show()
       }
   }
}

এখন আপনার অ্যাপ চালান! যখন আপনি NewWordActivity এ ডাটাবেসে একটি শব্দ যোগ করবেন, তখন UI স্বয়ংক্রিয়ভাবে আপডেট হবে।

এখন আপনার কাছে একটি কার্যকরী অ্যাপ আছে, আসুন আপনি যা তৈরি করেছেন তা সংক্ষেপে দেখা যাক। এখানে আবার অ্যাপ্লিকেশন কাঠামো:

অ্যাপটির উপাদানগুলো হল:

  • MainActivity : একটি RecyclerView এবং WordListAdapter ব্যবহার করে একটি তালিকায় শব্দ প্রদর্শন করে। MainActivity , একজন Observer আছে যে ডাটাবেস থেকে লাইভডেটা শব্দগুলি পর্যবেক্ষণ করে এবং যখন সেগুলি পরিবর্তন হয় তখন অবহিত করা হয়।
  • NewWordActivity: তালিকায় একটি নতুন শব্দ যোগ করে।
  • WordViewModel : ডেটা স্তর অ্যাক্সেস করার পদ্ধতি প্রদান করে এবং এটি লাইভডেটা প্রদান করে যাতে MainActivity পর্যবেক্ষক সম্পর্ক স্থাপন করতে পারে।*
  • LiveData<List<Word>> : UI উপাদানগুলিতে স্বয়ংক্রিয় আপডেটগুলি সম্ভব করে তোলে। MainActivity , একজন Observer আছে যে ডাটাবেস থেকে লাইভডেটা শব্দগুলি পর্যবেক্ষণ করে এবং যখন সেগুলি পরিবর্তন হয় তখন তাকে অবহিত করা হয়।
  • Repository: এক বা একাধিক ডেটা উত্স পরিচালনা করে। Repository অন্তর্নিহিত ডেটা প্রদানকারীর সাথে ইন্টারঅ্যাক্ট করার জন্য ViewModel-এর পদ্ধতিগুলি প্রকাশ করে৷ এই অ্যাপে, সেই ব্যাকএন্ড হল একটি রুম ডাটাবেস।
  • Room : চারপাশে একটি মোড়ক এবং একটি SQLite ডাটাবেস প্রয়োগ করে। রুম আপনার জন্য অনেক কাজ করে যা আপনাকে নিজেকে করতে হবে।
  • DAO: মানচিত্র পদ্ধতি ডাটাবেস প্রশ্নে কল করে, যাতে রিপোজিটরি যখন getAlphabetizedWords() এর মতো একটি পদ্ধতি কল করে, তখন রুম SELECT * from word_table ORDER BY word ASC চালাতে পারে
  • Word : সত্তা শ্রেণী যা একটি একক শব্দ ধারণ করে।

* Views এবং Activities (এবং Fragments ) শুধুমাত্র ViewModel মাধ্যমে ডেটার সাথে ইন্টারঅ্যাক্ট করে। যেমন, ডেটা কোথা থেকে আসে তা বিবেচ্য নয়।

স্বয়ংক্রিয় UI আপডেটের জন্য ডেটা প্রবাহ (প্রতিক্রিয়াশীল UI)

স্বয়ংক্রিয় আপডেট সম্ভব কারণ আমরা LiveData ব্যবহার করছি। MainActivity , একজন Observer আছে যে ডাটাবেস থেকে লাইভডেটা শব্দগুলি পর্যবেক্ষণ করে এবং যখন সেগুলি পরিবর্তন হয় তখন তাকে অবহিত করা হয়। যখন কোন পরিবর্তন হয়, পর্যবেক্ষকের onChange() পদ্ধতিটি কার্যকর করা হয় এবং WordListAdaptermWords আপডেট করে।

ডেটা পর্যবেক্ষণ করা যেতে পারে কারণ এটি LiveData । এবং যা পর্যবেক্ষণ করা হয় তা হল LiveData<List<Word>> যা WordViewModel একটি llWords সম্পত্তি দ্বারা ফেরত দেয়।

WordViewModel UI স্তর থেকে ব্যাকএন্ড সম্পর্কে সবকিছু লুকিয়ে রাখে। এটি ডেটা স্তর অ্যাক্সেস করার পদ্ধতি প্রদান করে এবং এটি LiveData প্রদান করে যাতে MainActivity পর্যবেক্ষক সম্পর্ক স্থাপন করতে পারে। Views এবং Activities (এবং Fragments ) শুধুমাত্র ViewModel মাধ্যমে ডেটার সাথে ইন্টারঅ্যাক্ট করে। যেমন, ডেটা কোথা থেকে আসে তা বিবেচ্য নয়।

এই ক্ষেত্রে, তথ্য Repository থেকে আসে। সেই রিপোজিটরিটি কিসের সাথে ইন্টারঅ্যাক্ট করে তা ViewModel জানার দরকার নেই। এটিকে কেবল Repository সাথে কীভাবে ইন্টারঅ্যাক্ট করতে হয় তা জানতে হবে, যা Repository দ্বারা উন্মুক্ত পদ্ধতির মাধ্যমে।

সংগ্রহস্থল এক বা একাধিক ডেটা উত্স পরিচালনা করে। WordListSample অ্যাপে, সেই ব্যাকএন্ডটি হল একটি রুম ডাটাবেস। রুম চারপাশে একটি মোড়ক এবং একটি SQLite ডাটাবেস প্রয়োগ করে। রুম আপনার জন্য অনেক কাজ করে যা আপনাকে নিজেকে করতে হবে। উদাহরণস্বরূপ, রুম সবকিছুই করে যা আপনি একটি SQLiteOpenHelper ক্লাসের সাথে করতেন।

DAO মানচিত্র পদ্ধতি ডাটাবেস প্রশ্নে কল করে, যাতে রিপোজিটরি যখন getAllWords() এর মতো একটি পদ্ধতি কল করে, তখন রুম SELECT * from word_table ORDER BY word ASC চালাতে পারে

কারণ ক্যোয়ারী থেকে প্রত্যাবর্তিত ফলাফল LiveData পরিলক্ষিত হয়, প্রতিবার রুমের ডেটা পরিবর্তিত হয়, Observer ইন্টারফেসের onChanged() পদ্ধতিটি কার্যকর করা হয় এবং UI আপডেট করা হয়।

[ঐচ্ছিক] সমাধান কোড ডাউনলোড করুন

আপনি যদি ইতিমধ্যেই না করে থাকেন তবে আপনি কোডল্যাবের সমাধান কোডটি দেখে নিতে পারেন। আপনি গিথুব সংগ্রহস্থলটি দেখতে পারেন বা এখানে কোডটি ডাউনলোড করতে পারেন:

সোর্স কোড ডাউনলোড করুন

ডাউনলোড করা জিপ ফাইলটি আনপ্যাক করুন। এটি একটি রুট ফোল্ডার আনপ্যাক করবে, android-room-with-a-view-kotlin , যাতে সম্পূর্ণ অ্যাপ রয়েছে।