ডাটা লেয়ার

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

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

ডেটা লেয়ার আর্কিটেকচার

ডেটা লেয়ারটি রিপোজিটরি দিয়ে গঠিত, যার প্রতিটিতে শূন্য থেকে একাধিক ডেটা সোর্স থাকতে পারে। আপনার অ্যাপে ব্যবহৃত প্রতিটি ভিন্ন ধরনের ডেটার জন্য একটি করে রিপোজিটরি ক্লাস তৈরি করা উচিত। উদাহরণস্বরূপ, আপনি সিনেমা সম্পর্কিত ডেটার জন্য একটি MoviesRepository ক্লাস, অথবা পেমেন্ট সম্পর্কিত ডেটার জন্য একটি PaymentsRepository ক্লাস তৈরি করতে পারেন।

একটি প্রচলিত আর্কিটেকচারে, ডেটা লেয়ারের রিপোজিটরিগুলো অ্যাপের বাকি অংশে ডেটা সরবরাহ করে এবং ডেটা সোর্সগুলোর উপর নির্ভর করে।
চিত্র ১. অ্যাপ আর্কিটেকচারে ডেটা লেয়ারের ভূমিকা।

রিপোজিটরি ক্লাসগুলো নিম্নলিখিত কাজগুলোর জন্য দায়ী:

  • অ্যাপের বাকি অংশে ডেটা প্রকাশ করা।
  • ডেটার পরিবর্তনসমূহ কেন্দ্রীভূত করা।
  • একাধিক ডেটা উৎসের মধ্যেকার দ্বন্দ্ব নিরসন করা।
  • অ্যাপের বাকি অংশ থেকে ডেটার উৎসগুলোকে আলাদা করা।
  • ব্যবসায়িক যুক্তি ধারণ করে।

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

হায়ারার্কির অন্যান্য লেয়ারগুলোর কখনোই সরাসরি ডেটা সোর্স অ্যাক্সেস করা উচিত নয়; ডেটা লেয়ারের এন্ট্রি পয়েন্ট সবসময় রিপোজিটরি ক্লাসগুলোই হয়ে থাকে। স্টেট হোল্ডার ক্লাস ( ইউআই লেয়ার গাইড দেখুন) বা ইউজ কেস ক্লাস ( ডোমেইন লেয়ার গাইড দেখুন)-এর সরাসরি ডিপেন্ডেন্সি হিসেবে কখনোই কোনো ডেটা সোর্স থাকা উচিত নয়। এন্ট্রি পয়েন্ট হিসেবে রিপোজিটরি ক্লাস ব্যবহার করলে আর্কিটেকচারের বিভিন্ন লেয়ার স্বাধীনভাবে স্কেল করতে পারে।

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

ডিপেন্ডেন্সি ইনজেকশনের সর্বোত্তম অনুশীলন অনুসরণ করে, রিপোজিটরিটি তার কনস্ট্রাক্টরে ডেটা সোর্সগুলোকে ডিপেন্ডেন্সি হিসেবে গ্রহণ করে:

class ExampleRepository(
    private val exampleRemoteDataSource: ExampleRemoteDataSource, // network
    private val exampleLocalDataSource: ExampleLocalDataSource // database
) { /* ... */ }

এপিআই প্রকাশ করুন

ডেটা লেয়ারের ক্লাসগুলো সাধারণত এককালীন Create, Read, Update এবং Delete (CRUD) কল সম্পাদন করার জন্য অথবা সময়ের সাথে সাথে ডেটার পরিবর্তন সম্পর্কে অবহিত হওয়ার জন্য ফাংশন সরবরাহ করে। এই প্রতিটি ক্ষেত্রের জন্য ডেটা লেয়ারে নিম্নলিখিত বিষয়গুলো থাকা উচিত:

  • এককালীন অপারেশনের জন্য সাসপেন্ড ফাংশনগুলো উন্মুক্ত করুন।
  • সময়ের সাথে সাথে ডেটার পরিবর্তন সম্পর্কে অবহিত হতে , ফ্লোগুলো প্রকাশ করুন।
class ExampleRepository(
    private val exampleRemoteDataSource: ExampleRemoteDataSource, // network
    private val exampleLocalDataSource: ExampleLocalDataSource // database
) {

    val data: Flow<Example> = ...

    suspend fun modifyData(example: Example) { ... }
}

এই নির্দেশিকায় নামকরণের নিয়মাবলী

এই নির্দেশিকায়, রিপোজিটরি ক্লাসগুলোর নামকরণ করা হয় সেই ডেটার নামানুসারে যার জন্য সেগুলো দায়বদ্ধ। প্রচলিত রীতিটি নিম্নরূপ:

ডেটার ধরণ + রিপোজিটরি

উদাহরণস্বরূপ: NewsRepository , MoviesRepository , বা PaymentsRepository

ডেটা সোর্স ক্লাসগুলোর নামকরণ করা হয় সেই ডেটার নামানুসারে যার জন্য তারা দায়ী এবং যে উৎস তারা ব্যবহার করে তার নামানুসারে। প্রচলিত নিয়মটি নিম্নরূপ:

ডেটার প্রকার + উৎসের প্রকার + ডেটা উৎস

ডেটার ধরনের জন্য, আরও সাধারণ হতে Remote বা Local ব্যবহার করুন, কারণ ইমপ্লিমেন্টেশন পরিবর্তিত হতে পারে। উদাহরণস্বরূপ: NewsRemoteDataSource বা NewsLocalDataSource । যদি সোর্সটি গুরুত্বপূর্ণ হয় এবং আরও সুনির্দিষ্ট হতে চান, তবে সোর্সের ধরন ব্যবহার করুন। উদাহরণস্বরূপ: NewsNetworkDataSource বা NewsDiskDataSource

কোনো ইমপ্লিমেন্টেশন ডিটেইলের উপর ভিত্তি করে ডেটা সোর্সের নামকরণ করবেন না—উদাহরণস্বরূপ, UserSharedPreferencesDataSource কারণ যে রিপোজিটরিগুলো সেই ডেটা সোর্স ব্যবহার করে, তাদের জানা উচিত নয় যে ডেটা কীভাবে সংরক্ষিত হয়। আপনি যদি এই নিয়মটি অনুসরণ করেন, তাহলে যে লেয়ারটি সেই সোর্সকে কল করে, তার উপর কোনো প্রভাব না ফেলেই আপনি ডেটা সোর্সের ইমপ্লিমেন্টেশন পরিবর্তন করতে পারবেন (উদাহরণস্বরূপ, SharedPreferences থেকে DataStore- এ মাইগ্রেট করা)।

একাধিক স্তরের সংগ্রহস্থল

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

উদাহরণস্বরূপ, ব্যবহারকারীর প্রমাণীকরণ ডেটা পরিচালনা করে এমন একটি রিপোজিটরি, UserRepository , তার প্রয়োজনীয়তা পূরণের জন্য LoginRepository এবং RegistrationRepository মতো অন্যান্য রিপোজিটরিগুলোর উপর নির্ভর করতে পারে।

উদাহরণটিতে, UserRepository অন্য দুটি রিপোজিটরি ক্লাসের উপর নির্ভরশীল: LoginRepository, যা অন্যান্য লগইন ডেটা সোর্সের উপর নির্ভরশীল; এবং RegistrationRepository, যা অন্যান্য রেজিস্ট্রেশন ডেটা সোর্সের উপর নির্ভরশীল।
চিত্র ২. একটি রিপোজিটরির নির্ভরতা গ্রাফ, যা অন্যান্য রিপোজিটরির উপর নির্ভরশীল।

সত্যের উৎস

প্রতিটি রিপোজিটরির জন্য তথ্যের একটি একক নির্ভরযোগ্য উৎস নির্ধারণ করা গুরুত্বপূর্ণ। এই নির্ভরযোগ্য উৎসে সর্বদা সামঞ্জস্যপূর্ণ, সঠিক এবং হালনাগাদ ডেটা থাকে। প্রকৃতপক্ষে, রিপোজিটরি থেকে প্রকাশিত ডেটা সর্বদা সরাসরি নির্ভরযোগ্য উৎস থেকে আসা ডেটাই হওয়া উচিত।

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

আপনার অ্যাপের বিভিন্ন রিপোজিটরির তথ্যের উৎস ভিন্ন হতে পারে। উদাহরণস্বরূপ, LoginRepository ক্লাসটি তার ক্যাশে-কে তথ্যের উৎস হিসেবে ব্যবহার করতে পারে এবং PaymentsRepository ক্লাসটি নেটওয়ার্ক ডেটা সোর্স ব্যবহার করতে পারে।

অফলাইন-ফার্স্ট সাপোর্ট প্রদানের জন্য, একটি স্থানীয় ডেটা উৎস—যেমন একটি ডেটাবেস—হলো তথ্যের প্রস্তাবিত নির্ভরযোগ্য উৎস

থ্রেডিং

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

উল্লেখ্য যে, বেশিরভাগ ডেটা সোর্স ইতিমধ্যেই মেইন-সেফ এপিআই (main-safe APIs) প্রদান করে, যেমন Room , Retrofit বা Ktor- এর সাসপেন্ড (suspend) মেথড কল। এই এপিআইগুলো উপলব্ধ হলে আপনার রিপোজিটরি সেগুলোর সুবিধা নিতে পারবে।

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

জীবনচক্র

ডেটা লেয়ারের ক্লাসগুলোর ইনস্ট্যান্স মেমরিতে ততক্ষণ পর্যন্ত থাকে, যতক্ষণ সেগুলো গার্বেজ কালেকশন রুট থেকে অ্যাক্সেসযোগ্য থাকে—সাধারণত আপনার অ্যাপের অন্যান্য অবজেক্ট থেকে রেফারেন্স করার মাধ্যমে।

যদি কোনো ক্লাসে ইন-মেমরি ডেটা থাকে—যেমন, একটি ক্যাশ—তবে আপনি একটি নির্দিষ্ট সময়ের জন্য সেই ক্লাসের একই ইনস্ট্যান্স পুনরায় ব্যবহার করতে চাইতে পারেন। এটিকে ক্লাস ইনস্ট্যান্সের লাইফসাইকেলও বলা হয়।

যদি কোনো ক্লাসের দায়িত্ব পুরো অ্যাপ্লিকেশনের জন্য অত্যন্ত গুরুত্বপূর্ণ হয়, তাহলে আপনি সেই ক্লাসের একটি ইনস্ট্যান্সকে Application ক্লাসের আওতায় আনতে পারেন। এর ফলে ইনস্ট্যান্সটি অ্যাপ্লিকেশনের লাইফসাইকেল অনুসরণ করে। বিকল্পভাবে, যদি আপনার অ্যাপের কোনো নির্দিষ্ট ফ্লো-তে—যেমন রেজিস্ট্রেশন বা লগইন ফ্লো—একই ইনস্ট্যান্স পুনরায় ব্যবহার করার প্রয়োজন হয়, তাহলে ইনস্ট্যান্সটিকে সেই ক্লাসের আওতায় আনা উচিত যা ওই ফ্লো-র লাইফসাইকেলের দায়িত্বে রয়েছে। উদাহরণস্বরূপ, আপনি ইন-মেমরি ডেটা ধারণকারী একটি RegistrationRepository RegistrationActivity আওতায়, অথবা NavEntryDecorator ব্যবহার করে কোনো ব্যাকস্ট্যাকের আওতায় আনতে পারেন।

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

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

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

উদাহরণস্বরূপ, এমন একটি নিউজ এপিআই সার্ভারের কথা ভাবুন যা শুধু আর্টিকেলের তথ্যই নয়, বরং সম্পাদনার ইতিহাস, ব্যবহারকারীর মন্তব্য এবং কিছু মেটাডেটাও ফেরত দেয়:

data class ArticleApiModel(
    val id: Long,
    val title: String,
    val content: String,
    val publicationDate: Date,
    val modifications: Array<ArticleApiModel>,
    val comments: Array<CommentApiModel>,
    val lastModificationDate: Date,
    val authorId: Long,
    val authorName: String,
    val authorDateOfBirth: Date,
    val readTimeMin: Int
)

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

data class Article(
    val id: Long,
    val title: String,
    val content: String,
    val publicationDate: Date,
    val authorName: String,
    val readTimeMin: Int
)

মডেল ক্লাস আলাদা করা নিম্নলিখিত উপায়ে উপকারী:

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

আপনি এই পদ্ধতিটি আরও প্রসারিত করতে পারেন এবং আপনার অ্যাপ আর্কিটেকচারের অন্যান্য অংশেও আলাদা মডেল ক্লাস সংজ্ঞায়িত করতে পারেন—উদাহরণস্বরূপ, ডেটা সোর্স ক্লাস এবং ভিউমডেল-এ। তবে, এর জন্য আপনাকে অতিরিক্ত ক্লাস এবং লজিক সংজ্ঞায়িত করতে হবে, যা আপনার যথাযথভাবে ডকুমেন্ট এবং টেস্ট করা উচিত। ন্যূনতমপক্ষে, এটি সুপারিশ করা হয় যে, যখনই কোনো ডেটা সোর্স এমন ডেটা গ্রহণ করে যা আপনার অ্যাপের বাকি অংশের প্রত্যাশার সাথে মেলে না, তখন নতুন মডেল তৈরি করুন।

ডেটা অপারেশনের প্রকারভেদ

ডেটা লেয়ার গুরুত্বের ভিত্তিতে বিভিন্ন ধরণের অপারেশন পরিচালনা করতে পারে, যেমন—ইউআই-ভিত্তিক, অ্যাপ-ভিত্তিক এবং ব্যবসা-ভিত্তিক অপারেশন।

UI-ভিত্তিক অপারেশন

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

UI-ভিত্তিক অপারেশনগুলো সাধারণত UI লেয়ার দ্বারা ট্রিগার হয় এবং কলারের লাইফসাইকেল অনুসরণ করে—উদাহরণস্বরূপ, ViewModel-এর লাইফসাইকেল। UI-ভিত্তিক অপারেশনের একটি উদাহরণের জন্য "Make a network request" সেকশনটি দেখুন।

অ্যাপ-ভিত্তিক কার্যক্রম

অ্যাপটি খোলা থাকা পর্যন্ত অ্যাপ-ভিত্তিক অপারেশনগুলো প্রাসঙ্গিক থাকে। অ্যাপটি বন্ধ করা হলে বা প্রসেসটি কিল করা হলে, এই অপারেশনগুলো বাতিল হয়ে যায়। একটি উদাহরণ হলো নেটওয়ার্ক রিকোয়েস্টের ফলাফল ক্যাশ করে রাখা, যাতে পরে প্রয়োজনে তা ব্যবহার করা যায়। এ বিষয়ে আরও জানতে ‘ইমপ্লিমেন্ট ইন-মেমরি ডেটা ক্যাশিং’ অংশটি দেখুন।

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

ব্যবসায়-ভিত্তিক কার্যক্রম

ব্যবসায়িক কার্যক্রম বাতিল করা যায় না। প্রক্রিয়াগত মৃত্যু ঘটলেও সেগুলোকে টিকে থাকতে হবে। উদাহরণস্বরূপ, ব্যবহারকারীর প্রোফাইলে পোস্ট করতে চাওয়া একটি ছবির আপলোড সম্পন্ন করা।

ব্যবসায়িক কার্যক্রমের জন্য WorkManager ব্যবহার করার পরামর্শ দেওয়া হয়। আরও জানতে ‘Schedule tasks using WorkManager’ অংশটি দেখুন।

ত্রুটি প্রকাশ করুন

রিপোজিটরি এবং ডেটার উৎসের সাথে ইন্টারঅ্যাকশন সফল হতে পারে অথবা কোনো ব্যর্থতা ঘটলে একটি এক্সেপশন থ্রো করতে পারে। কো-রুটিন এবং ফ্লো-এর জন্য, আপনার কোটলিনের বিল্ট-ইন এরর-হ্যান্ডলিং মেকানিজম ব্যবহার করা উচিত। সাসপেন্ড ফাংশনের কারণে যে এররগুলো হতে পারে, সেগুলোর জন্য প্রয়োজন অনুযায়ী try/catch ব্লক ব্যবহার করুন; এবং ফ্লো-এর ক্ষেত্রে catch অপারেটর ব্যবহার করুন। এই পদ্ধতিতে, ডেটা লেয়ারকে কল করার সময় UI লেয়ারই এক্সেপশনগুলো হ্যান্ডেল করবে বলে আশা করা হয়।

ডেটা লেয়ার বিভিন্ন ধরনের ত্রুটি বুঝতে ও সামলাতে পারে এবং কাস্টম এক্সেপশন ব্যবহার করে সেগুলো প্রকাশ করতে পারে—উদাহরণস্বরূপ, একটি UserNotAuthenticatedException

কো-রুটিনে ত্রুটি সম্পর্কে আরও জানতে, “ Exceptions in coroutines” ব্লগ পোস্টটি দেখুন।

সাধারণ কাজগুলি

নিম্নলিখিত বিভাগগুলিতে অ্যান্ড্রয়েড অ্যাপে প্রচলিত কিছু নির্দিষ্ট কাজ সম্পাদনের জন্য ডেটা লেয়ার কীভাবে ব্যবহার ও ডিজাইন করতে হয় তার উদাহরণ উপস্থাপন করা হয়েছে। উদাহরণগুলি এই গাইডের শুরুতে উল্লিখিত সাধারণ নিউজ অ্যাপটির উপর ভিত্তি করে তৈরি।

একটি নেটওয়ার্ক অনুরোধ করুন

একটি নেটওয়ার্ক রিকোয়েস্ট পাঠানো অ্যান্ড্রয়েড অ্যাপের অন্যতম সাধারণ একটি কাজ। নিউজ অ্যাপটির কাজ হলো নেটওয়ার্ক থেকে সংগৃহীত সর্বশেষ খবর ব্যবহারকারীকে দেখানো। এজন্য, নেটওয়ার্ক অপারেশন পরিচালনার জন্য অ্যাপটির একটি ডেটা সোর্স ক্লাস প্রয়োজন: NewsRemoteDataSource । অ্যাপের বাকি অংশে তথ্যটি পৌঁছে দেওয়ার জন্য, নিউজ ডেটার ওপর অপারেশন পরিচালনার জন্য একটি নতুন রিপোজিটরি তৈরি করা হয়: NewsRepository

প্রয়োজনীয়তাটি হলো, ব্যবহারকারী যখনই স্ক্রিনটি খুলবেন, তখনই সর্বশেষ খবরটি আপডেট হতে হবে। সুতরাং, এটি একটি UI-ভিত্তিক কার্যক্রম

ডেটা উৎস তৈরি করুন

ডেটা সোর্সটিতে এমন একটি ফাংশন থাকা প্রয়োজন যা সর্বশেষ সংবাদ ফেরত দেবে: ArticleHeadline ইনস্ট্যান্সগুলোর একটি তালিকা। ডেটা সোর্সটিকে নেটওয়ার্ক থেকে সর্বশেষ সংবাদ পাওয়ার জন্য একটি মেইন-সেফ উপায় প্রদান করতে হবে। এর জন্য, কাজটি চালানোর জন্য এটিকে CoroutineDispatcher বা Executor উপর নির্ভর করতে হবে।

নেটওয়ার্ক অনুরোধ করা একটি এককালীন কল, যা নতুন fetchLatestNews() মেথড দ্বারা পরিচালিত হয়:

class NewsRemoteDataSource(
  private val newsApi: NewsApi,
  private val ioDispatcher: CoroutineDispatcher
) {
    /**
     * Fetches the latest news from the network and returns the result.
     * This executes on an IO-optimized thread pool, the function is main-safe.
     */
    suspend fun fetchLatestNews(): List<ArticleHeadline> =
        // Move the execution to an IO-optimized thread since the ApiService
        // doesn't support coroutines and makes synchronous requests.
        withContext(ioDispatcher) {
            newsApi.fetchLatestNews()
        }
    }

// Makes news-related network synchronous requests.
interface NewsApi {
    fun fetchLatestNews(): List<ArticleHeadline>
}

NewsApi ইন্টারফেসটি নেটওয়ার্ক এপিআই ক্লায়েন্টের ইমপ্লিমেন্টেশনকে গোপন রাখে; ইন্টারফেসটি Retrofit বা HttpURLConnection দ্বারা সমর্থিত কিনা তাতে কোনো পার্থক্য হয় না। ইন্টারফেসের উপর নির্ভর করার ফলে আপনার অ্যাপে এপিআই ইমপ্লিমেন্টেশনগুলো অদলবদলযোগ্য হয়ে ওঠে।

রিপোজিটরি তৈরি করুন

যেহেতু এই কাজের জন্য রিপোজিটরি ক্লাসে কোনো অতিরিক্ত লজিকের প্রয়োজন হয় না, তাই NewsRepository নেটওয়ার্ক ডেটা সোর্সের প্রক্সি হিসেবে কাজ করে। অ্যাবস্ট্রাকশনের এই অতিরিক্ত স্তরটি যুক্ত করার সুবিধাগুলো ইন-মেমরি ক্যাশিং বিভাগে ব্যাখ্যা করা হয়েছে।

// NewsRepository is consumed from other layers of the hierarchy.
class NewsRepository(
    private val newsRemoteDataSource: NewsRemoteDataSource
) {
    suspend fun fetchLatestNews(): List<ArticleHeadline> =
        newsRemoteDataSource.fetchLatestNews()
}

UI লেয়ার থেকে সরাসরি রিপোজিটরি ক্লাস কীভাবে ব্যবহার করতে হয় তা জানতে, UI লেয়ার গাইডটি দেখুন।

ইন-মেমরি ডেটা ক্যাশিং বাস্তবায়ন করুন

ধরা যাক, নিউজ অ্যাপের জন্য একটি নতুন আবশ্যকতা আনা হয়েছে: ব্যবহারকারী যখন স্ক্রিনটি খুলবেন, তখন পূর্বে কোনো অনুরোধ করা হয়ে থাকলে ক্যাশ করা খবর তাকে দেখাতে হবে। অন্যথায়, সর্বশেষ খবর আনার জন্য অ্যাপটিকে একটি নেটওয়ার্ক অনুরোধ করতে হবে।

নতুন আবশ্যকতা অনুসারে, ব্যবহারকারী যতক্ষণ অ্যাপটি খোলা রাখবেন, ততক্ষণ এটিকে সর্বশেষ খবর মেমরিতে সংরক্ষণ করতে হবে। সুতরাং, এটি একটি অ্যাপ-ভিত্তিক কার্যক্রম

ক্যাশে

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

নেটওয়ার্ক অনুরোধের ফলাফল ক্যাশ করুন

সরলতার জন্য, NewsRepository সর্বশেষ খবর ক্যাশ করতে একটি পরিবর্তনযোগ্য ভেরিয়েবল ব্যবহার করে। বিভিন্ন থ্রেড থেকে রিড এবং রাইট সুরক্ষিত রাখতে একটি Mutex ব্যবহার করা হয়। শেয়ার্ড মিউটেবল স্টেট এবং কনকারেন্সি সম্পর্কে আরও জানতে, কোটলিন ডকুমেন্টেশন দেখুন।

নিম্নলিখিত বাস্তবায়নটি রিপোজিটরির একটি ভেরিয়েবলে সর্বশেষ খবরের তথ্য ক্যাশ করে রাখে, যা একটি Mutex দ্বারা রাইট-প্রোটেক্টেড। যদি নেটওয়ার্ক অনুরোধের ফলাফল সফল হয়, তবে ডেটাটি latestNews ভেরিয়েবলে অ্যাসাইন করা হয়।

class NewsRepository(
  private val newsRemoteDataSource: NewsRemoteDataSource
) {
    // Mutex to make writes to cached values thread-safe.
    private val latestNewsMutex = Mutex()

    // Cache of the latest news got from the network.
    private var latestNews: List<ArticleHeadline> = emptyList()

    suspend fun getLatestNews(refresh: Boolean = false): List<ArticleHeadline> {
        if (refresh || latestNews.isEmpty()) {
            val networkResult = newsRemoteDataSource.fetchLatestNews()
            // Thread-safe write to latestNews
            latestNewsMutex.withLock {
                this.latestNews = networkResult
            }
        }

        return latestNewsMutex.withLock { this.latestNews }
    }
}

একটি অপারেশনকে স্ক্রিনের চেয়ে বেশি সময় ধরে সচল রাখুন।

নেটওয়ার্ক রিকোয়েস্ট চলাকালীন যদি ব্যবহারকারী স্ক্রিন থেকে অন্য কোথাও চলে যান, তাহলে রিকোয়েস্টটি বাতিল হয়ে যাবে এবং এর ফলাফল ক্যাশ করা হবে না। এই লজিকটি সম্পাদনের জন্য NewsRepository কলারের CoroutineScope ব্যবহার করা উচিত নয়। এর পরিবর্তে, NewsRepository এমন একটি CoroutineScope ব্যবহার করা উচিত যা তার নিজস্ব লাইফসাইকেলের সাথে সংযুক্ত। সর্বশেষ খবর সংগ্রহ করা একটি অ্যাপ-ভিত্তিক অপারেশন হওয়া প্রয়োজন।

ডিপেন্ডেন্সি ইনজেকশনের সর্বোত্তম অনুশীলন অনুসরণ করতে, NewsRepository এর নিজস্ব CoroutineScope তৈরি করার পরিবর্তে এর কনস্ট্রাক্টরে একটি প্যারামিটার হিসেবে স্কোপ গ্রহণ করা উচিত। যেহেতু রিপোজিটরিগুলোর বেশিরভাগ কাজ ব্যাকগ্রাউন্ড থ্রেডে করা উচিত, তাই আপনার CoroutineScope Dispatchers.Default অথবা আপনার নিজস্ব থ্রেড পুল দিয়ে কনফিগার করা উচিত।

class NewsRepository(
    ...,
    // This could be CoroutineScope(SupervisorJob() + Dispatchers.Default).
    private val externalScope: CoroutineScope
) { ... }

যেহেতু NewsRepository বাহ্যিক CoroutineScope সাথে অ্যাপ-ভিত্তিক অপারেশন সম্পাদন করার জন্য প্রস্তুত, তাই এটিকে অবশ্যই ডেটা সোর্সে কল করতে হবে এবং সেই স্কোপ দ্বারা শুরু করা একটি নতুন কোরাউটিনের মাধ্যমে এর ফলাফল সংরক্ষণ করতে হবে:

class NewsRepository(
    private val newsRemoteDataSource: NewsRemoteDataSource,
    private val externalScope: CoroutineScope
) {
    /* ... */

    suspend fun getLatestNews(refresh: Boolean = false): List<ArticleHeadline> {
        return if (refresh) {
            externalScope.async {
                newsRemoteDataSource.fetchLatestNews().also { networkResult ->
                    // Thread-safe write to latestNews.
                    latestNewsMutex.withLock {
                        latestNews = networkResult
                    }
                }
            }.await()
        } else {
            return latestNewsMutex.withLock { this.latestNews }
        }
    }
}

এক্সটার্নাল স্কোপে কো-রুটিন শুরু করার জন্য async ব্যবহার করা হয়। নেটওয়ার্ক রিকোয়েস্ট ফিরে আসা এবং ফলাফল ক্যাশে সেভ না হওয়া পর্যন্ত নতুন কো-রুটিনটিকে সাসপেন্ড করে রাখার জন্য await কল করা হয়। যদি ততক্ষণে ব্যবহারকারী স্ক্রিনেই থাকেন, তবে তিনি সর্বশেষ খবর দেখতে পাবেন; আর যদি ব্যবহারকারী স্ক্রিন থেকে সরে যান, তাহলে await বাতিল হয়ে যায়, কিন্তু async ভেতরের লজিক চলতে থাকে।

CoroutineScope এর প্যাটার্ন সম্পর্কে আরও পড়ুন।

ডিস্ক থেকে ডেটা সংরক্ষণ এবং পুনরুদ্ধার করুন

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

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

  • যেসব বৃহৎ ডেটাসেট কোয়েরি করার, রেফারেন্সিয়াল ইন্টিগ্রিটি বজায় রাখার বা আংশিক আপডেট করার প্রয়োজন হয়, সেগুলোর ডেটা একটি Room ডেটাবেসে সংরক্ষণ করুন। নিউজ অ্যাপের উদাহরণে, সংবাদ নিবন্ধ বা লেখকদের ডেটাবেসে সংরক্ষণ করা যেতে পারে।
  • ছোট ডেটাসেটের জন্য, যেগুলোকে শুধু পুনরুদ্ধার এবং সেট করার প্রয়োজন হয় (আংশিকভাবে কোয়েরি বা আপডেট করার প্রয়োজন হয় না), DataStore ব্যবহার করুন। নিউজ অ্যাপের উদাহরণে, ব্যবহারকারীর পছন্দের তারিখের ফরম্যাট বা অন্যান্য ডিসপ্লে পছন্দ DataStore-এ সংরক্ষণ করা যেতে পারে।
  • JSON অবজেক্টের মতো ডেটার খণ্ডাংশের জন্য একটি ফাইল ব্যবহার করুন।

‘সত্যের উৎস’ বিভাগে যেমন উল্লেখ করা হয়েছে, প্রতিটি ডেটা সোর্স শুধুমাত্র একটি উৎসের সাথেই কাজ করে এবং একটি নির্দিষ্ট ডেটা টাইপের (যেমন, News , Authors , NewsAndAuthors , বা UserPreferences ) সাথে সঙ্গতিপূর্ণ থাকে। যে ক্লাসগুলো ডেটা সোর্স ব্যবহার করে, তাদের জানা উচিত নয় যে ডেটা কীভাবে সংরক্ষিত আছে—উদাহরণস্বরূপ, ডেটাবেসে নাকি কোনো ফাইলে।

ডেটা উৎস হিসেবে রুম

যেহেতু প্রতিটি ডেটা সোর্সের একটি নির্দিষ্ট ধরণের ডেটার জন্য শুধুমাত্র একটি সোর্সের সাথে কাজ করার দায়িত্ব থাকা উচিত, তাই একটি Room ডেটা সোর্স প্যারামিটার হিসেবে একটি ডেটা অ্যাক্সেস অবজেক্ট (DAO) অথবা স্বয়ং ডাটাবেসটি গ্রহণ করবে। উদাহরণস্বরূপ, NewsLocalDataSource প্যারামিটার হিসেবে NewsDao এর একটি ইনস্ট্যান্স নিতে পারে, এবং AuthorsLocalDataSource প্যারামিটার হিসেবে AuthorsDao এর একটি ইনস্ট্যান্স নিতে পারে।

কিছু ক্ষেত্রে, যদি কোনো অতিরিক্ত লজিকের প্রয়োজন না হয়, তাহলে আপনি সরাসরি রিপোজিটরিতে DAO ইনজেক্ট করতে পারেন, কারণ DAO হলো এমন একটি ইন্টারফেস যা টেস্টের মধ্যে সহজেই প্রতিস্থাপন করা যায়।

Room API-গুলো নিয়ে কাজ করার বিষয়ে আরও জানতে, Room গাইডগুলো দেখুন।

ডেটা উৎস হিসেবে ডেটাস্টোর

ব্যবহারকারীর সেটিংসের মতো কী-ভ্যালু পেয়ার সংরক্ষণের জন্য ডেটাস্টোর আদর্শ। এর উদাহরণ হতে পারে সময়ের ফরম্যাট, নোটিফিকেশন সংক্রান্ত পছন্দ এবং ব্যবহারকারী সংবাদ পড়ার পর তা দেখানো হবে কি না। ডেটাস্টোর প্রোটোকল বাফারের মাধ্যমে টাইপড অবজেক্টও সংরক্ষণ করতে পারে।

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

উদাহরণস্বরূপ, আপনার একটি NotificationsDataStore থাকতে পারে যা শুধুমাত্র নোটিফিকেশন-সম্পর্কিত প্রেফারেন্সগুলো পরিচালনা করে এবং একটি NewsPreferencesDataStore থাকতে পারে যা শুধুমাত্র নিউজ স্ক্রিন-সম্পর্কিত প্রেফারেন্সগুলো পরিচালনা করে। এভাবে, আপনি আপডেটগুলোকে আরও ভালোভাবে সীমাবদ্ধ করতে পারবেন, কারণ newsScreenPreferencesDataStore.data ফ্লোটি শুধুমাত্র তখনই ইমিট করে যখন সেই স্ক্রিন-সম্পর্কিত কোনো প্রেফারেন্স পরিবর্তিত হয়। এর আরও একটি সুবিধা হলো, অবজেক্টটির লাইফসাইকেল ছোট হতে পারে, কারণ এটি শুধুমাত্র নিউজ স্ক্রিনটি প্রদর্শিত থাকা পর্যন্তই টিকে থাকতে পারে।

DataStore API-গুলো নিয়ে কাজ করার বিষয়ে আরও জানতে, DataStore গাইডগুলো দেখুন।

ডেটা উৎস হিসেবে একটি ফাইল

JSON অবজেক্ট বা বিটম্যাপের মতো বড় অবজেক্ট নিয়ে কাজ করার সময়, আপনাকে একটি File অবজেক্ট ব্যবহার করতে হবে এবং থ্রেড পরিবর্তনের বিষয়টি সামলাতে হবে।

