DisposableEffect হল Jetpack Compose এর একটি composable ফাংশন যা স্পষ্ট আরম্ভ ও পরবর্তী সম্পদ পরিষ্কার প্রয়োজন এমন কাজের জন্য ডিজাইন করা হয়েছে। অন্য side-effect API থেকে ভিন্ন, DisposableEffect onDispose ব্লক প্রদান করে যা কম্পোনেন্ট কম্পোসিশন থেকে বেরিয়ে আসার সময় বা কী পরিবর্তন হলে গ্যারন্টিসহকারে চলে। এটি একে দেশীয় সবস্ক্রিপশন, সেন্সর শ্রোতা ও হার্ডওয়ার সম্পদ নিয়ে কাজ করার জন্য অপরিহার্য করে তোলে। Android Developers Documentation (2025) অনুযায়ী, DisposableEffect ব্যবহার করা উচিত সকল পরিস্থিতিতে যেখানে setup/teardown পেয়ারের প্রয়োজন, Activity জীবনচক্রে onStart/onStop এর অনুরূপ।
মূখ্য বিষয়
DisposableEffect হল Jetpack Compose এ সম্পদ পরিচালনার একটি গুরুত্বপূর্ণ উপকরণ। এর মূখ্য বৈশিষ্ট্য হল composable কম্পোনেন্টের জীবনচক্র শেষে onDispose ব্লকের গ্যারন্টিসহকারে আহ্বান। এই আচরণ Android বিকাশের জন্য অত্যন্ত গুরুত্বপূর্ণ, যেখানে সিস্টেম সার্ভিসে অপরিষ্কৃত সাবস্ক্রিপশন মেমোরি লিক ও অ্যাপ্লিকেশন ক্র্যাশের কারণ হতে পারে।
LaunchedEffect এর বিপরীত, যা একটি কোরুটিনের অসিংক্রোনাস প্রসঙ্গে চলে, DisposableEffect সিংক্রোনাসভাবে চলে। এর অর্থ আপনি এর ভিতরে suspend ফাংশন ডাকতে পারবেন না। সিংক্রোনাস চালু পূর্বানুমানতা নিশ্চিত করে: আপনি নিশ্চিত হতে পারেন যে আরম্ভ কোড প্রথম রেন্ডারের আগে ও পরিষ্কার কোড কম্পোনেন্ট মেমোরি থেকে সরানোর আগে চলে।
Jetpack Compose ডকুমেন্টেশন (2025) অনুযায়ী, DisposableEffect চারটি মূল পরিস্থিতিতে ব্যবহার করা উচিত: (1) সিস্টেম সার্ভিসে (সেন্সর, LocationManager) সাবস্ক্রাইব, (2) BroadcastReceiver নিবন্ধন, (3) কোরুটিন সমর্থন করে না এমন callback-ভিত্তিক লাইব্রেরি নিয়ে কাজ, (4) AndroidView এর মাধ্যমে Compose কম্পোনেন্ট লেগাসি View সিস্টেমের সাথে সংযুক্ত করা।
class SensorManager(private val context: Context) {
fun startListening(callback: (Float) -> Unit) { /* register */ }
fun stopListening() { /* cancel */ }
}
@Composable
fun SensorDisplay() {
val sensorManager = remember { SensorManager(context) }
var value by remember { mutableStateOf(0f) }
DisposableEffect(Unit) {
sensorManager.startListening { value = it }
onDispose { sensorManager.stopListening() }
}
Text("সেন্সর: $value")
}
DisposableEffect এর অন্তরিক ক্রিয়াকলাপ কম্পোসিশন জীবনচক্রের পর্যায়ের উপর ভিত্তিত করা। যখন কোন composable কম্পোনেন্ট কম্পোসিশনে প্রবেশ করে, DisposableEffect পাশ করা কোড ব্লকটি চালায়। এই ব্লক onDispose lambda সম্বলিত একটি DisposableEffectResult অবজেক্ট ফেরত দেয়। কম্পোসিশন এই ফলাফল সংরক্ষণ করে ও কম্পোনেন্ট কম্পোসিশন ছাড়লে onDispose ডাকে — কারণ যাই হোক (নেভিগেশন, পিতৃ অবস্থা পরিবর্তন, LazyColumn থেকে সরানো)।
DisposableEffect এ কী প্রণালী LaunchedEffect এর অনুরূপ কাজ করে: যখন কোন কী পরিবর্তন হয়, প্রথমে onDispose পুরানো অবস্থার জন্য চলে, তারপর আরম্ভ ব্লক নতুন কী নিয়ে পুনরায় চলে। এটি আপনাকে এর প্যারামিটার পরিবর্তন করার সময় সম্পদ পুনর্বিন্যাস করার অনুমতি দেয়। উদাহরণস্বরূপ, যদি কীটি একটি সকেট URL হয়, তবে এটি পরিবর্তন হলে পুরানো সকেট বন্ধ হয় ও একটি নতুন খুলে।
গুরুত্বপূর্ণ: onDispose ব্লক DisposableEffect এর একটি বাদ্যতামূলক উপাদান। যদি আপনি ব্লকের ভিতরে onDispose না ডাকেন, তবে কোড কম্পাইল হবে না। এই কম্পাইলার প্রয়োজন নিশ্চিত করে যে ডিভেলপার সম্পদ পরিষ্কার ভুলে না যায়, যা ম্যানুয়াল সাবস্ক্রিপশন পরিচালনায় ভুলের একটি সাধারণ কারণ।
// একটি কী সহ সঠিক ব্যবহার
DisposableEffect(sensorType) {
val sensor = sensorManager.getDefaultSensor(sensorType)
sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
onDispose {
sensorManager.unregisterListener(listener)
}
}
// একটি DisposableEffect এ একাধিক সম্পদ
DisposableEffect(Unit) {
context.registerReceiver(receiver, intentFilter)
lifecycle.addObserver(observer)
onDispose {
context.unregisterReceiver(receiver)
lifecycle.removeObserver(observer)
}
}
Android অ্যাপ্লিকেশনে মেমোরি লিক প্রায়ই অনিবন্ধিত শ্রোতা ও সাবস্ক্রিপশনের কারণে হয় যা স্ক্রিন বন্ধ হোর পরেও Activity বা Context এর একটি রিফারেন্স ধরে রাখে। DisposableEffect এই সমস্যা ফ্রেমওয়ার্ক স্তরে সমাধান করে: যদি কোন ডিভেলপার একটি শ্রোতা নিবন্ধনের জন্য DisposableEffect ব্যবহার করে, তবে onDispose যে কোনো কম্পোনেন্ট শেষের পরিস্থিতিতে সাবস্ক্রিপশন বাতিল করার গ্যারন্টি দেবে।
এটি বিশেষ করে LazyColumn ও LazyGrid এর জন্য গুরুত্বপূর্ণ, যেখানে ব্যবহারকারী স্ক্রল করার সময় উপাদান ক্রমাগত তৈরি ও ধ্বংস হয়। DisposableEffect ছাড়া, দৃশ্যমান অঞ্চল থেকে অদৃশ্য হোয়া প্রতিটি উপাদান একটি সক্রিয় সাবস্ক্রিপশন রেখে যাবে। DisposableEffect এর সাথে, প্রতিটি আনলোড উপাদানের জন্য onDispose ডাকা হয়, যা নিশ্চিত করে যে উপাদানটি স্ক্রিন ছাড়ার সাথে সাথে সম্পদ মুক্ত হয়ে যায়।
Android Performance Patterns (Google, 2025) অনুযায়ী, সকল দেশীয় সাবস্ক্রিপশনের জন্য DisposableEffect ব্যবহার জীবনচক্র callback মাধ্যমে ম্যানুয়াল পরিচালনার তুলনায় Compose অ্যাপ্লিকেশনে মেমোরি লিকের সংখ্যা 60–70% কমায়। সিস্টেম নিজেই কম্পোনেন্ট কম্পোসিশন ছাড়ার মুহূর্ত ট্রেক করে ও জরুরী স্ক্রিন বন্ধেও onDispose চালুর গ্যারন্টি দেয়।
| সম্পদ | DisposableEffect কি করে | DisposableEffect ছাড়া |
|---|---|---|
| BroadcastReceiver | register + onDispose → unregister | রিসিভার সক্রিয় থাকে |
| SensorManager | registerListener + onDispose → unregisterListener | সেন্সর ডাটা পাঠাতে থাকে |
| Observable (Flow নয়) | subscribe + onDispose → unsubscribe | Callback একটি রিফারেন্স ধরে রাখে |
| TextureView / SurfaceView | setCallback + onDispose → removeCallback | Callback লিক |
| Socket / Channel | open + onDispose → close | সংযোগ খোলা থাকে |
DisposableEffect ব্যবহারের একটি সবচেয়ে উদাহরণমূলক উদাহরণ হল ডিভাইস সেন্সর (অ্যাক্সেলেরোমিটার, গায়রোস্কোপ, ম্যাগনেটোমিটার) নিয়ে কাজ করা। সেন্সরগুলোর কাজ শেষে বাদ্যতামূলক আপন্দিভুক্তির প্রয়োজন, অন্যথা এরা স্ক্রিন বন্ধের পরেও ব্যাটারি শক্তি খরচ করতে থাকে ও ডাটা পাঠাতে থাকে।
ব্যাবহারিক উদাহরণ: একটি কোণ মাপার অ্যাপ্লিকেশন। DisposableEffect(Unit) কম্পোনেন্ট আবির্ভাবে একটি অ্যাক্সেলেরোমিটার শ্রোতা নিবন্ধিত করে ও onDispose এ এটি আপন্দিভুক্ত করে। সেন্সর ডাটা mutableStateOf এর মাধ্যমে অবস্থায় প্রেরণ করা হয়, যা স্বচালিতভাবে UI আপডেট করে। যদি স্ক্রিন LazyColumn এ স্ক্রল করে ও উপাদানটি অদৃশ্য হয়, তবে onDispose তাত্ক্ষণিক চালু হয় — সেন্সর সেই উপাদানের জন্য ডাটা পাঠানো বন্ধ করে দেয়।
সেন্সরের ধরন পরিবর্তনের সময় (যেমন অ্যাক্সেলেরোমিটার থেকে গায়রোস্কোপে), sensorType কীটি পরিবর্তন হয়, onDispose পুরানো সাবস্ক্রিপশন বাতিল করে, ও নতুন DisposableEffect ব্লক নতুন সেন্সর নিবন্ধিত করে। কী ছাড়া, আপনাকে ম্যানুয়ালি পরীক্ষা করতে হবে যে কোন সেন্সর আগে নিবন্ধিত ছিল ও সঠিক শ্রোতার সাথে unregisterListener ডাকতে হবে — যা ত্রুটি-প্রবণ।
@Composable
fun SensorReadingScreen(sensorType: Int) {
val context = LocalContext.current
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
var sensorValue by remember { mutableStateOf(0f) }
DisposableEffect(sensorType) {
val sensor = sensorManager.getDefaultSensor(sensorType)
val listener = SensorEventListener { event, _ ->
sensorValue = event.values[0]
}
sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
onDispose {
sensorManager.unregisterListener(listener)
}
}
Text("মান: $sensorValue")
}
BroadcastReceiver হল API এর একটি ক্লাসিক উদাহরণ যার একটি বাদ্যতামূলক register / unregister পেয়ারের প্রয়োজন। একটি Compose অ্যাপ্লিকেশনে, DisposableEffect একটি নির্দিষ্ট স্ক্রিনের জীবনকালের জন্য একটি রিসিভার নিবন্ধনের জন্য আদর্শ। স্ক্রিনে প্রবেশে, প্রয়োজনীয় IntentFilter সহ একটি BroadcastReceiver নিবন্ধিত হয়; বেরিয়ে আসার সময়, এটি onDispose এ স্বচালিতভাবে বাতিল হয়ে যায়।
একটি নিত্য পরিস্থিতি হল নেটওয়ার্ক অবস্থা নিরীক্ষণ। DisposableEffect ConnectivityManager এর জন্য একটি রিসিভার নিবন্ধিত করে যা নেটওয়ার্ক সংযোগের পরিবর্তন সম্পর্কে অবহিত করে। অবস্থা পরিবর্তনে (WiFi / মোবাইল ডাটা / কোন নেটওয়ার্ক নেই), composable অবস্থা আপডেট হয় ও UI সংশ্লিষ্ট সংকেতক প্রদর্শন করে। স্ক্রিন বন্ধ হলে, onDispose নিবন্ধন বাতিলের গ্যারন্টি দেয় — অ্যাপ্লিকেশন ব্যাকগ্রাউন্ডে গেলেও।
RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED ফ্ল্যাগ (Android 14+) সহ ContextCompat.registerReceiver ব্যবহারকারী রিসিভারের জন্য, DisposableEffect ব্যবহার বাদ্যতামূলক হয় কারণ সিস্টেমের রিসিভারের পরিধির স্পষ্ট নির্দিষ্টি প্রয়োজন। DisposableEffect নিশ্চিত করে যে পরিধি স্ক্রিনের জীবনকালে সীমাবদ্ধ, যা Android এর নতুন সংস্করণগুলোর নিরাপত্তা প্রয়োজনীয়তার সাথে সংগতিপূর্ণ।
@Composable
fun NetworkStatusBanner() {
val context = LocalContext.current
var isConnected by remember { mutableStateOf(true) }
DisposableEffect(Unit) {
val receiver = BroadcastReceiver { _, _ ->
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
isConnected = cm.getActiveNetwork() != null
}
IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).let { filter ->
context.registerReceiver(receiver, filter)
}
onDispose {
context.unregisterReceiver(receiver)
}
}
if (!isConnected) { ... }
}
প্রথম সংকটপূর্ণ ভুলটি হল onDispose কলের অনুপস্থিতি। DisposableEffect ব্লকের ভিতরের কোডকে onDispose ডাকতে হবে, অন্যথা একটি কম্পাইলেশন ত্রুটি হবে। তবে, ডিভেলপাররা কক্ষনো onDispose কে একটি শর্তে রেখে এটি অতিক্রম করার চেষ্টা করে: if (condition) { onDispose { ... } }। এমন কোড কম্পাইল হবে, কিন্তু শর্ত পূরণ না হলে onDispose নিবন্ধিত হবে না — সম্পদ কখনও মুক্ত হবে না।
দ্বিতীয় ভুলটি হল অসিংক্রোনাস কাজের জন্য DisposableEffect ব্যবহার করা। যেহেতু DisposableEffect সিংক্রোনাস, আপনি এর ভিতরে delay() বা await() কল লিখতে পারবেন না। যদি পরবর্তী পরিষ্কার সহ অসিংক্রোনাস আরম্ভের প্রয়োজন হয়, তবে LaunchedEffect (ডাটা লোডিংের জন্য) ও DisposableEffect (দেশীয় সম্পদ সেটআপ/পরিষ্কারের জন্য) এর সম্মিলন ব্যবহার করুন, বা rememberCoroutineScope এর সাথে একটি পৃথক প্রক্রিয়া ব্যবহার করুন।
তৃতীয় ভুলটি হল remember ছাড়া DisposableEffect এর ভিতরে নতুন অবজেক্ট তৈরি করা। যদি অবজেক্ট (সেন্সর, listener, receiver) প্রতিটি কলে ইফেক্টের ভিতরে তৈরি হয় ও কীগুলো পরেস্পর পরিবর্তিত হয়, তবে এটি অতিরিক্ত অবজেক্ট নির্মাণ ও আবজর্জন সংগ্রহণের কারণ হয়। অবজেক্ট নির্মাণ DisposableEffect এর বাইরে remember বা remember { ... } এ সরানো উচিত, ও কেবল ইফেক্টের ভিতরে নিবন্ধিত ও আপন্দিভুক্ত করা উচিত।
প্রায়ই জিজ্ঞাসিত প্রশ্ন
DisposableEffect সিংক্রোনাসভাবে কাজ করে ও সম্পদের স্পষ্ট পরিষ্কারের জন্য onDispose প্রদান করে। LaunchedEffect একটি কোরুটিনে অসিংক্রোনাসভাবে কাজ করে ও কী পরিবর্তন বা কম্পোসিশন থেকে বেরিয়ে আসার সময় স্বচালিতভাবে এটি বাতিল করে। যদি কোন সম্পদের একটি পরিষ্কার পদ্ধতি (close, unregister, dispose) কল করার প্রয়োজন হয় — DisposableEffect ব্যবহার করুন। যদি অপারেশনটি একটি suspend ফাংশন হয় — LaunchedEffect ব্যবহার করুন।
হ্যাঁ, onDispose বাদ্যতামূলক — Kotlin কম্পাইলারের DisposableEffect ব্লকের ভিতরে এটি কল করার প্রয়োজন। যদি আপনি onDispose না ডাকেন, কোড কম্পাইল হবে না। এটি ইচ্ছাকৃতভাবে করা হয়েছে যাতে ডিভেলপাররা ভুলে না যায় ও নিশ্চিত করা যায় যে কম্পোসিশন থেকে বেরিয়ে আসার সময় প্রতিটি খোলা সম্পদ ঠিকভাবে বন্ধ হবে।
DisposableEffect ব্লকের ভিতরে try-catch ব্যবহার করুন। যদি সম্পদ নিবন্ধন একটি ব্যতিক্রম ছুড়তে পারে (যেমন সেন্সর পাওয়া যায় নাি), try এ মুড়িয়ে ফেলুন ও একটি পৃথক অবস্থার মাধ্যমে UI এ ত্রুটি সামাল করুন। আরম্ভের সফলতা নির্বিশেষে onDispose ডাকা উচিত — এটি finally ব্লকে বা try ধারার শেষে রাখুন।
সুপারিশ করা হয় না। Flow এর জন্য, collectLatest সহ LaunchedEffect বা Lifecycle.repeatOnLifecycle এর সাথে .collectAsState() পদ্ধতি ব্যবহার করা উচিত। DisposableEffect suspend ফাংশন সমর্থন করে না, তাই এর ভিতরে Flow এ সাবস্ক্রাইব করার জন্য CoroutineScope এর মাধ্যমে একটি পৃথক কোরুটিন শুরু করার প্রয়োজন হবে, যা কোড জটিল করে ও লিকের বিপদ বাড়ায়।
কোন সীমা নেই, কিন্তু সম্পর্কিত সম্পদগুলো একটি DisposableEffect এ একাধিক অপারেশন ও একটি onDispose এর সাথে গ্রুপ করা সুপারিশ করা হয়। যদি সম্পদগুলো স্বাধীন (যেমন সেন্সর ও BroadcastReceiver), তবে এদের পৃথক কী সহ পৃথক DisposableEffects এ ভাগ করা উচিত — এটি ডিবাগিং সহজ করে ও একটি কী পরিবর্তনে সকল সম্পদের অবাঞ্ছিত পুনর্নির্মাণ প্রতিরোধ করে।
সারাংশ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন