UI লেয়ার

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

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

সাধারণ আর্কিটেকচারে, UI লেয়ারের UI এলিমেন্ট স্টেট
    হোল্ডারের উপর নির্ভর করে, যা আবার ডেটা লেয়ার বা
    ঐচ্ছিক ডোমেন লেয়ারের ক্লাসগুলির উপর নির্ভর করে।
ছবি ১. অ্যাপ আর্কিটেকচারে UI লেয়ারের ভূমিকা।

একটি প্রাথমিক কেস স্টাডি

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

  • পড়তে উপলভ্য নিবন্ধ দেখুন।
  • বিভাগ অনুযায়ী নিবন্ধ ব্রাউজ করুন।
  • সাইন-ইন করে নির্দিষ্ট নিবন্ধ বুকমার্ক করুন।
  • উপযুক্ত হলে কিছু প্রিমিয়াম ফিচার অ্যাক্সেস করতে পারবেন।
একটি নমুনা খবরের অ্যাপে নিবন্ধের প্রিভিউ দেখানো হচ্ছে, যার মধ্যে একটি বুকমার্ক করা আছে।
ছবি ২. UI কেস স্টাডির জন্য খবরের অ্যাপের নমুনা।

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

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

UI বলতে কন্টেনার ও কম্পোজ করার উপযুক্ত ফাংশনের মতো UI এলিমেন্টকে বোঝায় যেগুলি ডেটা দেখায়। Android UI তৈরি করার জন্য, Jetpack Compose হল সাজেস্ট করা টুলকিট। ডেটা লেয়ার অ্যাপ ডেটা হোল্ড, ম্যানেজ ও অ্যাক্সেস প্রদান করে বলে, UI লেয়ারকে অবশ্যই নিম্নলিখিত ধাপগুলি সম্পূর্ণ করতে হবে:

  1. অ্যাপ ডেটা ব্যবহার করে এবং UI সহজে রেন্ডার করতে পারে এমন ডেটাতে তা পরিবর্তন করে।
  2. UI-তে রেন্ডার করা যায় এমন ডেটা ব্যবহার করে তা ব্যবহারকারীর কাছে দেখানোর জন্য UI এলিমেন্টে পরিবর্তন করে।
  3. সেইসব একত্রিত UI এলিমেন্ট থেকে ব্যবহারকারীর ইনপুট ইভেন্ট গ্রহণ করুন এবং প্রয়োজন অনুযায়ী UI ডেটাতে সেগুলির প্রভাব রিফ্লেক্ট করুন।
  4. যতক্ষণ প্রয়োজন, ১ থেকে ৩ নম্বর ধাপগুলি আবার করুন।

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

  • UI স্টেট কীভাবে নির্ধারণ করতে হয়
  • UI স্টেট তৈরি ও ম্যানেজ করার উপায় হিসেবে একমুখী ডেটা ফ্লো (UDF)
  • UDF নীতি অনুযায়ী কীভাবে অবজার্ভ করা যায় এমন ডেটা টাইপের মাধ্যমে UI স্টেট এক্সপোজ করা যায়
  • কীভাবে এমন UI প্রয়োগ করতে হয় যা অবজার্ভেবল UI স্টেট কনজিউম করে

এর মধ্যে সবচেয়ে মৌলিক হল UI স্টেটের সংজ্ঞা।

UI স্টেট সংজ্ঞায়িত করা

আগে উল্লেখ করা কেস স্টাডিতে, UI-তে প্রতিটি নিবন্ধের জন্য কিছু মেটাডেটা সহ নিবন্ধের একটি তালিকা দেখানো হয়। অ্যাপটি ব্যবহারকারীকে যেসব তথ্য দেখায়, সেগুলি হল UI স্টেট।

অন্যভাবে বলতে গেলে, UI হল যা ব্যবহারকারী দেখেন, UI স্টেট হল যা অ্যাপ বলে যে ব্যবহারকারীকে দেখানো উচিত। একই মুদ্রার দুটি দিকের মতো, UI হল UI স্টেটের ভিজ্যুয়াল প্রতিনিধিত্ব। UI স্টেটে কোনও পরিবর্তন হলে তা সঙ্গে সঙ্গে UI-তে দেখা যায়।

UI হল UI স্টেটের সাথে স্ক্রিনে UI এলিমেন্ট বাইন্ডিংয়ের ফলাফল।
ছবি ৩. UI হল স্ক্রিনে UI এলিমেন্টকে UI স্টেটের সাথে বাইন্ড করার ফলাফল।

