জেটপ্যাক কম্পোজ কোটলিনকে কেন্দ্র করে তৈরি। কিছু ক্ষেত্রে, কোটলিন বিশেষ বাগধারা প্রদান করে যা ভালো কম্পোজ কোড লেখা সহজ করে তোলে। আপনি যদি অন্য প্রোগ্রামিং ভাষায় চিন্তা করেন এবং সেই ভাষাটি মানসিকভাবে কোটলিনে অনুবাদ করেন, তাহলে আপনি কম্পোজের কিছু শক্তি মিস করতে পারেন এবং আপনার কাছে বাগধারা অনুসারে লেখা কোটলিন কোড বুঝতে অসুবিধা হতে পারে। কোটলিনের স্টাইলের সাথে আরও পরিচিতি অর্জন আপনাকে সেই ঝুঁকিগুলি এড়াতে সাহায্য করতে পারে।
ডিফল্ট আর্গুমেন্ট
যখন আপনি একটি Kotlin ফাংশন লেখেন, তখন আপনি ফাংশন আর্গুমেন্টের জন্য ডিফল্ট মান নির্দিষ্ট করতে পারেন, যদি কলার স্পষ্টভাবে সেই মানগুলি পাস না করে তবে এটি ব্যবহার করা হয়। এই বৈশিষ্ট্যটি ওভারলোডেড ফাংশনের প্রয়োজনীয়তা হ্রাস করে।
উদাহরণস্বরূপ, ধরুন আপনি এমন একটি ফাংশন লিখতে চান যা একটি বর্গক্ষেত্র আঁকে। সেই ফাংশনটিতে একটি একক প্রয়োজনীয় প্যারামিটার থাকতে পারে, sideLength , যা প্রতিটি বাহুর দৈর্ঘ্য নির্দিষ্ট করে। এতে বেশ কয়েকটি ঐচ্ছিক প্যারামিটার থাকতে পারে, যেমন thickness , edgeColor ইত্যাদি; যদি কলার সেগুলি নির্দিষ্ট না করে, তাহলে ফাংশনটি ডিফল্ট মান ব্যবহার করে। অন্যান্য ভাষায়, আপনি বেশ কয়েকটি ফাংশন লেখার আশা করতে পারেন:
// We don't need to do this in Kotlin! void drawSquare(int sideLength) { } void drawSquare(int sideLength, int thickness) { } void drawSquare(int sideLength, int thickness, Color edgeColor) { }
কোটলিনে, আপনি একটি একক ফাংশন লিখতে পারেন এবং আর্গুমেন্টগুলির জন্য ডিফল্ট মান নির্দিষ্ট করতে পারেন:
fun drawSquare( sideLength: Int, thickness: Int = 2, edgeColor: Color = Color.Black ) { }
একাধিক অপ্রয়োজনীয় ফাংশন লেখার ঝামেলা থেকে বাঁচানোর পাশাপাশি, এই বৈশিষ্ট্যটি আপনার কোডটি আরও স্পষ্টভাবে পড়তে সাহায্য করে। যদি কলার কোনও আর্গুমেন্টের জন্য কোনও মান নির্দিষ্ট না করে, তবে এর অর্থ হল তারা ডিফল্ট মান ব্যবহার করতে ইচ্ছুক। এছাড়াও, নামযুক্ত প্যারামিটারগুলি কী ঘটছে তা দেখা অনেক সহজ করে তোলে। আপনি যদি কোডটি দেখেন এবং এই জাতীয় একটি ফাংশন কল দেখতে পান, drawSquare() কোডটি পরীক্ষা না করে আপনি প্যারামিটারগুলির অর্থ কী তা জানতে পারবেন না:
drawSquare(30, 5, Color.Red);
বিপরীতে, এই কোডটি স্ব-নথিভুক্ত:
drawSquare(sideLength = 30, thickness = 5, edgeColor = Color.Red)
বেশিরভাগ কম্পোজ লাইব্রেরি ডিফল্ট আর্গুমেন্ট ব্যবহার করে, এবং আপনার লেখা কম্পোজেবল ফাংশনের ক্ষেত্রেও এটি করা একটি ভালো অভ্যাস। এই অনুশীলনটি আপনার কম্পোজেবলগুলিকে কাস্টমাইজেবল করে তোলে, তবে ডিফল্ট আচরণকে আহ্বান করা সহজ করে তোলে। উদাহরণস্বরূপ, আপনি এইরকম একটি সাধারণ টেক্সট উপাদান তৈরি করতে পারেন:
Text(text = "Hello, Android!")
এই কোডটির প্রভাব নিচের কোডের মতোই, অনেক বেশি ভার্বোজ কোডের মতো, যেখানে আরও বেশি Text প্যারামিটার স্পষ্টভাবে সেট করা আছে:
Text( text = "Hello, Android!", color = Color.Unspecified, fontSize = TextUnit.Unspecified, letterSpacing = TextUnit.Unspecified, overflow = TextOverflow.Clip )
প্রথম কোড স্নিপেটটি কেবল অনেক সহজ এবং পঠনযোগ্য নয়, এটি স্ব-নথিভুক্তকরণও। শুধুমাত্র text প্যারামিটার নির্দিষ্ট করে, আপনি নথিভুক্ত করেন যে অন্যান্য সমস্ত প্যারামিটারের জন্য, আপনি ডিফল্ট মান ব্যবহার করতে চান। বিপরীতে, দ্বিতীয় স্নিপেটটি বোঝায় যে আপনি স্পষ্টভাবে সেই অন্যান্য প্যারামিটারগুলির জন্য মান সেট করতে চান, যদিও আপনার সেট করা মানগুলি ফাংশনের জন্য ডিফল্ট মান।
উচ্চ-ক্রম ফাংশন এবং ল্যাম্বডা এক্সপ্রেশন
কোটলিন উচ্চ-ক্রম ফাংশন সমর্থন করে, ফাংশনগুলি যা প্যারামিটার হিসাবে অন্যান্য ফাংশন গ্রহণ করে। কম্পোজ এই পদ্ধতির উপর ভিত্তি করে তৈরি হয়। উদাহরণস্বরূপ, Button কম্পোজেবল ফাংশন একটি onClick ল্যাম্বডা প্যারামিটার প্রদান করে। সেই প্যারামিটারের মান হল একটি ফাংশন, যা ব্যবহারকারী যখন এটিতে ক্লিক করে তখন বোতামটি কল করে:
Button( // ... onClick = myClickFunction ) // ...
উচ্চ-ক্রমের ফাংশনগুলি স্বাভাবিকভাবেই lambda এক্সপ্রেশনের সাথে যুক্ত হয়, যা একটি ফাংশনের মূল্যায়ন করে। যদি আপনার কেবল একবার ফাংশনটির প্রয়োজন হয়, তাহলে উচ্চ-ক্রমের ফাংশনে এটি পাস করার জন্য আপনাকে অন্য কোথাও এটি সংজ্ঞায়িত করতে হবে না। পরিবর্তে, আপনি কেবল lambda এক্সপ্রেশন দিয়ে ফাংশনটি সংজ্ঞায়িত করতে পারেন। পূর্ববর্তী উদাহরণটি ধরে নেয় যে myClickFunction() অন্য কোথাও সংজ্ঞায়িত করা হয়েছে। কিন্তু আপনি যদি এখানে কেবল সেই ফাংশনটি ব্যবহার করেন, তাহলে ল্যাম্বডা এক্সপ্রেশন দিয়ে ফাংশনটি ইনলাইনে সংজ্ঞায়িত করা সহজ:
Button( // ... onClick = { // do something // do something else } ) { /* ... */ }
ট্রেইলিং ল্যাম্বডাস
Kotlin উচ্চ-ক্রম ফাংশন কল করার জন্য একটি বিশেষ সিনট্যাক্স অফার করে যার শেষ প্যারামিটার হল lambda। যদি আপনি একটি lambda এক্সপ্রেশনকে সেই প্যারামিটার হিসাবে পাস করতে চান, তাহলে আপনি trailing lambda সিনট্যাক্স ব্যবহার করতে পারেন। lambda এক্সপ্রেশনটি বন্ধনীর মধ্যে রাখার পরিবর্তে, আপনি এটি পরে রাখবেন। এটি Compose-এ একটি সাধারণ পরিস্থিতি, তাই কোডটি কেমন দেখাচ্ছে তা সম্পর্কে আপনার পরিচিত হওয়া প্রয়োজন।
উদাহরণস্বরূপ, সকল লেআউটের শেষ প্যারামিটার, যেমন Column() composable ফাংশন, হল content , একটি ফাংশন যা চাইল্ড UI উপাদানগুলিকে নির্গত করে। ধরুন আপনি তিনটি টেক্সট উপাদান সম্বলিত একটি কলাম তৈরি করতে চান, এবং আপনাকে কিছু ফর্ম্যাটিং প্রয়োগ করতে হবে। এই কোডটি কাজ করবে, কিন্তু এটি খুবই জটিল:
Column( modifier = Modifier.padding(16.dp), content = { Text("Some text") Text("Some more text") Text("Last text") } )
যেহেতু ফাংশন সিগনেচারের শেষ প্যারামিটারটি হল content প্যারামিটার, এবং আমরা এর মানটি ল্যাম্বডা এক্সপ্রেশন হিসেবে পাস করছি, তাই আমরা এটিকে বন্ধনী থেকে বের করে আনতে পারি:
Column(modifier = Modifier.padding(16.dp)) { Text("Some text") Text("Some more text") Text("Last text") }
দুটি উদাহরণের অর্থ ঠিক একই। ব্রেসগুলি ল্যাম্বডা এক্সপ্রেশনকে সংজ্ঞায়িত করে যা content প্যারামিটারে পাস করা হয়।
আসলে, যদি আপনি একমাত্র প্যারামিটারটি পাস করেন যা ট্রেইলিং ল্যাম্বডা হয় - অর্থাৎ, যদি চূড়ান্ত প্যারামিটারটি একটি ল্যাম্বডা হয়, এবং আপনি অন্য কোনও প্যারামিটার পাস না করেন - তাহলে আপনি বন্ধনীগুলি সম্পূর্ণরূপে বাদ দিতে পারেন। সুতরাং, উদাহরণস্বরূপ, ধরুন আপনার Column এ কোনও মডিফায়ার পাস করার প্রয়োজন ছিল না। আপনি কোডটি এভাবে লিখতে পারেন:
Column { Text("Some text") Text("Some more text") Text("Last text") }
এই সিনট্যাক্সটি Compose-এ বেশ সাধারণ, বিশেষ করে Column মতো লেআউট উপাদানগুলির জন্য। শেষ প্যারামিটারটি হল একটি ল্যাম্বডা এক্সপ্রেশন যা উপাদানের শিশুগুলিকে সংজ্ঞায়িত করে, এবং ফাংশন কলের পরে সেই শিশুগুলিকে ব্রেসগুলিতে নির্দিষ্ট করা হয়।
স্কোপ এবং রিসিভার
কিছু পদ্ধতি এবং বৈশিষ্ট্য শুধুমাত্র একটি নির্দিষ্ট স্কোপে উপলব্ধ। সীমিত স্কোপে আপনি যেখানে প্রয়োজন সেখানে কার্যকারিতা প্রদান করতে পারেন এবং যেখানে উপযুক্ত নয় সেখানে দুর্ঘটনাক্রমে সেই কার্যকারিতা ব্যবহার এড়াতে পারেন।
Compose-এ ব্যবহৃত একটি উদাহরণ বিবেচনা করুন। যখন আপনি Row লেআউটকে composable বলেন, তখন আপনার কন্টেন্ট lambda স্বয়ংক্রিয়ভাবে RowScope মধ্যে চালু হয়ে যায়। এটি Row এমন কার্যকারিতা প্রকাশ করতে সক্ষম করে যা শুধুমাত্র Row মধ্যে বৈধ। নীচের উদাহরণটি দেখায় যে Row কীভাবে align modifier-এর জন্য একটি row-নির্দিষ্ট মান প্রকাশ করেছে:
Row { Text( text = "Hello world", // This Text is inside a RowScope so it has access to // Alignment.CenterVertically but not to // Alignment.CenterHorizontally, which would be available // in a ColumnScope. modifier = Modifier.align(Alignment.CenterVertically) ) }
কিছু API ল্যাম্বডাস গ্রহণ করে যা রিসিভার স্কোপ নামে পরিচিত। প্যারামিটার ঘোষণার উপর ভিত্তি করে, এই ল্যাম্বডাসগুলির অন্য কোথাও সংজ্ঞায়িত বৈশিষ্ট্য এবং ফাংশনগুলিতে অ্যাক্সেস রয়েছে:
Box( modifier = Modifier.drawBehind { // This method accepts a lambda of type DrawScope.() -> Unit // therefore in this lambda we can access properties and functions // available from DrawScope, such as the `drawRectangle` function. drawRect( /*...*/ /* ... ) } )
আরও তথ্যের জন্য, কোটলিন ডকুমেন্টেশনে রিসিভার সহ ফাংশন লিটারেলস দেখুন।
অর্পিত সম্পত্তি
কোটলিন ডেলিগেটেড প্রোপার্টি সমর্থন করে। এই প্রোপার্টিগুলিকে ফিল্ড হিসেবে ডাকা হয়, কিন্তু তাদের মান একটি এক্সপ্রেশন মূল্যায়নের মাধ্যমে গতিশীলভাবে নির্ধারিত হয়। আপনি এই প্রোপার্টিগুলিকে by সিনট্যাক্স ব্যবহার করে চিনতে পারেন:
class DelegatingClass { var name: String by nameGetterFunction() // ... }
অন্যান্য কোড এই কোড ব্যবহার করে সম্পত্তি অ্যাক্সেস করতে পারে:
val myDC = DelegatingClass() println("The name property is: " + myDC.name)
যখন println() এক্সিকিউট হয়, তখন স্ট্রিং এর মান ফেরত দেওয়ার জন্য nameGetterFunction() কল করা হয়।
রাষ্ট্র-সমর্থিত সম্পত্তি নিয়ে কাজ করার সময় এই ডেলিগেটেড বৈশিষ্ট্যগুলি বিশেষভাবে কার্যকর:
var showDialog by remember { mutableStateOf(false) } // Updating the var automatically triggers a state change showDialog = true
ডেটা ক্লাস গঠন করা
যদি আপনি একটি ডেটা ক্লাস সংজ্ঞায়িত করেন, তাহলে আপনি একটি ডিস্ট্রাক্টরিং ডিক্লেয়ারেশনের মাধ্যমে সহজেই ডেটা অ্যাক্সেস করতে পারবেন। উদাহরণস্বরূপ, ধরুন আপনি একটি Person ক্লাস সংজ্ঞায়িত করেছেন:
data class Person(val name: String, val age: Int)
যদি আপনার কাছে এই ধরণের কোন অবজেক্ট থাকে, তাহলে আপনি এই কোড ব্যবহার করে এর মানগুলি অ্যাক্সেস করতে পারেন:
val mary = Person(name = "Mary", age = 35) // ... val (name, age) = mary
আপনি প্রায়শই Compose ফাংশনে এই ধরণের কোড দেখতে পাবেন:
Row { val (image, title, subtitle) = createRefs() // The `createRefs` function returns a data object; // the first three components are extracted into the // image, title, and subtitle variables. // ... }
ডেটা ক্লাসগুলি আরও অনেক কার্যকর কার্যকারিতা প্রদান করে। উদাহরণস্বরূপ, যখন আপনি একটি ডেটা ক্লাস সংজ্ঞায়িত করেন, তখন কম্পাইলার স্বয়ংক্রিয়ভাবে equals() এবং copy() মতো দরকারী ফাংশনগুলি সংজ্ঞায়িত করে। আপনি ডেটা ক্লাস ডকুমেন্টেশনে আরও তথ্য পেতে পারেন।
সিঙ্গেলটন বস্তু
কোটলিন সিঙ্গেলটন ডিক্লেয়ার করা সহজ করে তোলে, যে ক্লাসগুলিতে সর্বদা একটি এবং শুধুমাত্র একটি ইনস্ট্যান্স থাকে। এই সিঙ্গেলটনগুলি object কীওয়ার্ড দিয়ে ডিক্লেয়ার করা হয়। কম্পোজ প্রায়শই এই ধরনের অবজেক্ট ব্যবহার করে। উদাহরণস্বরূপ, MaterialTheme একটি singleton অবজেক্ট হিসাবে সংজ্ঞায়িত করা হয়; MaterialTheme.colors , shapes এবং typography বৈশিষ্ট্যগুলিতে বর্তমান থিমের মান থাকে।
টাইপ-সেফ বিল্ডার এবং ডিএসএল
কোটলিন টাইপ-সেফ বিল্ডারদের সাথে ডোমেন-নির্দিষ্ট ভাষা (DSL) তৈরি করার অনুমতি দেয়। DSL জটিল শ্রেণিবদ্ধ ডেটা কাঠামো তৈরি করার অনুমতি দেয় আরও রক্ষণাবেক্ষণযোগ্য এবং পাঠযোগ্য উপায়ে।
জেটপ্যাক কম্পোজ কিছু API যেমন LazyRow এবং LazyColumn এর জন্য DSL ব্যবহার করে।
@Composable fun MessageList(messages: List<Message>) { LazyColumn { // Add a single item as a header item { Text("Message List") } // Add list of messages items(messages) { message -> Message(message) } } }
Kotlin receiver এর সাথে ফাংশন লিটারেল ব্যবহার করে টাইপ-সেফ বিল্ডারদের গ্যারান্টি দেয়। যদি আমরা Canvas কম্পোজেবলকে উদাহরণ হিসেবে নিই, তাহলে এটি একটি প্যারামিটার হিসেবে DrawScope সহ একটি ফাংশন গ্রহণ করে, onDraw: DrawScope.() -> Unit , যা কোডের ব্লককে DrawScope এ সংজ্ঞায়িত সদস্য ফাংশন কল করার অনুমতি দেয়।
Canvas(Modifier.size(120.dp)) { // Draw grey background, drawRect function is provided by the receiver drawRect(color = Color.Gray) // Inset content by 10 pixels on the left/right sides // and 12 by the top/bottom inset(10.0f, 12.0f) { val quadrantSize = size / 2.0f // Draw a rectangle within the inset bounds drawRect( size = quadrantSize, color = Color.Red ) rotate(45.0f) { drawRect(size = quadrantSize, color = Color.Blue) } } }
টাইপ-সেফ বিল্ডার এবং DSL সম্পর্কে আরও জানুন কোটলিনের ডকুমেন্টেশনে ।
কোটলিন কোরোটিন
কোটলিনে ভাষা স্তরে কোরোটিনগুলি অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং সমর্থন প্রদান করে। কোরোটিনগুলি থ্রেড ব্লক না করেই এক্সিকিউশন স্থগিত করতে পারে। একটি প্রতিক্রিয়াশীল UI স্বভাবতই অ্যাসিঙ্ক্রোনাস, এবং জেটপ্যাক কম্পোজ কলব্যাক ব্যবহার না করে API স্তরে কোরোটিনগুলি গ্রহণ করে এই সমস্যার সমাধান করে।
Jetpack Compose এমন API অফার করে যা UI লেয়ারের মধ্যে coroutine ব্যবহারকে নিরাপদ করে। rememberCoroutineScope ফাংশনটি একটি CoroutineScope প্রদান করে যার সাহায্যে আপনি ইভেন্ট হ্যান্ডলারগুলিতে coroutine তৈরি করতে পারেন এবং Compose suspend API কল করতে পারেন। ScrollState এর animateScrollTo API ব্যবহার করে নীচের উদাহরণটি দেখুন।
// Create a CoroutineScope that follows this composable's lifecycle val composableScope = rememberCoroutineScope() Button( // ... onClick = { // Create a new coroutine that scrolls to the top of the list // and call the ViewModel to load data composableScope.launch { scrollState.animateScrollTo(0) // This is a suspend function viewModel.loadData() } } ) { /* ... */ }
Coroutines ডিফল্টভাবে কোডের ব্লকটি ক্রমানুসারে কার্যকর করে। একটি চলমান coroutine যা একটি suspend ফাংশন কল করে, suspend ফাংশনটি ফিরে না আসা পর্যন্ত তার কার্যকরকরণ স্থগিত করে । suspend ফাংশনটি এক্সিকিউশনটিকে একটি ভিন্ন CoroutineDispatcher এ স্থানান্তর করলেও এটি সত্য। পূর্ববর্তী উদাহরণে, suspend ফাংশন animateScrollTo ফিরে না আসা পর্যন্ত loadData কার্যকর করা হবে না।
একই সাথে কোড চালানোর জন্য, নতুন কোরোটিন তৈরি করতে হবে। উপরের উদাহরণে, স্ক্রিনের শীর্ষে স্ক্রোল করা এবং viewModel থেকে ডেটা লোড করার সমান্তরালকরণের জন্য, দুটি কোরোটিন প্রয়োজন।
// Create a CoroutineScope that follows this composable's lifecycle val composableScope = rememberCoroutineScope() Button( // ... onClick = { // Scroll to the top and load data in parallel by creating a new // coroutine per independent work to do composableScope.launch { scrollState.animateScrollTo(0) } composableScope.launch { viewModel.loadData() } } ) { /* ... */ }
Coroutines অ্যাসিঙ্ক্রোনাস API গুলিকে একত্রিত করা সহজ করে তোলে। নিম্নলিখিত উদাহরণে, আমরা pointerInput মডিফায়ারকে অ্যানিমেশন API গুলির সাথে একত্রিত করি যাতে ব্যবহারকারী যখন স্ক্রিনে ট্যাপ করে তখন একটি উপাদানের অবস্থান অ্যানিমেট করা যায়।
@Composable fun MoveBoxWhereTapped() { // Creates an `Animatable` to animate Offset and `remember` it. val animatedOffset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) } Box( // The pointerInput modifier takes a suspend block of code Modifier .fillMaxSize() .pointerInput(Unit) { // Create a new CoroutineScope to be able to create new // coroutines inside a suspend function coroutineScope { while (true) { // Wait for the user to tap on the screen and animate // in the same block awaitPointerEventScope { val offset = awaitFirstDown().position // Launch a new coroutine to asynchronously animate to // where the user tapped on the screen launch { // Animate to the pressed position animatedOffset.animateTo(offset) } } } } } ) { Text("Tap anywhere", Modifier.align(Alignment.Center)) Box( Modifier .offset { // Use the animated offset as the offset of this Box IntOffset( animatedOffset.value.x.roundToInt(), animatedOffset.value.y.roundToInt() ) } .size(40.dp) .background(Color(0xff3c1361), CircleShape) ) }
কোরোটিন সম্পর্কে আরও জানতে, অ্যান্ড্রয়েডে কোটলিন কোরোটিন নির্দেশিকাটি দেখুন।
{% অক্ষরে অক্ষরে %}আপনার জন্য প্রস্তাবিত
- দ্রষ্টব্য: জাভাস্ক্রিপ্ট বন্ধ থাকলে লিঙ্ক টেক্সট প্রদর্শিত হয়।
- উপাদান উপাদান এবং বিন্যাস
- কম্পোজে পার্শ্বপ্রতিক্রিয়া
- রচনা লেআউটের মূল বিষয়গুলি