জেটপ্যাক কম্পোজ দিয়ে অভিযোজিত অ্যাপ তৈরি করুন

১. ভূমিকা

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

মূল আলোচনায় যাওয়ার আগে, অভিযোজনযোগ্যতা বলতে আমরা কী বুঝি তা বোঝা জরুরি।

অভিযোজনযোগ্যতা

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

আরও জানতে, অ্যাডাপ্টিভ ডিজাইন দেখুন।

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

আপনি যা শিখবেন

  • Jetpack Compose ব্যবহার করে কীভাবে আপনার অ্যাপকে সব ধরনের উইন্ডো সাইজের জন্য ডিজাইন করবেন
  • বিভিন্ন ধরনের ফোল্ডেবল ফোনের জন্য আপনার অ্যাপকে কীভাবে টার্গেট করবেন
  • উন্নততর পৌঁছানো ও প্রবেশগম্যতার জন্য বিভিন্ন ধরণের নেভিগেশন কীভাবে ব্যবহার করবেন।
  • প্রতিটি উইন্ডো সাইজের জন্য সেরা অভিজ্ঞতা নিশ্চিত করতে Material 3 কম্পোনেন্টগুলো কীভাবে ব্যবহার করবেন।

আপনার যা যা লাগবে

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

ফোন, খোলা, ট্যাবলেট এবং ডেস্কটপ—এই বিকল্পসহ আকার পরিবর্তনযোগ্য এমুলেটর।

আপনি যদি Compose-এর সাথে পরিচিত না হন, তাহলে এই কোডল্যাবটি সম্পন্ন করার আগে Jetpack Compose basics কোডল্যাবটি করে দেখার কথা বিবেচনা করতে পারেন।

আপনি যা তৈরি করবেন

  • রিপ্লাই (Reply) নামক একটি ইন্টারেক্টিভ ইমেল ক্লায়েন্ট অ্যাপ, যা অভিযোজনযোগ্য ডিজাইন, বিভিন্ন ম্যাটেরিয়াল নেভিগেশন এবং স্ক্রিনের জায়গার সর্বোত্তম ব্যবহারের জন্য সেরা পদ্ধতিগুলো অনুসরণ করে।

এই কোডল্যাবে আপনি একাধিক ডিভাইস সমর্থনের যে প্রদর্শনীটি অর্জন করবেন

২. প্রস্তুত হন

এই কোডল্যাবের কোড পেতে, কমান্ড লাইন থেকে গিটহাব রিপোজিটরিটি ক্লোন করুন:

git clone https://github.com/android/codelab-android-compose.git
cd codelab-android-compose/AdaptiveUiCodelab

বিকল্পভাবে, আপনি রিপোজিটরিটি একটি ZIP ফাইল হিসেবে ডাউনলোড করতে পারেন:

আমরা আপনাকে মূল ব্রাঞ্চের কোড দিয়ে শুরু করার এবং নিজের গতিতে ধাপে ধাপে কোডল্যাবটি অনুসরণ করার পরামর্শ দিচ্ছি।

অ্যান্ড্রয়েড স্টুডিওতে প্রজেক্টটি খুলুন।

  1. অ্যান্ড্রয়েড স্টুডিওতে স্বাগতম উইন্ডোতে, নির্বাচন করুন c01826594f360d94.png একটি বিদ্যমান প্রকল্প খুলুন।
  2. <Download Location>/AdaptiveUiCodelab ফোল্ডারটি নির্বাচন করুন (নিশ্চিত করুন যে আপনি build.gradle ধারণকারী AdaptiveUiCodelab ডিরেক্টরিটি নির্বাচন করেছেন)।
  3. অ্যান্ড্রয়েড স্টুডিও প্রজেক্টটি ইম্পোর্ট করার পর, পরীক্ষা করে দেখুন যে আপনি main ব্রাঞ্চটি রান করতে পারছেন কি না।

স্টার্ট কোডটি অন্বেষণ করুন