কেস স্টাডি দেখুন: News অ্যাপের প্রয়োজনীয়তা পূরণ করতে, UI সম্পূর্ণ রেন্ডার করার জন্য প্রয়োজনীয় তথ্যকে নিম্নলিখিতভাবে সংজ্ঞায়িত করা NewsUiState ডেটা ক্লাসে এনক্যাপসুলেট করা যেতে পারে:

data class NewsUiState(
    val isSignedIn: Boolean = false,
    val isPremium: Boolean = false,
    val newsItems: List<NewsItemUiState> = listOf(),
    val userMessages: List<Message> = listOf()
)

data class NewsItemUiState(
    val title: String,
    val body: String,
    val bookmarked: Boolean = false,
    // ...
)

UI স্টেট সম্পর্কে আরও জানতে, স্টেট ও Jetpack Compose দেখুন।

পরিবর্তনযোগ্য নয়

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

যেমন, আগের কেস স্টাডিটি বিবেচনা করুন। UI state থেকে NewsItemUiState অবজেক্টে থাকা bookmarked ফ্ল্যাগটি Activity ক্লাসে আপডেট করা হলে, সেই ফ্ল্যাগটি কোনও নিবন্ধের বুকমার্ক করা স্ট্যাটাসের সোর্স হিসেবে ডেটা লেয়ারের সাথে প্রতিযোগিতা করে। এই ধরনের অসঙ্গতি প্রতিরোধ করার জন্য অপরিবর্তনীয় ডেটা ক্লাস খুবই উপযোগী।

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

এই নির্দেশিকায়, UI স্টেট ক্লাসের নাম দেওয়া হয়েছে সেই স্ক্রিন বা স্ক্রিনের অংশের কার্যকারিতার উপর ভিত্তি করে যেগুলি বর্ণনা করা হয়েছে। কনভেনশনটি হল:

কার্যকারিতা + UiState.

যেমন, খবর দেখানো স্ক্রিনের স্টেটকে NewsUiState এবং খবরের আইটেমের তালিকায় থাকা খবরের আইটেমের স্টেটকে NewsItemUiState বলা হতে পারে।

একমুখী ডেটা ফ্লোয়ের মাধ্যমে স্টেট ম্যানেজ করা

আগের বিভাগে এটি প্রতিষ্ঠিত হয়েছে যে UI স্টেট হল UI রেন্ডার করার জন্য প্রয়োজনীয় বিবরণের অপরিবর্তনীয় স্ন্যাপশট। তবে, অ্যাপে ডেটার ডায়নামিক নেচার বলতে বোঝায় যে সময়ের সাথে সাথে স্টেট পরিবর্তন হতে পারে। ব্যবহারকারীর ইন্টার‍্যাকশন বা অন্যান্য ইভেন্টের কারণে এটি হতে পারে, যা অ্যাপে ডেটা পূরণ করার জন্য ব্যবহৃত অন্তর্নিহিত ডেটা পরিবর্তন করে।

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

এই বিভাগে একমুখী ডেটা ফ্লো (UDF) নিয়ে আলোচনা করা হয়েছে, এটি একটি আর্কিটেকচার প্যাটার্ন যা দায়িত্বের এই সুস্থ বিভাজনকে এনফোর্স করতে সাহায্য করে।

স্টেট হোল্ডার

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

পরের ক্ষেত্রে, সাধারণ প্রয়োগ হল একটি ViewModel-এর ইনস্ট্যান্স, যদিও অ্যাপ্লিকেশনের প্রয়োজনীয়তার উপর নির্ভর করে, একটি সাধারণ ক্লাসই যথেষ্ট হতে পারে। কেস স্টাডি থেকে পাওয়া News অ্যাপ, যেমন, সেই বিভাগে দেখানো স্ক্রিনের জন্য UI স্টেট তৈরি করতে NewsViewModel ক্লাসকে স্টেট হোল্ডার হিসেবে ব্যবহার করে।

UI ও এর স্টেট প্রডিউসারের মধ্যে কোডিপেন্ডেন্সি মডেল করার অনেক উপায় আছে। তবে, UI এবং এর ViewModel ক্লাসের মধ্যে ইন্টার‍্যাকশনকে ইভেন্ট ইনপুট এবং এর ফলে হওয়া স্টেট আউটপুট হিসেবেই মূলত বোঝা যায়, তাই সম্পর্কটিকে নিম্নলিখিত ডায়াগ্রামে দেখানো হয়েছে:

