কম্পিউটার টিউটোরিয়াল

Jetpack Compose বেসিক্স মাস্টার করুন: কমপোজেবল ও রিকম্পোজিশন বिगিনার গাইড

এই টিউটোরিয়াল আপনাকে জেটপ্যাক কমপোজ UI লাইব্রেরির সাথে জড়িত কিছু ফান্ডামেন্টাল কনসেপ্ট এবং টার্ম শিখাবে।

এটি কমপোজের জন্য একটি বিগিনার গাইড হলেও, এটি অ্যান্ড্রয়েডের জন্য বিগিনার গাইড নয় – তাই আপনাকে কমপোজে নয় তবুও কমপক্ষে একদুই অ্যাপ বিল্ড করা থাকা উচিত।

শুরু করার আগে, আমি الأوليةভাবে সিনিয়র ডেভেলপারদের দিকে নির্দেশিত একটি ফলো-আপ আর্টিকেল লিখতে প্ল্যান করেছিলাম যতক্ষণ না Leland Richardson-এর দুই partis আর্টিকেল সিরিজে দৃষ্টি পড়েছিলাম। Leland শুধু জেটপ্যাক কমপোজ টিমে কাজ করা একটি সফটওয়্যার ইঞ্জিনিয়ার নন, বরং তিনি একজন দুর্দান্ত লেখকও বলে আমি মনে করি।

যদিও আমার আর্টিকেলটি জেটপ্যাক কমপোজের বেসিক্সের জন্য স্বতন্ত্রভাবে দাঁড়াতে পারবে, আমি দৃৢভাবে পরামর্শ দিচ্ছি কমপোজের সাথে কিছু প্র্যাকটিক্যাল অভিজ্ঞতা পাওয়ার পর (অথবা তुरুনি আপনি যদি সেভাবে শিখতে পছন্দ করেন) তার আর্টিকেলগুলো পড়ুন।

এই আর্টিকেলে ব্যাখ্যা করা কী টার্ম/কনসেপ্ট:

  • পুরনো View System এবং Hierarchy-এর সংক্ষিপ্ত রিভিউ
  • Composables এবং কিভাবে তারা Views-এর সাথে সম্পর্কিত
  • Recomposition এবং কিভাবে এটি খুব খারাপভাবে করা থেকে বচা যায়!

একটি Composable কী?

এই সেকশনে, আমরা Jetpack Compose লাইব্রেরির সবচেয়ে ফান্ডামেন্টাল অংশ নিয়ে আলোচনা করব। আপনি যদি একজন অভিজ্ঞ অ্যান্ড্রয়েড ডেভেলপার হন, তবে আপনি “Are Composables Views?” নামে সাব-সেকশনে স্কিপ করতে পারেন।

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

View Hierarchy

Android SDK-এর কন্টেক্সটে (যে লাইব্রেরিগুলো আমরা এই প্ল্যাটফর্মে ইউজার ইন্টারফেস তৈরি করতে ব্যবহার করি), একটি View হলো যা আমরা আমাদের অ্যাপ্লিকেশনে স্ট্রাকচার এবং স্টাইল দিতে ব্যবহার করি।

এটি একটি দिये गये ইউজার ইন্টারফেস (UI)-এর সবচেয়ে ফান্ডামেন্টাল বিল্ডিং ব্লক বা এলিমেন্ট, এবং এই বিল্ডিং ব্লকগুলো প্রতিটি নিচের ধরনের ইনফরমেশন ধারণ করে (অন্যান্য জিনিসের পাশাপাশি):

  • X এবং Y স্ট্রাট এবং এন্ড পজিশন যা কম্পিউটারকে বলে ডিভাইস স্ক্রিনে ভিউটি কোথায় ড্র করবে
  • রং এবং আলফা (ট্রান্সপেরেন্সি) ভ্যালু
  • ফন্ট ইনফরমেশন, টেক্সট, সিম্বল, এবং ইমেজ
  • ইভেন্টের উপর ভিত্তি করে বিহেভিয়ার যেমন ইউজার ইন্টারঅ্যাকশন (ক্লিক) বা অ্যাপ্লিকেশনের ডেটায় পরিবর্তন (বादে আলোচনা হবে)

বুঝতে গুরুত্বপূর্ণ যে একটি View হতে পারে একটি বাটন (সাধারণত যাকে "widget" বলা হয়), কিন্তু এটি পুরো স্ক্রিন, স্ক্রিনের একটি অংশ, বা অন্যান্য চাইল্ড Views-এর একটি কন্টেইনার ও হতে পারে।

এই ধরনের কন্টেইনার সাধারণত Layouts বা Viewgroups বলা হয় কন্টেক্সট অনুযায়ী। এবং, উইজেটের মত অধিকাংশ ইনফরমেশন শেয়ার করার ওপরান্তে, সেগুলো নেস্টেড অন্যান্য Views কিভাবে অরেঞ্জ এবং ডিসপ্লে করবেন সেটার ইনফরমেশনও ধারণ করে।

এটা ধরে, আমরা এই View system রিভিউর গুরুত্বপূর্ণ অংশে পৌঁছেছি: View Hierarchy। Web Developers-এর জন্য, View Hierarchy আসলে Android-এর Document Object Model (DOM) এর ভার্সন।

Android Developers-এর জন্য, আপনি View Hierarchy কে আপনার XML ফাইলগুলিতে বা জাভা বা কোটলিনে প্রোগ্রামেটিক্যালি ডিফাইন করা সকল Views-এর ভার্চুয়াল রিপ্রেজেন্টেশন হিসেবে চিন্তা করতে পারেন।

এটি ইলাস্ট্রেট করার জন্য, আসুন একটি XML ফাইল দেখি (এটা ঘন ঘন অধ্যয়ন করার দরকার নয়, শুধু নামগুলো লক্ষ্য করুন)। তারপর, একটি ডিবাগার/স্টেপার টুল ব্যবহার করে, আমরা দেখব এটি Fragment-এর মেমরি স্পেসে কেমন দেখে যেটি এই ফাইলটি ইনফ্লেট করে:

fragment_hour_view.xml:

<?xml version=”1.0" encoding=”utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android=”https://schemas.android.com/apk/res/android" android:layout_width=”match_parent” android:layout_height=”match_parent” android:id=”@+id/root_hour_view_fragment” xmlns:app=”https://schemas.android.com/apk/res-auto" > <androidx.compose.ui.platform.ComposeView android:id=”@+id/tlb_hour_view” //... /> <com.wiseassblog.samsaradayplanner.ui.managehourview.HourToggleView android:id=”@+id/vqht_one” //... /> <com.wiseassblog.samsaradayplanner.ui.managehourview.HourToggleView android:id=”@+id/vqht_two” //... /> <com.wiseassblog.samsaradayplanner.ui.managehourview.HourToggleView android:id=”@+id/vqht_three” //... /> <com.wiseassblog.samsaradayplanner.ui.managehourview.HourToggleView android:id=”@+id/vqht_four” //... /> </androidx.constraintlayout.widget.ConstraintLayout>

Memory Space of (Fragment)HourView.kt:

Jetpack Compose বেসিক্স মাস্টার করুন: কমপোজেবল ও রিকম্পোজিশন বिगিনার গাইড
View Hierarchy এর ছবি

ডিবাগার এবং স্টেপার টুলগুলো আমার প্রিয় উপায়ोंতে একটি যা আমি বিভিন্ন লাইব্রেরি থেকে যে কোড ব্যবহার করি সেটার আন্ডার দ হুড কী হচ্ছে সেটা শিখতে। একবার ট্রাই করুন!

এই XML ফাইলটি এবং এটি একটি প্রসেস (একটি প্রসেস হলো সহজ ভাষায় একটি চলমান প্রোগ্রাম) এ কীতে রূপ নেয় সেটা দেখানোর উদ্দেশ্য হলো নেস্টেড Views XML ফাইলে কিভাবে রানটাইমে নেস্টেড View Hierarchy-তে রূপ নেয় সেটা দেখানো।

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

Composables কি Views?

এটি আমার প্রথম প্রশ্নের একটি ছিল যখন আমি Compose-এ কাজ শুরু করেছিলাম, এবং আমি যে জবাবে পৌঁছেছি সেটি হ্যাঁ এবং না দুটোই।

হ্যাঁ, এই অর্থে যে একটি Composable পুরনো সিস্টেমে View-এর সেইম কনসেপচুয়াল রোল পূরণ করে। একটি Composable হতে পারে একটি উইজেট যেন বাটন, বা একটি কন্টেইনার যেন ConstraintLayout (এটি নোট করা যাক যে ConstraintLayout-এর একটি কমপোজেবল ইমপ্লিমেন্টেশন উপলব্ধ)।

না, এই অর্থে যে UI আর ভার্চুয়ালি একটি View Hierarchy-তে রিপ্রেজেন্টেড নয় (ইন্টারঅপারেবিলিটি জড়িত পরিস্থিতিগুলো বাদে)।そう言うと, Compose ম্যাজিক ব্যবহার করে না ভার্চুয়ালি UI রিপ্রেজেন্ট এবং ট্র্যাক রাখতে। এর মানে সেটার নিজস্ব কিছু আছে যা কনসেপচুয়ালি View Hierarchy-এর মত।

আসুন এটাকে খুব সংক্ষেপে দেখি। এখানে, আমাদের একটি Activity আছে যা setContent {…} ফাংশন ব্যবহার করে একটি কমপোজেবলকে নিজের সাথে বাইন্ড করে:

ActiveGameActivity.kt:

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(it) }, viewModel ) } logic = buildActiveGameLogic(this, viewModel, applicationContext) } //… }

ActiveGameScreen.kt:

@Composable fun ActiveGameScreen( onEventHandler: ((ActiveGameEvent) -> Unit), viewModel: ActiveGameViewModel ) { //... GraphSudokuTheme { Column( Modifier .background(MaterialTheme.colors.primary) .fillMaxHeight() ) { ActiveGameToolbar( clickHandler = { onEventHandler.invoke( ActiveGameEvent.OnNewGameClicked ) } ) Box { //content } } } }

Compose-এ, View Hierarchy রিপ্লেস হয়েছে এমন কিছু দিয়ে যা আমরা খুঁজে পেতে পারি যদি এই Activity-এর mWindow ফিল্ডে খুব গভীরে ডাব করি। সেই ফিল্ডের ভিতরে থাকে View Hierarchy-এর কনসেপচুয়াল রিপ্লেসমেন্ট: The Composer এবং তার slotTable।

Jetpack Compose বেসিক্স মাস্টার করুন: কমপোজেবল ও রিকম্পোজিশন বिगিনার গাইড

এই মুহূর্তে, আপনি যদি Composer এবং এর slotTable-এর বিস্তারিত ওভারভিউ চান, তবে আমি আবার Leland-এর আর্টিকেল পড়ার পরামর্শ দিচ্ছি (সে পার্ট ২-তে বিস্তারিত বলেছেন)। Compose Hierarchy-তে Composer এবং এর slotTable-এর বাইরেও আরও কিছু আছে, তবে শুরু করার জন্য এটাই পর্যাপ্ত।

সাধারণভাবে, Jetpack Compose ব্যবহার করে যা আমরা Compose Hierarchy বলতে পারি (যেটি Composer এবং এর slotTable-এর মতো জিনিসগুলো বানানো এবং ম্যানেজ করা হয়)।

আবার, এটি View hierarchy-এর সেম কনসেপচুয়াল আইডিয়া, মেমরি স্পেসে অবজেক্টের একটি গুচ্ছ যেগুলো মিলে UI রিপ্রেজেন্ট করে, কিন্তু এটি ভিন্নভাবে ইমপ্লিমেন্টেড।

একটি গুরুত্বপূর্ণ পার্থক্য আছে, যেটা টেকনিক্যালি বুঝতে কঠিন কিন্তু প্রিন্সিপ্যালি সহজ। এটিই হলো Compose Compose Hierarchy-তে আপডেট হ্যান্ডেল করে: Recomposition।

Recomposition: Compose UI আপডেট করার পদ্ধতি

আমার ESL বন্ধুদের জন্য, Compose শব্দটি ল্যাটিন componere থেকে এসেছে, যার রফ মানে "একসাথে রাখা"। কেউ কেউ যারা সংগীত রচনা করেন তাদেরকে "Composer" বলা হয়, যাকে চিন্তা করা যায় সে ব্যক্তি যে এক বা একাধিক ইন্সট্রুমেন্ট থেকে আসা নোটগুলো একসাথে রেখে একটি কমপোজিশন (গান) বানায়।

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

আমি যুক্তিসঙ্গত বললাম, কারণ আমি মনে করি DRY (Don’t Repeat Yourself) এর মত প্রিন্সিপলগুলো ততটুকু ফলো করা উচিত যতটুকু সেগুলো সমস্যা সৃষ্টি করার চেয়ে বেশি সমস্যা সমাধান করে।

এই কনসেপ্ট অ্যাপ্লাই করার অনেক বেনিফিট আছে, যাকে মডুলারিটি বলা হয় (অথবা যেটি আমি পছন্দ করি, Separation of Concerns, বা SOC)। আমি অবগত যে আপনাদের মধ্যে কিছু লোক মনে করতে পারেন আমি শুধু Leland তার আর্টিকেলে যা বলেছেন সেটা কপি করছি, কিন্তু আমি SOC কে যেকোনো ভালো আর্কিটেকচারের গোল্ডেন প্রিন্সিপল বলে অনেক বছর ধরে কথা বলছি।

এটি কীভাবে Compose-এ প্লে করে, সেটি পপুলার জাভাস্ক্রিপ্ট লাইব্রেরি React-এ যা দেখি সেম প্রিন্সিপল। যথাযথভাবে করলে, Compose শুধু সেই কমপোজেবলগুলো "রিকম্পোজ" করবে (রিড্র, রি-রেন্ডার, আপডেট, যা বলুন) যেগুলো রিকম্পোজ করার দরকার।

এটি একটি অ্যাপ্লিকেশনের পারফরম্যান্সের জন্য ENORMOUSLY গুরুত্বপূর্ণ। কারণ UI রিড্র করা, পুরনো View system-এ হোক বা Compose-এ, সিস্টেম রিসোর্সের জন্য খুব ব্যয়বহুল।

আপনি যদি না জানেন, পুরনো RecyclerView-এর সম্পূর্ণ উদ্দেশ্য (যেটি ২০১৬-এ আমি প্রথম টিউটোরিয়াল বান 않았던!) হলো ViewHolder প্যাটার্ন একটি ডেটা লিস্টে অ্যাপ্লাই করা। এটি প্রতিটি লিস্ট আইটেমের জন্য নতুন Views ইনফ্লেট (বানানো) করার প্রয়োজন আপস করে দিয়েছিল।

এই আর্টিকেলে আমার লক্ষ্য ছিল মেজর দিক теорииতে ফোকাস করা, কারণ আমি আগামী কয়েক মাসে প্র্যাকটিক্যাল কন্টেন্ট প্রচুর লিখব। য totsometimes, আমি আর্টিকেলটি আমার ডায়রেক্ট অভিজ্ঞতা থেকে একটি গল্প দিয়ে সমাপ্ত করব, যা আপনাকে আরও ভালোভাবে বুঝতে সাহায্য করবে রিকম্পোজিশন কীভাবে কাজ করে, এবং কিভাবে এটি খুব খারাপভাবে করা থেকে বचना যায়!

স্টপওয়াচ উদাহরণ

আমার প্রথম ফুল কমপোজ অ্যাপ্লিকেশনের জন্য, আমি সুডোকু বিল্ড করতে সিদ্ধান্ত নিলাম। এর কারণ عدة, যার মধ্যে একটি হলো আমি এমন একটি প্রজেক্ট চেয়েছিলাম যেটির UI Безবাধ কমপ্লিকেটেড না। আমি গ্রাফ DS এবং অ্যালগোরিদমে ডিপ ডাইভ করার সুযোগও চেয়েছিলাম, যেটি সুডোকু পাজলের জন্য khá উপযুক্ত।

একটা জিনিস আমি চেয়েছিলাম একটি Stopwatch যা ট্র্যাক করবে ইউজারকে পাজল কমপ্লিট করতে কতটুকু সময় লাগল:

Jetpack Compose বেসিক্স মাস্টার করুন: কমপোজেবল ও রিকম্পোজিশন বिगিনার গাইড
Graph Sudoku puzzle

আমার প্রফেশনে যেমনটা ভাবছিলাম, এই টাইমারটা অনেক সহজে যোগ করা যাবে বলে ভাবেছিলাম। আমি অ্যান্ড্রয়েডের Chronometer ক্লাস এবং Java Timer ক্লাস দিয়ে এক্সপেরিমেন্ট করলাম, এবং উভয়ই আলাদা আলাদা কিন্তু এপ্লিকেশন-ব্রেকিং সমস্যা উপস্থাপন করল।

এন্ডে আমি একটু পিছনে হটে বুঝলাম যে আমি কোটলিনে লিখছি। তাই আমার প্রেজেন্টেশন লজিক ক্লাসে (সেটি সবচেয়ে সেন্সেবল লাগল সেটার জন্য) একটি কোরুটিন-ভিত্তিক টাইমার সেট আপ করলাম, যা প্রতি সেকেন্ডে আমার ভিউমডেল আপডেট করত:

Class ActiveGameLogic(…):…{ //… inline fun startCoroutineTimer( delayMillis: Long = 0, repeatMillis: Long = 1000, crossinline action: () -> Unit ) = launch { delay(delayMillis) if (repeatMillis > 0) { while (true) { action() delay(repeatMillis) } } else { action() } } private fun onStart() = launch { gameRepo.getCurrentGame( { puzzle, isComplete -> viewModel.initializeBoardState( puzzle, isComplete ) if (!isComplete) timerTracker = startCoroutineTimer { viewModel.updateTimerState() } },{ container?.onNewGameClick() }) } //… }

ViewModel (AAC থেকে নয় – আমি নিজের VM লিখি। কিন্তু Compose-এ ইতিমধ্যে AAC VM-এর সাথে ভালো ইন্টারঅপারেবিলিটি আছে যা আমি দেখতে পাচ্ছি।) কলব্যাক ফাংশনগুলোতে রেফারেন্স এক্সপোজ করত, যেগুলো আমি আমার কমপোজেবলস আপডেট করতে ব্যবহার করতাম:

class ActiveGameViewModel { //… internal var subTimerState: ((Long) -> Unit)? = null internal var timerState: Long = 0L //… internal fun updateTimerState(){ timerState++ subTimerState?.invoke(timerState) } //… }

এখন গুরুত্বপূর্ণ অংশ আসছে! Compose-র কিছু ফিচার যেমন remember ফাংশন ব্যবহার করে আমরা Compose Hierarchy-এর রিকম্পোজিশন ট্রিগার করতে পারি:

var timerState by remember { mutableStateOf(“”) }

আপনি যদি জানতে চান, এই ফিচারগুলো যেই স্টেটটি আপনি রিমেম্বার করছেন সেটি slotTable-এ সংরক্ষণ করে। সংক্ষেপে, এখানে state শব্দটি ডেটার বর্তমান "state"-কে বোঝায়, যা শুধু একটি খালি String-তে শুরু হয়।

এখানেই আমি গোলামাল করলাম। আমি আমার সিম্পল টাইমার কমপোজেবলকে তার নিজস্ব ফাংশনে টেনে আনলাম (SOC অ্যাপ্লাই করলাম), এবং timerState সেই কমপোজেবলে প্যারামিটার হিসেবে পাস করতাম।

তবে, উপরের স্নিপেটগুলো টাইমারের প্যারেন্ট কমপোজেবলে বসে থাকত, যা UI-এর সবচেয়ে জটিল অংশের (৯x৯ সুডোকুতে অনেক উইজেট লাগে) কন্টেইনার ছিল:

@Composable fun GameContent( onEventHandler: (ActiveGameEvent) -> Unit, viewModel: ActiveGameViewModel ) { Surface( Modifier .wrapContentHeight() .fillMaxWidth() ) { BoxWithConstraints(Modifier.background(MaterialTheme.colors.primary)) { //… ConstraintLayout { val (board, timer, diff, inputs) = createRefs() var isComplete by remember { mutableStateOf(false) } var timerState by remember { mutableStateOf("") } viewModel.subTimerState = { timerState = it.toTime() } viewModel.subIsCompleteState = { isComplete = it } //…Sudoku board //Timer Box(Modifier .wrapContentSize() .constrainAs(timer) { top.linkTo(board.bottom) start.linkTo(parent.start) } .padding(start = 16.dp)) { TimerText(timerState) } //…difficulty display //…Input buttons } } } } @Composable fun TimerText(timerState: String) { Text( text = timerState, style = activeGameSubtitle.copy(color = MaterialTheme.colors.secondary) ) }

এই কারণেই কিছু قابل ল্যাগ এবং অনরেসপন্সিভনেস হতো। ডিবাগার हेवीলি ব্যবহার করে আমি বের করতে পারলাম কেন। আমার timerState ভেরিয়েবল প্যারেন্ট কমপোজেবলের ভিতরে তৈরি ও আপডেট হচ্ছিল, তাই সেটি UI-এর সে পুরো অংশকে রিকম্পোজ করত। প্রতি. একক. টিক।

উপযুক্ত কোডটি TimerText কমপোজেবলে সরিয়ে দিলে সব কিছু খুব স্মুথলি কাজ করল:

@Composable fun TimerText(viewModel: ActiveGameViewModel) { var timerState by remember { mutableStateOf("") } viewModel.subTimerState = { timerState = it.toTime() } Text( text = timerState, style = activeGameSubtitle.copy(color = MaterialTheme.colors.secondary) ) }

আশা করি আমি আপনাকে রিকম্পোজিশন এবং এটি খুব খারাপভাবে করার সবচেয়ে বড় উপায়ের একটি সম্পর্কে একটি ওয়ার্কিং আন্ডারস্ট্যান্ডিং দিয়েছি।

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

রিসোর্স ও সাপোর্ট

আপনি যদি এই আর্টিকেল পছন্দ করেন, তবে সোশ্যাল মিডিয়াতে শেয়ার করুন এবং freeCodeCamp-এ আমার অন্যান্য আর্টিকেল চেক আউট করুন। আমার একটি ইউটিউব চ্যানেল আছে সैकੜো টিউটোরিয়ালের সাথে, এবং আমি বিভিন্ন প্ল্যাটফর্মে একটিভ রাইٹر।

সোশ্যাল মিডিয়ায় আমার সাথে কানেক্ট হন

আপনি আমাকে ইনস্টাগ্রামে এখানে এবং টুইটারে এখানে পাবেন।

এছাড়া, আমি Jetpack Compose-এ শুরু করার জন্য যে একক রিসোর্সটি ব্যবহার করলাম সেটি নির্দেশ করতে চাই: ভালো ডেভেলপারদের থেকে ওয়ার্কিং কোড স্যাম্পল।

  1. উইন্ডোজ ১০ পিসি ব্যবহার গাইড: নতুন ব্যবহারকারীদের জন্য মৌলিক টিউটোরিয়াল ও টিপস

  2. অ্যান্ড্রয়েডে আইফোন ইমোজি দেখার পদ্ধতি

  3. Android Visibility Listeners মাস্টার করুন: ভিউ দৃশ্যমানতা ট্র্যাক করার পদ্ধতি ও কারণ

  4. কোটলিন অ্যান্ড্রয়েড অ্যানিমেশনকে অ্যাক্সেসিবল করা: একটি প্র্যাক্টিক্যাল গাইড