ফাইল স্টোরেজ নিয়ে কাজ করার বিষয়ে আরও জানতে, স্টোরেজ ওভারভিউ পৃষ্ঠাটি দেখুন।

WorkManager ব্যবহার করে টাস্ক শিডিউল করুন

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

WorkManager অ্যাসিঙ্ক্রোনাস ও নির্ভরযোগ্য কাজের সময়সূচী নির্ধারণ করা সহজ করে এবং কনস্ট্রেইন্ট ম্যানেজমেন্টের দায়িত্ব নিতে পারে। স্থায়ী কাজের জন্য এটিই প্রস্তাবিত লাইব্রেরি। উপরে বর্ণিত কাজটি সম্পাদন করার জন্য, RefreshLatestNewsWorker নামে একটি Worker ক্লাস তৈরি করা হয়েছে। এই ক্লাসটি সর্বশেষ সংবাদ সংগ্রহ করে ডিস্কে ক্যাশ করার জন্য NewsRepository একটি ডিপেন্ডেন্সি হিসেবে গ্রহণ করে।

class RefreshLatestNewsWorker(
    private val newsRepository: NewsRepository,
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result = try {
        newsRepository.refreshLatestNews()
        Result.success()
    } catch (error: Throwable) {
        Result.failure()
    }
}

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

এই উদাহরণে, এই সংবাদ-সম্পর্কিত টাস্কটিকে অবশ্যই NewsRepository থেকে কল করতে হবে, যা একটি নতুন ডেটা সোর্সকে ডিপেন্ডেন্সি হিসেবে গ্রহণ করবে, NewsTasksDataSource , যা নিম্নরূপে ইমপ্লিমেন্ট করা হয়েছে:

private const val REFRESH_RATE_HOURS = 4L
private const val FETCH_LATEST_NEWS_TASK = "FetchLatestNewsTask"
private const val TAG_FETCH_LATEST_NEWS = "FetchLatestNewsTaskTag"

class NewsTasksDataSource(
    private val workManager: WorkManager
) {
    fun fetchNewsPeriodically() {
        val fetchNewsRequest = PeriodicWorkRequestBuilder<RefreshLatestNewsWorker>(
            REFRESH_RATE_HOURS, TimeUnit.HOURS
        ).setConstraints(
            Constraints.Builder()
                .setRequiredNetworkType(NetworkType.TEMPORARILY_UNMETERED)
                .setRequiresCharging(true)
                .build()
        )
            .addTag(TAG_FETCH_LATEST_NEWS)

        workManager.enqueueUniquePeriodicWork(
            FETCH_LATEST_NEWS_TASK,
            ExistingPeriodicWorkPolicy.KEEP,
            fetchNewsRequest.build()
        )
    }

    fun cancelFetchingNewsPeriodically() {
        workManager.cancelAllWorkByTag(TAG_FETCH_LATEST_NEWS)
    }
}

এই ধরনের ক্লাসগুলোর নামকরণ করা হয় সেই ডেটার নামানুসারে যার জন্য তারা দায়ী—উদাহরণস্বরূপ, NewsTasksDataSource বা PaymentsTasksDataSource । একটি নির্দিষ্ট ধরনের ডেটার সাথে সম্পর্কিত সমস্ত টাস্ক একই ক্লাসের মধ্যে অন্তর্ভুক্ত করা উচিত।

অ্যাপ চালুর সময় টাস্কটি ট্রিগার করার প্রয়োজন হলে, App Startup লাইব্রেরি ব্যবহার করে WorkManager রিকোয়েস্টটি ট্রিগার করার পরামর্শ দেওয়া হয়, যা একটি Initializer থেকে রিপোজিটরিকে কল করে।

WorkManager API নিয়ে কাজ করার বিষয়ে আরও জানতে, WorkManager গাইডগুলো দেখুন।

পরীক্ষা

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

ইউনিট পরীক্ষা

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

ইন্টিগ্রেশন পরীক্ষা

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

ডাটাবেসের জন্য, Room একটি ইন-মেমরি ডাটাবেস তৈরি করার সুযোগ দেয়, যা আপনি আপনার টেস্টগুলোতে সম্পূর্ণরূপে নিয়ন্ত্রণ করতে পারেন। আরও জানতে, “আপনার ডাটাবেস টেস্ট ও ডিবাগ করুন” পৃষ্ঠাটি দেখুন।

নেটওয়ার্কিংয়ের জন্য WireMock বা MockWebServer- এর মতো জনপ্রিয় লাইব্রেরি রয়েছে, যেগুলো দিয়ে আপনি HTTP ও HTTPS কল নকল করতে পারেন এবং অনুরোধগুলো প্রত্যাশা অনুযায়ী করা হয়েছে কিনা তা যাচাই করতে পারেন।

অতিরিক্ত সম্পদ

নমুনা

{% হুবহু %} {% endverbatim %} {% হুবহু %} {% endverbatim %}