মেইন ব্রাঞ্চ কোডে ui প্যাকেজটি রয়েছে। আপনাকে সেই প্যাকেজের নিম্নলিখিত ফাইলগুলো নিয়ে কাজ করতে হবে:

  • MainActivity.kt - এটি হলো অ্যাপের প্রবেশপথ বা এন্ট্রি পয়েন্ট অ্যাক্টিভিটি, যেখান থেকে আপনি আপনার অ্যাপটি চালু করেন।
  • ReplyApp.kt - এতে মূল স্ক্রিনের UI কম্পোজেবলগুলো রয়েছে।
  • ReplyHomeViewModel.kt - অ্যাপের কন্টেন্টের জন্য ডেটা এবং UI স্টেট প্রদান করে।
  • ReplyListContent.kt - এতে তালিকা এবং বিস্তারিত স্ক্রিন প্রদানের জন্য কম্পোজেবল ফাইল রয়েছে।

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

ফোনের প্রাথমিক স্ক্রিন

ট্যাবলেটে প্রাথমিক প্রসারিত দৃশ্য

স্ক্রিনের জায়গার সদ্ব্যবহার করতে, ব্যবহারযোগ্যতা বাড়াতে এবং সার্বিক ব্যবহারকারীর অভিজ্ঞতা উন্নত করতে আপনি এটি আপডেট করবেন।

৩. অ্যাপগুলোকে অভিযোজনযোগ্য করে তুলুন

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

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

জানালার মাপ

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

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

সংক্ষিপ্ত, মাঝারি এবং প্রসারিত প্রস্থের জন্য WindowWidthSizeClass।

কম্প্যাক্ট, মিডিয়াম এবং এক্সপান্ডেড হাইটের জন্য WindowHeightSizeClass।

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

ভাঁজ অবস্থা

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

ভাঁজযোগ্য ভঙ্গি, সমতল এবং অর্ধ-উন্মুক্ত

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

ভাঁজের ভঙ্গি ও কব্জা সম্পর্কে আরও পড়ুন।

ফোল্ডেবল ডিভাইস সমর্থন করে এমন অ্যাডাপ্টিভ লেআউট বাস্তবায়ন করার সময় এই সমস্ত বিষয়গুলো বিবেচনা করতে হবে।

অভিযোজিত তথ্য পান

Material3 adaptive লাইব্রেরিটি আপনার অ্যাপটি যে উইন্ডোতে চলছে, সে সম্পর্কিত তথ্যে সুবিধাজনক অ্যাক্সেস প্রদান করে।

  1. এই আর্টিফ্যাক্ট এবং এর ভার্সনের জন্য এন্ট্রিগুলো ভার্সন ক্যাটালগ ফাইলে যোগ করুন:

gradle/libs.versions.toml

[versions]
material3Adaptive = "1.0.0"

[libraries]
androidx-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "material3Adaptive" }
  1. অ্যাপ মডিউলের বিল্ড ফাইলে নতুন লাইব্রেরি ডিপেন্ডেন্সিটি যোগ করুন এবং তারপরে একটি গ্রেডল সিঙ্ক সম্পাদন করুন:

অ্যাপ/বিল্ড.গ্রেডল.কেটিএস

dependencies {

    implementation(libs.androidx.material3.adaptive)
}

এখন, যেকোনো কম্পোজেবল স্কোপে, আপনি currentWindowAdaptiveInfo() ব্যবহার করে একটি WindowAdaptiveInfo অবজেক্ট পেতে পারেন, যাতে বর্তমান উইন্ডো সাইজ ক্লাস এবং ডিভাইসটি টেবিলটপ পসচারের মতো কোনো ফোল্ডেবল পসচারে আছে কিনা, সেই ধরনের তথ্য থাকে।

আপনি এখন MainActivity তে এটি চেষ্টা করে দেখতে পারেন।

  1. ReplyTheme ব্লকের ভিতরে onCreate() ফাংশনে, উইন্ডোর অ্যাডাপ্টিভ তথ্য নিন এবং একটি Text composable-এ সাইজ ক্লাসগুলো প্রদর্শন করুন। আপনি এটি ReplyApp() এলিমেন্টের পরে যোগ করতে পারেন:

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContent {
        ReplyTheme {
            val uiState by viewModel.uiState.collectAsStateWithLifecycle()
            ReplyApp(
                replyHomeUIState = uiState,
                onEmailClick = viewModel::setSelectedEmail
            )

            val adaptiveInfo = currentWindowAdaptiveInfo()
            val sizeClassText =
                "${adaptiveInfo.windowSizeClass.windowWidthSizeClass}\n" +
                "${adaptiveInfo.windowSizeClass.windowHeightSizeClass}"
            Text(
                text = sizeClassText,
                color = Color.Magenta,
                modifier = Modifier.padding(
                    WindowInsets.safeDrawing.asPaddingValues()
                )
            )
        }
    }
}

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

৪. ডাইনামিক নেভিগেশন

এখন আপনি ডিভাইসের অবস্থা ও আকার পরিবর্তনের সাথে সাথে অ্যাপের নেভিগেশনও পরিবর্তন করবেন, যাতে আপনার অ্যাপটি ব্যবহার করা আরও সহজ হয়।

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

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

  • ডিভাইসটি ধরে থাকা অবস্থায় কোন কোন জায়গায় সহজে হাত পৌঁছানো যায়?
  • এমন কোন কোন জায়গা আছে যেখানে শুধুমাত্র আঙুল বাড়িয়ে পৌঁছানো যায়, যা অসুবিধাজনক হতে পারে?
  • কোন এলাকাগুলোতে পৌঁছানো কঠিন অথবা ব্যবহারকারী যেখানে ডিভাইসটি ধরে রাখেন সেখান থেকে অনেক দূরে?

নেভিগেশন হলো প্রথম জিনিস যার সাথে ব্যবহারকারীরা ইন্টারঅ্যাক্ট করে এবং এতে ব্যবহারকারীর গুরুত্বপূর্ণ যাত্রাপথের সাথে সম্পর্কিত উচ্চ-গুরুত্বপূর্ণ অ্যাকশনগুলো থাকে, তাই এটিকে সবচেয়ে সহজে পৌঁছানো যায় এমন জায়গায় রাখা উচিত। ডিভাইসের উইন্ডো সাইজ ক্লাসের উপর নির্ভর করে, ম্যাটেরিয়াল অ্যাডাপটিভ লাইব্রেরিটি নেভিগেশন বাস্তবায়নে সাহায্য করার জন্য বেশ কিছু কম্পোনেন্ট সরবরাহ করে।

বটম নেভিগেশন

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

আইটেম সহ নীচের নেভিগেশন বার

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

আইটেম সহ নেভিগেশন রেল

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

মোডাল নেভিগেশন ড্রয়ার

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

আইটেম সহ মোডাল নেভিগেশন ড্রয়ার

স্থায়ী নেভিগেশন ড্রয়ার

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

আইটেম সহ স্থায়ী নেভিগেশন ড্রয়ার

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

এখন, ডিভাইসের অবস্থা ও আকার পরিবর্তনের সাথে সাথে আপনি বিভিন্ন ধরনের নেভিগেশনের মধ্যে পরিবর্তন করতে পারবেন।

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

  1. এই কম্পোনেন্টটি পাওয়ার জন্য ভার্সন ক্যাটালগ এবং অ্যাপের বিল্ড স্ক্রিপ্ট আপডেট করে গ্রেডল ডিপেন্ডেন্সি যোগ করুন, তারপর একটি গ্রেডল সিঙ্ক সম্পাদন করুন:

gradle/libs.versions.toml

[versions]
material3AdaptiveNavSuite = "1.3.0"

[libraries]
androidx-material3-adaptive-navigation-suite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite", version.ref = "material3AdaptiveNavSuite" }

অ্যাপ/বিল্ড.গ্রেডল.কেটিএস

dependencies {

    implementation(libs.androidx.material3.adaptive.navigation.suite)
}
  1. ReplyApp.ktReplyNavigationWrapper() কম্পোজেবল ফাংশনটি খুঁজুন এবং Column ও এর ভেতরের বিষয়বস্তু একটি NavigationSuiteScaffold দিয়ে প্রতিস্থাপন করুন:

ReplyApp.kt

@Composable
private fun ReplyNavigationWrapperUI(
    content: @Composable () -> Unit = {}
) {
    var selectedDestination: ReplyDestination by remember {
        mutableStateOf(ReplyDestination.Inbox)
    }

    NavigationSuiteScaffold(
        navigationSuiteItems = {
            ReplyDestination.entries.forEach {
                item(
                    selected = it == selectedDestination,
                    onClick = { /*TODO update selection*/ },
                    icon = {
                        Icon(
                            imageVector = it.icon,
                            contentDescription = stringResource(it.labelRes)
                        )
                    },
                    label = {
                        Text(text = stringResource(it.labelRes))
                    },
                )
            }
        }
    ) {
        content()
    }
}

navigationSuiteItems আর্গুমেন্টটি একটি ব্লক, যা আপনাকে ` item() ফাংশন ব্যবহার করে আইটেম যোগ করার সুযোগ দেয়, অনেকটা LazyColumn এ আইটেম যোগ করার মতোই। শেষের ল্যাম্বডার ভিতরে থাকা এই কোডটি ReplyNavigationWrapperUI() এর আর্গুমেন্ট হিসেবে পাস করা ` content() ফাংশনটিকে কল করে।

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

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

  1. NavigationSuiteScaffold কে কল করার ঠিক আগে নিম্নলিখিত কোডটি যোগ করুন:

ReplyApp.kt

@Composable
private fun ReplyNavigationWrapperUI(
    content: @Composable () -> Unit = {}
) {
    var selectedDestination: ReplyDestination by remember {
        mutableStateOf(ReplyDestination.Inbox)
    }

    val windowSize = with(LocalDensity.current) {
        currentWindowSize().toSize().toDpSize()
    }
    val layoutType = if (windowSize.width >= 1200.dp) {
        NavigationSuiteType.NavigationDrawer
    } else {
        NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(
            currentWindowAdaptiveInfo()
        )
    }

    NavigationSuiteScaffold(
        layoutType = layoutType,
        ...
    ) {
        content()
    }
}

এই কোডটি প্রথমে currentWindowSize() এবং LocalDensity.current ব্যবহার করে উইন্ডোর সাইজ নিয়ে সেটিকে ডিপি (DP) ইউনিটে রূপান্তর করে, এবং তারপর নেভিগেশন UI-এর লেআউট টাইপ নির্ধারণ করার জন্য উইন্ডোর প্রস্থের সাথে তুলনা করে। যদি উইন্ডোর প্রস্থ কমপক্ষে 1200.dp হয়, তবে এটি NavigationSuiteType.NavigationDrawer ব্যবহার করে। অন্যথায়, এটি ডিফল্ট গণনা পদ্ধতিতে ফিরে যায়।

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

বিভিন্ন আকারের ডিভাইসের জন্য অভিযোজন ক্ষমতার পরিবর্তন দেখানো হচ্ছে।

অভিনন্দন, আপনি বিভিন্ন ধরনের উইন্ডোর আকার এবং অবস্থা সমর্থন করার জন্য বিভিন্ন ধরণের নেভিগেশন সম্পর্কে শিখেছেন!

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

৫. স্ক্রিনের স্থান ব্যবহার

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

ম্যাটেরিয়াল ৩ তিনটি ক্যানোনিকাল লেআউট সংজ্ঞায়িত করে, যেগুলোর প্রতিটিতে কম্প্যাক্ট, মিডিয়াম এবং এক্সপান্ডেড উইন্ডো সাইজ ক্লাসের জন্য কনফিগারেশন রয়েছে। লিস্ট ডিটেইল ক্যানোনিকাল লেআউটটি এই ব্যবহারের জন্য উপযুক্ত, এবং এটি কম্পোজে ListDetailPaneScaffold হিসেবে উপলব্ধ।

  1. নিম্নলিখিত ডিপেন্ডেন্সিগুলো যোগ করে এবং একটি গ্রেডল সিঙ্ক সম্পাদন করে এই কম্পোনেন্টটি পান:

gradle/libs.versions.toml

[libraries]
androidx-material3-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout", version.ref = "material3Adaptive" }
androidx-material3-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation", version.ref = "material3Adaptive" }

অ্যাপ/বিল্ড.গ্রেডল.কেটিএস

dependencies {

    implementation(libs.androidx.material3.adaptive.layout)
    implementation(libs.androidx.material3.adaptive.navigation)
}
  1. ReplyApp.ktReplyAppContent() কম্পোজেবল ফাংশনটি খুঁজুন, যা বর্তমানে শুধুমাত্র ReplyListPane() কল করে লিস্ট পেইনটি দেখায়। নিম্নলিখিত কোডটি প্রবেশ করিয়ে এই ইমপ্লিমেন্টেশনটি ListDetailPaneScaffold দিয়ে প্রতিস্থাপন করুন। যেহেতু এটি একটি পরীক্ষামূলক API, তাই আপনাকে ReplyAppContent() ফাংশনটিতে @OptIn অ্যানোটেশনটিও যোগ করতে হবে:

ReplyApp.kt

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
    replyHomeUIState: ReplyHomeUIState,
    onEmailClick: (Email) -> Unit,
) {
    val navigator = rememberListDetailPaneScaffoldNavigator<Long>()

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            ReplyListPane(replyHomeUIState, onEmailClick)
        },
        detailPane = {
            ReplyDetailPane(replyHomeUIState.emails.first())
        }
    )
}

এই কোডটি প্রথমে rememberListDetailPaneNavigator () ব্যবহার করে একটি নেভিগেটর তৈরি করে। rememberListDetailPaneNavigator () কোন প্যানটি প্রদর্শিত হবে এবং সেই প্যানে কী বিষয়বস্তু দেখানো হবে, তার উপর কিছুটা নিয়ন্ত্রণ প্রদান করে, যা পরবর্তীতে প্রদর্শন করা হবে।

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

অবশিষ্ট প্রয়োজনীয় প্যারামিটারগুলো হলো প্যানগুলোর জন্য কম্পোজেবল ল্যাম্বডা। ReplyListPane() এবং ReplyDetailPane() (যা ReplyListContent.kt এ পাওয়া যায়) যথাক্রমে লিস্ট এবং ডিটেইল প্যানের ভূমিকা পালন করতে ব্যবহৃত হয়। ReplyDetailPane() একটি ইমেল আর্গুমেন্ট আশা করে, তাই আপাতত এই কোডটি ReplyHomeUIState এ থাকা ইমেলের তালিকা থেকে প্রথম ইমেলটি ব্যবহার করছে।

অ্যাপটি চালান এবং দুটি প্যানেলের লেআউটটি দেখতে এমুলেটর ভিউটি ফোল্ডেবল বা ট্যাবলেটে পরিবর্তন করুন (প্রয়োজনে আপনাকে ওরিয়েন্টেশনও পরিবর্তন করতে হতে পারে)। এটি এখন আগের চেয়ে অনেক ভালো দেখাচ্ছে!

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

  1. ReplyHomeViewModel.kt খুলুন এবং ReplyHomeUIState ডেটা ক্লাসটি খুঁজুন। নির্বাচিত ইমেইলের জন্য একটি প্রপার্টি যোগ করুন, যার ডিফল্ট মান হবে null :

ReplyHomeViewModel.kt

data class ReplyHomeUIState(
    val emails : List<Email> = emptyList(),
    val selectedEmail: Email? = null,
    val loading: Boolean = false,
    val error: String? = null
)
  1. একই ফাইলে, ReplyHomeViewModel একটি setSelectedEmail() ফাংশন আছে যা ব্যবহারকারী তালিকার কোনো আইটেমে ট্যাপ করলে কল করা হয়। UI স্টেট কপি করতে এবং নির্বাচিত ইমেলটি রেকর্ড করতে এই ফাংশনটি পরিবর্তন করুন:

ReplyHomeViewModel.kt

fun setSelectedEmail(email: Email) {
    _uiState.update {
        it.copy(selectedEmail = email)
    }
}

একটি বিবেচ্য বিষয় হলো, ব্যবহারকারী কোনো আইটেমে ট্যাপ করার আগে এবং নির্বাচিত ইমেলটি null হলে কী ঘটবে। ডিটেইল প্যানে কী প্রদর্শন করা উচিত? এই পরিস্থিতি সামাল দেওয়ার একাধিক উপায় রয়েছে, যেমন ডিফল্টরূপে তালিকার প্রথম আইটেমটি দেখানো।

  1. একই ফাইলে, observeEmails() ফাংশনটি পরিবর্তন করুন। ইমেলের তালিকা লোড হওয়ার পর, যদি পূর্ববর্তী UI স্টেটে কোনো ইমেল নির্বাচিত না থাকে, তাহলে সেটিকে প্রথম আইটেমটিতে সেট করুন:

ReplyHomeViewModel.kt

private fun observeEmails() {
    viewModelScope.launch {
        emailsRepository.getAllEmails()
            .catch { ex ->
                _uiState.value = ReplyHomeUIState(error = ex.message)
            }
            .collect { emails ->
                val currentSelection = _uiState.value.selectedEmail
                _uiState.value = ReplyHomeUIState(
                    emails = emails,
                    selectedEmail = currentSelection ?: emails.first()
                )
            }
    }
}
  1. ReplyApp.kt এ ফিরে যান এবং নির্বাচিত ইমেলটি (যদি উপলব্ধ থাকে) ব্যবহার করে ডিটেইল পেনের বিষয়বস্তু পূরণ করুন:

ReplyApp.kt

ListDetailPaneScaffold(
    // ...
    detailPane = {
        if (replyHomeUIState.selectedEmail != null) {
            ReplyDetailPane(replyHomeUIState.selectedEmail)
        }
    }
)

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

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

  1. এটি ঠিক করতে, ReplyListPane এ পাস করা ল্যাম্বডা হিসেবে নিম্নলিখিত কোডটি যুক্ত করুন:

ReplyApp.kt

ListDetailPaneScaffold(
    // ...
    listPane = {
        ReplyListPane(
            replyHomeUIState = replyHomeUIState,
            onEmailClick = { email ->
                onEmailClick(email)
                navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
            }
        )
    },
    // ...
)

এই ল্যাম্বডাটি কোনো আইটেমে ক্লিক করা হলে অতিরিক্ত আচরণ যোগ করার জন্য পূর্বে তৈরি করা নেভিগেটরটি ব্যবহার করে। এটি এই ফাংশনে পাস করা মূল ল্যাম্বডাটিকে কল করবে এবং তারপর কোন পেইনটি দেখানো উচিত তা নির্দিষ্ট করে navigator.navigateTo() কল করবে। স্ক্যাফোল্ডের প্রতিটি পেইনের সাথে একটি রোল যুক্ত থাকে, এবং ডিটেইল পেইনের জন্য সেটি হলো ListDetailPaneScaffoldRole.Detail । ছোট উইন্ডোতে, এটি এমন একটি ধারণা দেবে যে অ্যাপটি সামনে এগিয়ে গেছে।

ব্যবহারকারী ডিটেইল পেইন থেকে ব্যাক বাটন চাপলে কী ঘটবে, সেটাও অ্যাপটিকে সামলাতে হবে এবং একটি পেইন নাকি দুটি পেইন দৃশ্যমান আছে, তার ওপর নির্ভর করে এই আচরণটি ভিন্ন হবে।

  1. নিম্নলিখিত কোডটি যোগ করে ব্যাক নেভিগেশন সমর্থন করুন।

ReplyApp.kt

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
    replyHomeUIState: ReplyHomeUIState,
    onEmailClick: (Email) -> Unit,
) {
    val navigator = rememberListDetailPaneScaffoldNavigator<Long>()

    BackHandler(navigator.canNavigateBack()) {
        navigator.navigateBack()
    }

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            AnimatedPane {
                ReplyListPane(
                    replyHomeUIState = replyHomeUIState,
                    onEmailClick = { email ->
                        onEmailClick(email)
                        navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
                    }
                )
            }
        },
        detailPane = {
            AnimatedPane {
                if (replyHomeUIState.selectedEmail != null) {
                    ReplyDetailPane(replyHomeUIState.selectedEmail)
                }
            }
        }
    )
}

নেভিগেটর ListDetailPaneScaffold এর সম্পূর্ণ অবস্থা, পিছনে ফিরে যাওয়া সম্ভব কিনা এবং এই সমস্ত পরিস্থিতিতে কী করতে হবে, তা জানে। এই কোডটি একটি BackHandler তৈরি করে যা নেভিগেটর পিছনে ফিরে যেতে পারলেই সক্রিয় হয় এবং ল্যাম্বডার ভিতরে navigateBack() কল করে। এছাড়াও, প্যানগুলির মধ্যে পরিবর্তনকে আরও মসৃণ করার জন্য, প্রতিটি প্যানকে একটি AnimatedPane() কম্পোজেবলের মধ্যে মোড়ানো হয়েছে।

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

বিভিন্ন আকারের ডিভাইসের জন্য অভিযোজন ক্ষমতার পরিবর্তন দেখানো হচ্ছে।

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

৬. অভিনন্দন

অভিনন্দন! আপনি সফলভাবে এই কোডল্যাবটি সম্পন্ন করেছেন এবং Jetpack Compose ব্যবহার করে কীভাবে অ্যাপকে অ্যাডাপ্টিভ করা যায় তা শিখেছেন।

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

এরপর কী?

Compose পাথওয়ের অন্যান্য কোডল্যাবগুলো দেখে নিন।

নমুনা অ্যাপ

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

রেফারেন্স নথি