জেটপ্যাক রচনার জন্য কোটলিন

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

ডিফল্ট আর্গুমেন্ট

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

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

// 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 প্যারামিটার নির্দিষ্ট করে, আপনি নথিভুক্ত করেন যে অন্যান্য সমস্ত প্যারামিটারের জন্য, আপনি ডিফল্ট মান ব্যবহার করতে চান। বিপরীতে, দ্বিতীয় স্নিপেটটি বোঝায় যে আপনি স্পষ্টভাবে সেই অন্যান্য পরামিতিগুলির জন্য মানগুলি সেট করতে চান, যদিও আপনার সেট করা মানগুলি ফাংশনের জন্য ডিফল্ট মান হতে পারে৷

উচ্চ-ক্রম ফাংশন এবং ল্যাম্বডা এক্সপ্রেশন

Kotlin উচ্চ-ক্রম ফাংশন সমর্থন করে, ফাংশন যা পরামিতি হিসাবে অন্যান্য ফাংশন গ্রহণ করে। রচনা এই পদ্ধতির উপর বিল্ড. উদাহরণস্বরূপ, Button কম্পোজেবল ফাংশন একটি onClick lambda প্যারামিটার প্রদান করে। সেই প্যারামিটারের মান হল একটি ফাংশন, যা ব্যবহারকারী ক্লিক করলে বোতামটি কল করে:

Button(
    // ...
    onClick = myClickFunction
)
// ...

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

Button(
    // ...
    onClick = {
        // do something
        // do something else
    }
) { /* ... */ }

পিছিয়ে থাকা ল্যাম্বডাস

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

উদাহরণস্বরূপ, Column() কম্পোজেবল ফাংশনের মতো সমস্ত লেআউটের শেষ প্যারামিটার হল 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")
}

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

সুযোগ এবং রিসিভার

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

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

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)
    )
}

কিছু এপিআই ল্যাম্বডাস গ্রহণ করে যাকে রিসিভার স্কোপে বলা হয়। প্যারামিটার ঘোষণার উপর ভিত্তি করে এই ল্যাম্বডাসের বৈশিষ্ট্য এবং ফাংশনগুলিতে অ্যাক্সেস রয়েছে যা অন্যত্র সংজ্ঞায়িত করা হয়েছে:

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() execute হয়, 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

আপনি প্রায়ই কম্পোজ ফাংশনে এই ধরনের কোড দেখতে পাবেন:

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() মতো দরকারী ফাংশনগুলিকে সংজ্ঞায়িত করে। আপনি ডেটা ক্লাস ডকুমেন্টেশনে আরও তথ্য পেতে পারেন।

একক বস্তু

কোটলিন সিঙ্গেলটন ঘোষণা করা সহজ করে তোলে, এমন ক্লাস যেখানে সবসময় একটি এবং শুধুমাত্র একটি উদাহরণ থাকে। এই singletons object কীওয়ার্ড দিয়ে ঘোষণা করা হয়। রচনা প্রায়ই এই ধরনের বস্তু ব্যবহার করে। উদাহরণস্বরূপ, MaterialTheme একটি সিঙ্গলটন বস্তু হিসাবে সংজ্ঞায়িত করা হয়; MaterialTheme.colors , shapes এবং typography বৈশিষ্ট্যে বর্তমান থিমের মান রয়েছে।

টাইপ-সেফ বিল্ডার এবং ডিএসএল

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

জেটপ্যাক কম্পোজ কিছু 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)
        }
    }
}

কোটলিন রিসিভার সহ ফাংশন লিটারাল ব্যবহার করে টাইপ-সেফ বিল্ডারদের গ্যারান্টি দেয়। যদি আমরা 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)
        }
    }
}

কোটলিনের ডকুমেন্টেশনে টাইপ-সেফ বিল্ডার এবং ডিএসএল সম্পর্কে আরও জানুন।

কোটলিন কোরোটিন

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

জেটপ্যাক কম্পোজ এমন API অফার করে যা UI স্তরের মধ্যে কোরোটিন ব্যবহার করে নিরাপদ করে। rememberCoroutineScope ফাংশন একটি CoroutineScope প্রদান করে যার সাহায্যে আপনি ইভেন্ট হ্যান্ডলারগুলিতে coroutine তৈরি করতে পারেন এবং কম্পোজ সাসপেন্ড 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 ডিফল্টরূপে ক্রমানুসারে কোডের ব্লক চালায়। একটি চলমান কোরোটিন যা একটি সাসপেন্ড ফাংশনকে কল করে, সাসপেন্ড ফাংশনটি ফিরে না আসা পর্যন্ত এটির সম্পাদন স্থগিত করে । সাসপেন্ড ফাংশন এক্সিকিউশনকে ভিন্ন CoroutineDispatcher এ নিয়ে গেলেও এটি সত্য। আগের উদাহরণে, সাসপেন্ড ফাংশন 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()
        }
    }
) { /* ... */ }

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

@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
                        val offset = awaitPointerEventScope {
                            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)
        )
    }

Coroutines সম্পর্কে আরও জানতে, Android গাইডে Kotlin coroutines দেখুন।

{% শব্দার্থে %} {% endverbatim %} {% শব্দার্থে %} {% endverbatim %}