অ্যাপ্লিকেশন ডেটা, ডেটা লেয়ার থেকে ViewModel-এ ফ্লো করে। UI স্টেট
    ViewModel থেকে UI এলিমেন্টে ফ্লো করে এবং ইভেন্ট UI
    এলিমেন্ট থেকে ViewModel-এ ফ্লো করে।
ছবি ৪. অ্যাপ আর্কিটেকচারে UDF কীভাবে কাজ করে তার ডায়াগ্রাম।

যে প্যাটার্নে স্টেট নিচের দিকে এবং ইভেন্ট উপরের দিকে ফ্লো করে, তাকে একমুখী ডেটা ফ্লো (UDF) বলা হয়। অ্যাপ আর্কিটেকচারের জন্য এই প্যাটার্নের প্রভাব নিচে উল্লেখ করা হল:

  • ViewModel, UI-এর ব্যবহার করার জন্য স্টেট হোল্ড করে এবং এক্সপোজ করে। UI state হল ViewModel-এর মাধ্যমে ট্রান্সফর্ম করা অ্যাপ্লিকেশন ডেটা।
  • UI, ব্যবহারকারীর ইভেন্ট সম্পর্কে ViewModel-কে বিজ্ঞপ্তি পাঠায়।
  • ViewModel ব্যবহারকারীর অ্যাকশন ম্যানেজ করে এবং স্টেট আপডেট করে।
  • রেন্ডার করার জন্য আপডেট করা স্ট্যাটাস UI-তে ফিড করা হয়।
  • যেসব ইভেন্ট স্টেট মিউটেশন ঘটায় সেগুলির ক্ষেত্রে উপরে উল্লেখ করা প্রসেসটি রিপিট করা হয়।

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

কেস স্টাডি অ্যাপ থেকে একটি নিবন্ধ আইটেম। UI-তে একটি থাম্বনেল, নিবন্ধের শিরোনাম, লেখক, নিবন্ধটি পড়ার আনুমানিক সময় এবং একটি বুকমার্ক আইকন দেখানো হয়।
ছবি ৫. কেস স্টাডি অ্যাপে নিবন্ধ আইটেমের UI.

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

ব্যবহারকারী কোনও নিবন্ধ বুকমার্ক করলে UI ইভেন্ট ঘটে। ViewModel
    স্টেট পরিবর্তন সম্পর্কে ডেটা লেয়ারকে বিজ্ঞপ্তি পাঠায়। ডেটা লেয়ার ডেটা পরিবর্তন
    সংরক্ষণ করে এবং অ্যাপ্লিকেশন ডেটা আপডেট করে। বুকমার্ক করা নিবন্ধ সহ নতুন অ্যাপ ডেটা
    ViewModel-এ পাস করা হয়, যা তারপরে নতুন UI স্টেট
    তৈরি করে এবং তা দেখানোর জন্য UI এলিমেন্টে পাস করে।
ছবি ৬. UDF-এ ইভেন্ট ও ডেটার চক্রের চিত্র সহ ডায়াগ্রাম।

নিম্নলিখিত বিভাগে, স্টেট পরিবর্তনকারী ইভেন্টগুলি এবং UDF ব্যবহার করে সেগুলি কীভাবে প্রসেস করা যায় তা আরও বিশদে দেখানো হয়েছে।

লজিকের ধরন

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

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

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

UI তৈরি করতে সাহায্য করার প্রেক্ষিতে স্টেট হোল্ডার কীভাবে কাজ করে সেই সম্পর্কে আরও জানতে, Jetpack Compose State গাইড দেখুন।

UDF কেন ব্যবহার করবেন?

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

অন্যভাবে বলতে গেলে, UDF নিম্নলিখিত বিষয়গুলির জন্য অনুমতি দেয়:

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

UI স্টেট এক্সপোজ করা

UI স্টেট নির্ধারণ করার পরে এবং কীভাবে সেই স্টেটের প্রোডাকশন ম্যানেজ করবেন তা ঠিক করার পরে পরবর্তী ধাপ হল, UI-তে প্রোডিউস করা স্টেট দেখানো।

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

class NewsViewModel(
    // ...
) : ViewModel() {

    val uiState: NewsUiState = /* ... */
}

Kotlin ফ্লো সম্পর্কে প্রাথমিক ধারণা পেতে, Android-এ Kotlin ফ্লো দেখুন। StateFlow-কে কীভাবে অবজার্ভেবল ডেটা হোল্ডার হিসেবে ব্যবহার করতে হয় তা জানতে, Jetpack Compose-এ উন্নত স্টেট ও সাইড এফেক্ট কোডল্যাব দেখুন।

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

