এই সংক্ষিপ্ত এবং প্র্যাকটিক্যাল আর্টিকেলে, আমরা Jetpack Compose-এ UI ইভেন্ট হ্যান্ডলিং নিয়ে কথা বলব।
পুরনো সিস্টেমে, আমরা OnClickListener এবং অন্যান্য ইন্টারফেস ব্যবহার করতাম। Compose-এ, আমরা কোটলিনের Sealed Classes, Function Types এবং Lambda Expressions-র পূর্ণ সুবিধা নিতে পারি।
আপনি যদি কমপোজেবল কী সেটা না জানেন, তবে এই আর্টিকেল পড়ুন যা ফান্ডামেন্টালস ব্যাখ্যা করে।
Sealed Class দিয়ে UI ইভেন্ট মডেল করা
প্রথমে, আমরা বুঝতে হবে UI ইভেন্ট কী এবং সেগুলোকে Sealed Classes দিয়ে কিভাবে মডেল করব।
আমি এই প্রক্রিয়াটি জাভা এবং কোটলিনের জন্য (পুরনো ভিউ সিস্টেমের সাথে) আগে বর্ণনা করেছি, তাই এখানে সংক্ষিপ্ত রাখব।
প্রক্রিয়াটি
আপনার UI-এর প্রতিটি স্ক্রিন বা সাব-স্ক্রিনের জন্য, নিজে থেকে এই প্রশ্ন করুন: ইউজার এর সাথে ইন্টারঅ্যাক্ট করার সব আলাদা উপায় কী কী?
আমার প্রথম volledig কমপোজে বিল্ট অ্যাপ, Graph Sudoku থেকে একটি উদাহরণ নিই:
এই স্ক্রিনের UI ইন্টারঅ্যাকশনগুলোকে রিপ্রেজেন্ট করার জন্য আমি যে sealed class ব্যবহার করি সেটি এরকম:
sealed class ActiveGameEvent { data class OnInput(val input: Int) : ActiveGameEvent() data class OnTileFocused(val x: Int, val y: Int) : ActiveGameEvent() object OnNewGameClicked : ActiveGameEvent() object OnStart : ActiveGameEvent() object OnStop : ActiveGameEvent() }
সংক্ষেপে ব্যাখ্যা করলে:
- OnInput রিপ্রেজেন্ট করে ইউজার একটি ইনপুট বাটন টাচ করল (যেমন 0, 1, 2, 3, 4)
- OnTileFocused রিপ্রেজেন্ট করে ইউজার একটি টাইল সিলেক্ট করল (যেমন আম্বর হাইলাইটেড টি)
- OnNewGameClicked স্বয়ংক্রিয়ভাবে বোঝা যায়
- OnStart এবং OnStop হলো লাইফসাইকেল ইভেন্ট যেগুলো আমার কমপোজেবলসকে চিন্তা নয়, কিন্তু এক্টিভিটিতে ব্যবহার হয় যা কমপোজেবলসের কন্টেইনার হিসেবে কাজ করে
একবার আপনার sealed class সেট আপ হলে, আপনি একটি সিঙ্গল ইভেন্ট হ্যান্ডলার ফাংশন দিয়ে ধরণের ইভেন্ট হ্যান্ডেল করতে পারেন। কখনও কখনও একাধিক ইভেন্ট হ্যান্ডলার ফাংশন বেশি সেন্স করতে পারে, তাই মনে রাখবেন যে এই পদ্ধতিটি আপনার প্রজেক্টের নির্দিষ্ট রিকোয়ারমেন্ট অনুযায়ী адап্ট করতে হবে।
আপনার সফটওয়্যার আর্কিটেকচার সংযুক্ত করা
এই ইভেন্টগুলো হ্যান্ডেল করবে কী, সেটা সম্পূর্ণ আপনার ওপর। কেউ কেউ মনে করেন MVVM সফটওয়্যার আর্কিটেকচারের গোল্ডেন স্ট্যান্ডার্ড, কিন্তু লাগছে যেন সবাই বুঝতে শুরু করছেন প্রতিটি পরিস্থিতিতে কাজ করার জন্য কোনো সিঙ্গল আর্কিটেকচার নেই।
অ্যান্ড্রয়েড কমপোজের সাথে, আমার বর্তমান পদ্ধতি হলো একটি খুব থার্ড পার্টি মিনিমালিস্ট পদ্ধতি যেটিতে প্রতিটি ফিচার (স্ক্রিন)-এ সাধারণত এই জিনিসগুলো থাকে:
- একটি (প্রেজেন্টেশন) লজিক ক্লাস ইভেন্ট হ্যান্ডলার হিসেবে
- একটি ViewModel ভিউ রেন্ডার করতে প্রয়োজনীয় ডেটা সংরক্ষণ করার জন্য (নাম অনুযায়ী)
- একটি Activity যা কন্টেইনার হিসেবে কাজ করে (গড অবজেক্ট নয়)
- ভিউ তৈরির জন্য কমপোজেবলস
আপনি কী ব্যবহার করুন সেটা আমার কাছে কিছু মেটার করে না, যতক্ষণ পর্যন্ত আপনি separation of concerns অ্যাপ্লাই করছেন। আমি এই আর্কিটেকচারে পৌঁছেছি সিম্পলি জিজ্ঞেস করে যে একই ক্লাসে কী রাখা উচিত এবং কী না।
আপনি চাইলে আপনার ViewModel, একটি Fragment, বা একটি Activity কে আপনার ইভেন্ট হ্যান্ডলার বানাতে পারেন, সবগুলো একইভাবে সেট আপ করা যায়: Function Types!
আপনার পছন্দের ক্লাসের ভিতরে, আপনার sealed class আর্গুমেন্ট হিসেবে নেওয়া একটি ইভেন্ট হ্যান্ডলার ফাংশন সেট আপ করুন:
class ActiveGameLogic( private val container: ActiveGameContainer?, private val viewModel: ActiveGameViewModel, private val gameRepo: IGameRepository, private val statsRepo: IStatisticsRepository, dispatcher: DispatcherProvider ) : BaseLogic<ActiveGameEvent>(dispatcher), CoroutineScope { //... override fun onEvent(event: ActiveGameEvent) { when (event) { is ActiveGameEvent.OnInput -> onInput( event.input, viewModel.timerState ) ActiveGameEvent.OnNewGameClicked -> onNewGameClicked() ActiveGameEvent.OnStart -> onStart() ActiveGameEvent.OnStop -> onStop() is ActiveGameEvent.OnTileFocused -> onTileFocused(event.x, event.y) } } //... }
এই পদ্ধতিটি খুব অর্গানাইজড এবং এই থার্ড পার্টি লাইব্রেরি-মুক্ত ক্লাসের প্রতিটি ইউনিট একটি সিঙ্গল এন্ট্রি পয়েন্ট দিয়ে টেস্ট করা সহজ করে তোলে।
তবে, আমরা এখনও শেষ করিনি। স্বাভাবিকভাবেই, আমাদের এই ইভেন্ট হ্যান্ডলার ফাংশন, onEvent, কে আমাদের কমপোজেবলসে রেফারেন্স দিতে হবে। সেটা আমরা একটি ফাংশন রেফারেন্স দিয়ে করতে পারি:
class ActiveGameActivity : AppCompatActivity(), ActiveGameContainer { private lateinit var logic: ActiveGameLogic override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val viewModel = ActiveGameViewModel() setContent { ActiveGameScreen( onEventHandler = logic::onEvent, viewModel ) } logic = buildActiveGameLogic(this, viewModel, applicationContext) } //... }
আমি জানি আপনাদের কিছু লোক ভাবছেন কেন আমি Activity ব্যবহার করছি। আপনি কখনও লাইভস্ট্রিম Q&A-এ জিজ্ঞেস করতে পারেন বিস্তারিত জবাবের জন্য।
সংক্ষেপে, ফ্র্যাগমেন্টস কমপোজের সাথে আমার আর্কিটেকচার পদ্ধতিতে কিছুটা পয়েন্টলেস মনে হয় (আমি Jetpack Navigation ব্যবহার করি না), এবং ফিচার-স্পেসিফিক কন্টেইনার হিসেবে Activity ব্যবহার করতে কিছু ভুল নেই। শুধু গড এক্টিভিটি লিখবেন না, মূলত।
স্পেসিফিক হতে, কোটলিনে একটি ফাংশনের রেফারেন্স बनाने পদ্ধতিটি হলো ক্লাস/ইন্টারফেসের নাম (অথবা সেটি স্কিপ করুন যদি এটি টপ-লেভেল ফাংশন হয়), এরপর দুটি কোলন, এবং ফাংশনের নাম কোনো আর্গুমেন্ট বা ব্র্যাকেট ছাড়া:
onEventHandler = logic::onEvent
onClickListener কে Jetpack Compose onClick Modifier দিয়ে রিপ্লেস করা
সেটা রেডি থাকলে, আমরা কমপোজেবলের ভিতরে এটি কিভাবে কাজ করে সেটা দেখি। স্বাভাবিকভাবেই, আপনার রুট কমপোজেবলকে ইভেন্ট হ্যান্ডলার ফাংশন একটি প্যারামিটার হিসেবে দরকার:
@Composable fun ActiveGameScreen( onEventHandler: (ActiveGameEvent) -> Unit, viewModel: ActiveGameViewModel ) { //... }
ফাংশন টাইপ সিনট্যাক্স সঠিকভাবে পাওয়া একটু ট্রিকি হতে পারে, কিন্তু বুঝতে হবে যে এটি সত্যিই একটি ফাংশনের রেফারেন্স, যা একটি ক্লাসের রেফারেন্স থেকে সplier আলাদা নয়।
আপনার গড অবজেক্ট বানাতে না পারার মত, আপনার জায়ান্ট কমপোজেবলও বানাতে পারবেন না:
- আপনার UI কে সবচেয়ে ছোট যুক্তিসঙ্গত অংশে ভাঙুন
- সেগুলোকে একটি কমপোজেবল ফাংশনে র্যাপ করুন
- প্রতিটি কমপোজেবলে যেটির সাথে UI ইন্টারঅ্যাকশন জড়িত, সেটিকে অবশ্যই আপনার ইভেন্ট হ্যান্ডলার ফাংশনের একটি রেফারেন্স দিতে হবে
এখানে একটি কমপোজেবল আছে যা সুডোকু অ্যাপের ইনপুট বাটনগুলকে রিপ্রেজেন্ট করে, যেটিকে ইভেন্ট হ্যান্ডলার রেফারেন্স দিয়ে দেয়া হয়েছে:
@Composable fun SudokuInputButton( onEventHandler: (ActiveGameEvent) -> Unit, number: Int ) { Button( onClick = { onEventHandler.invoke(ActiveGameEvent.OnInput(number)) }, modifier = Modifier .requiredSize(56.dp) .padding(2.dp) ) { Text( text = number.toString(), style = inputButton.copy(color = MaterialTheme.colors.onPrimary), modifier = Modifier.fillMaxSize() ) } }
ইভেন্টটি আসলে লজিক ক্লাসে পাস করতে, আমাদের invoke ফাংশন ব্যবহার করতে হবে, যা আর্গুমেন্ট গ্রহণ করবে ফাংশন টাইপ ডেফিনিশন অনুযায়ী (যেটি এই কেসে ActiveGameEvent গ্রহণ করে)।
এই পয়েন্টে, আপনি কোটলিনে (কমপোজ নাকি নাকি) UI ইন্টারঅ্যাকশন ইভেন্ট হ্যান্ডেল করার জন্য রেডি, এই সুন্দর এবং আধুনিক প্রোগ্রামিং ল্যাঙ্গুয়েজের পূর্ণ সুবিধা নিচ্ছেন।
আপনি যদি এই আর্টিকেল পছন্দ করেন, তবে সোশ্যাল মিডিয়াতে শেয়ার করুন এবং নিচের রিসোর্সগুলো চেক আউট করে একটি স্বাধীন প্রোগ্রামার এবং কন্টেন্ট ক্রিয়েটরকে সাপোর্ট করার কথা ভাবুন।
Social
আপনি আমাকে ইনস্টাগ্রামে এখানে এবং টুইটারে এখানে পাবেন।
আমার কিছু টিউটোরিয়াল ও কোর্স
https://youtube.com/wiseass https://www.freecodecamp.org/news/author/ryan-michael-kay/ https://skl.sh/35IdKsj (Android Studio-তে অ্যান্ড্রয়েডের পরিচিতি)