UiState স্ট্রিম তৈরি করার একটি সাধারণ উপায় হল mutableStateOf private set সহ প্রপার্টি এক্সপোজ করা, ViewModel-এর মধ্যে স্টেট পরিবর্তনযোগ্য রাখা কিন্তু UI-এর জন্য শুধু-পঠনযোগ্য রাখা।

class NewsViewModel(
    // ...
) : ViewModel() {

    var uiState by mutableStateOf(NewsUiState())
        private set

    // ...
}

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

class NewsViewModel(
    private val repository: NewsRepository,
    // ...
) : ViewModel() {

    var uiState by mutableStateOf(NewsUiState())
        private set

    private var fetchJob: Job? = null

    fun fetchArticles(category: String) {
        fetchJob?.cancel()
        fetchJob = viewModelScope.launch {
            try {
                val newsItems = repository.newsItemsForCategory(category)
                uiState = uiState.copy(newsItems = newsItems)
            } catch (ioe: IOException) {
                // Handle the error and notify the UI when appropriate.
                val messages = getMessagesFromThrowable(ioe)
                uiState = uiState.copy(userMessages = messages)
            }
        }
    }
}

আগের উদাহরণে, NewsViewModel ক্লাস একটি নির্দিষ্ট বিভাগের জন্য নিবন্ধ ফেচ করার চেষ্টা করে এবং তারপরে UI স্টেটে প্রচেষ্টার ফলাফল—সফল বা ব্যর্থ—প্রতিফলিত করে, যেখানে UI এটিতে যথাযথভাবে প্রতিক্রিয়া জানাতে পারে। সমস্যা সমাধান সম্পর্কে আরও জানতে, স্ক্রিনে সমস্যা দেখুন বিভাগটি দেখুন।

বিবেচনায় রাখার মতো আরও জিনিস

আগের নির্দেশিকা ছাড়াও, UI state এক্সপোজ করার সময় নিম্নলিখিত বিষয়গুলি বিবেচনা করুন:

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

    data class NewsUiState(
        val isSignedIn: Boolean = false,
        val isPremium: Boolean = false,
        val newsItems: List<NewsItemUiState> = listOf()
    )
    
    val NewsUiState.canBookmarkNews: Boolean get() = isSignedIn && isPremium

    এই ঘোষণায়, বুকমার্ক বোতামের দৃশ্যমানতা হল দুটি অন্যান্য প্রপার্টির থেকে প্রাপ্ত প্রপার্টি। বিজনেস লজিক যত জটিল হতে থাকে, ততই এমন একটি UiState ক্লাস থাকা গুরুত্বপূর্ণ হয়ে ওঠে যেখানে সব প্রপার্টি অবিলম্বে উপলভ্য থাকে।

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

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

    • UiState ডিফারেন্সিং: UiState অবজেক্টে যত বেশি ফিল্ড থাকবে, সেটির কোনও একটি ফিল্ড আপডেট হওয়ার ফলে স্ট্রিম এমিট হওয়ার সম্ভাবনা তত বেশি থাকবে। কারণ, UI এলিমেন্টে এমন কোনও ডিফারেন্স মেকানিজম নেই যা বোঝাতে পারে যে পরপর হওয়া এমিশন আলাদা না একই। তাই প্রতিটি এমিশন UI এলিমেন্টে আপডেট ঘটায়। এর অর্থ হল, Flow API পদ্ধতি যেমন distinctUntilChanged() ব্যবহার করে হ্রাস করার প্রয়োজন হতে পারে।

রেন্ডারিং ও UI স্টেট সম্পর্কে আরও জানতে, কম্পোজেবল আইটেমের লাইফসাইকেল দেখুন।

UI স্টেট কনজিউম করা

UI-তে UiState অবজেক্টের স্ট্রিম ব্যবহার করতে, আপনি যে ডেটা টাইপ ব্যবহার করছেন তার জন্য টার্মিনাল অপারেটর ব্যবহার করুন। যেমন, Kotlin ফ্লোয়ের জন্য collect() পদ্ধতি বা এর বিভিন্ন রূপ ব্যবহার করুন।

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

@Composable
private fun ConversationScreen(
    conversationViewModel: ConversationViewModel = viewModel()
) {

    val messages by conversationViewModel.messages.collectAsStateWithLifecycle()

    ConversationScreen(
        messages = messages,
        onSendMessage = { message: Message -> conversationViewModel.sendMessage(message) }
    )
}

@Composable
private fun ConversationScreen(
    messages: List<Message>,
    onSendMessage: (Message) -> Unit
) {

    MessagesList(messages, onSendMessage)
    /* ... */
}

চলমান অপারেশন দেখুন

UiState ক্লাসে লোডিং স্টেট দেখানোর একটি সহজ উপায় হল বুলিয়ান ফিল্ড:

data class NewsUiState(
    val isFetchingArticles: Boolean = false,
    // ...
)

এই ফ্ল্যাগের ভ্যালু থেকে UI-তে প্রগ্রেস বার আছে কিনা তা বোঝা যায়।

@Composable
fun LatestNewsScreen(
    modifier: Modifier = Modifier,
    viewModel: NewsViewModel = viewModel()
) {
    Box(modifier.fillMaxSize()) {

        if (viewModel.uiState.isFetchingArticles) {
            CircularProgressIndicator(Modifier.align(Alignment.Center))
        }

        // Add other UI elements. For example, the list.
    }
}

স্ক্রিনে সমস্যা দেখাও

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

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

data class Message(val id: Long, val message: String)

data class NewsUiState(
    val userMessages: List<Message> = listOf(),
    // ...
)

তারপরে আপনি UI এলিমেন্ট যেমন স্ন্যাকবার-এর মাধ্যমে ব্যবহারকারীকে সমস্যার মেসেজ দেখাতে পারেন। UI ইভেন্ট কীভাবে তৈরি ও ব্যবহার করা হয় সেই সম্পর্কে আরও জানতে, UI ইভেন্ট দেখুন।

থ্রেডিং ও কনকারেন্সি

ViewModel-এ করা সব কাজ main-safe— মূল থ্রেড থেকে কল করার জন্য নিরাপদ কিনা তা নিশ্চিত করুন। ডেটা ও ডোমেন লেয়ারের দায়িত্ব হল কাজকে অন্য থ্রেডে সরানো।

ViewModel যদি দীর্ঘ সময় ধরে চলতে থাকা অপারেশন পারফর্ম করে, তাহলে ব্যাকগ্রাউন্ড থ্রেডে সেই লজিক সরানোর দায়িত্বও তার। একসাথে একাধিক অপারেশন ম্যানেজ করার জন্য Kotlin কোরাউটিন একটি দারুণ উপায় এবং Jetpack Architecture Components-এ এগুলির জন্য বিল্ট-ইন সহায়তা প্রদান করা হয়। Android অ্যাপে কোরাউটিন ব্যবহার করা সম্পর্কে আরও জানতে, Android-এ Kotlin কোরাউটিন দেখুন।

অ্যাপ নেভিগেশনে পরিবর্তন প্রায়শই ইভেন্ট-ভিত্তিক নির্গমনের কারণে হয়। যেমন, SignInViewModel ক্লাস সাইন-ইন করার পরে, UiState-এ isSignedIn ফিল্ড true হিসেবে সেট করা থাকতে পারে। আগের UI স্টেট কনজিউম করা বিভাগে কভার করা ট্রিগারের মতো এগুলিও কনজিউম করুন, কিন্তু কনজিউম করার প্রয়োগ নেভিগেশন কম্পোনেন্ট-এ ডিফার করুন।

UI নেভিগেশন সম্পর্কে আরও তথ্য পেতে, নেভিগেশন ৩ দেখুন।

পেজিং

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

নিচের উদাহরণে Paging লাইব্রেরির Compose API দেখানো হয়েছে:

@Composable
fun MyScreen(flow: Flow<PagingData<String>>) {
    val lazyPagingItems = flow.collectAsLazyPagingItems()
    LazyColumn {
        items(
            lazyPagingItems.itemCount,
            key = lazyPagingItems.itemKey { it }
        ) { index ->
            val item = lazyPagingItems[index]
            Text("Item is $item")
        }
    }
}

অ্যানিমেশন

সাবলীল টপ-লেভেল নেভিগেশন ট্রানজিশন প্রদান করতে, অ্যানিমেশন শুরু করার আগে দ্বিতীয় স্ক্রিনে ডেটা লোড হওয়ার জন্য অপেক্ষা করতে হতে পারে।

নেভিগেশন ট্রানজিশন সম্পর্কে আরও জানতে, নেভিগেশন ৩ এবং Compose-এ শেয়ার করা এলিমেন্ট ট্রানজিশন দেখুন।

অতিরিক্ত রিসোর্স

কন্টেন্ট দেখা

নমুনা

নিচে দেওয়া Google-এর নমুনা থেকে UI লেয়ারের ব্যবহার সম্পর্কে ধারণা পাওয়া যায়। এই নির্দেশিকা কীভাবে প্রয়োগ করা হয় তা দেখতে, সেগুলি ঘুরে দেখুন: