DisposableEffect είναι μια composable-συνάρτηση στο Jetpack Compose που προορίζεται για λειτουργίες που απαιτούν ρητή αρχικοποίηση και επακόλουθο καθαρισμό πόρων. Σε αντίθεση με άλλα side-effect API, το DisposableEffect παρέχει ένα μπλοκ onDispose που εκτελείται εγγυημένα όταν το component εξέρχεται από τη σύνθεση ή όταν αλλάζει το κλειδί. Αυτό το καθιστά απαραίτητο για εργασία με εγγενείς συνδρομές, ακροατές αισθητήρων και πόρους υλικού. Σύμφωνα με το Android Developers Documentation (2025), το DisposableEffect συνιστάται σε όλα τα σενάρια όπου απαιτείται ζεύγος setup/teardown, ανάλογο με onStart/onStop στον κύκλο ζωής Activity.
Κύρια σημεία
DisposableEffect είναι ένα βασικό εργαλείο για τη διαχείριση πόρων στο Jetpack Compose. Το κύριο χαρακτηριστικό του είναι η εγγυημένη κλήση του μπλοκ onDispose κατά το τέλος του κύκλου ζωής του composable-component. Αυτή η συμπεριφορά είναι κρίσιμη για την ανάπτυξη Android, όπου οι μη κλεισμένες συνδρομές σε υπηρεσίες συστήματος μπορεί να οδηγήσουν σε διαρροές μνήμης και κατάρρευση της εφαρμογής.
Σε αντίθεση με το LaunchedEffect, που λειτουργεί σε ασύγχρονο περιβάλλον coroutine, το DisposableEffect εκτελείται σύγχρονα. Αυτό σημαίνει ότι δεν μπορούν να κληθούν συναρτήσεις suspend μέσα σε αυτό. Ο συγχρονισμός εξασφαλίζει προβλεψιμότητα: μπορείτε να είστε σίγουροι ότι ο κώδικας αρχικοποίησης θα εκτελεστεί πριν από την πρώτη απόδοση, και ο κώδικας καθαρισμού — πριν αφαιρεθεί το component από τη μνήμη.
Σύμφωνα με την Τεκμηρίωση Jetpack Compose (2025), το DisposableEffect πρέπει να χρησιμοποιείται σε τέσσερα κύρια σενάρια: (1) εγγραφή σε υπηρεσίες συστήματος (αισθητήρες, LocationManager), (2) καταχώρηση BroadcastReceiver, (3) εργασία με βιβλιοθήκες βασισμένες σε callback που δεν υποστηρίζουν coroutines, (4) σύνδεση components Compose με συστήματα Legacy View μέσω AndroidView.
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-component εισέρχεται στη σύνθεση, το DisposableEffect καλεί το μεταβιβαζόμενο μπλοκ κώδικα. Αυτό το μπλοκ επιστρέφει ένα αντικείμενο DisposableEffectResult που περιέχει το lambda onDispose. Η σύνθεση αποθηκεύει αυτό το αποτέλεσμα και καλεί το onDispose τη στιγμή που το component εγκαταλείπει τη σύνθεση — ανεξάρτητα από την αιτία (πλοήγηση, αλλαγή κατάστασης γονέα, αφαίρεση από LazyColumn).
Ο μηχανισμός κλειδιών στο DisposableEffect λειτουργεί ανάλογα με το LaunchedEffect: κατά την αλλαγή οποιουδήποτε κλειδιού, πρώτα εκτελείται το onDispose για την παλιά κατάσταση, στη συνέχεια το μπλοκ αρχικοποίησης επανεκκινείται με τα νέα κλειδιά. Αυτό επιτρέπει την εκ νέου διαμόρφωση του πόρου όταν αλλάζουν οι παράμετροί του. Για παράδειγμα, αν το κλειδί είναι το URL του socket, κατά την αλλαγή του το παλιό socket κλείνει και ένα νέο ανοίγει.
Σημαντικό: το μπλοκ onDispose είναι υποχρεωτικό στοιχείο του DisposableEffect. Αν δεν κληθεί το onDispose μέσα στο μπλοκ, ο κώδικας δεν θα μεταγλωττιστεί. Αυτή η απαίτηση του compiler διασφαλίζει ότι ο προγραμματιστής δεν θα ξεχάσει να προβλέψει τον καθαρισμό του πόρου, που είναι συνηθισμένη αιτία σφαλμάτων στη χειροκίνητη διαχείριση συνδρομών.
// Σωστή χρήση με κλειδί
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 λύνει αυτό το πρόβλημα σε επίπεδο framework: αν ο προγραμματιστής χρησιμοποίησε DisposableEffect για την καταχώρηση ενός ακροατή, το onDispose θα ακυρώσει εγγυημένα τη συνδρομή σε οποιοδήποτε σενάριο τερματισμού του component.
Αυτό είναι ιδιαίτερα κρίσιμο για LazyColumn και LazyGrid, όπου τα στοιχεία δημιουργούνται και καταστρέφονται συνεχώς κατά την κύλιση. Χωρίς DisposableEffect, κάθε στοιχείο που εξαφανίζεται από το ορατό πεδίο θα άφηνε μια ενεργή συνδρομή. Με DisposableEffect, το onDispose καλείται για κάθε εκφορτωμένο στοιχείο, διασφαλίζοντας ότι οι πόροι απελευθερώνονται αμέσως μόλις το στοιχείο εγκαταλείψει την οθόνη.
Σύμφωνα με το Android Performance Patterns (Google, 2025), η χρήση του DisposableEffect για όλες τις εγγενείς συνδρομές μειώνει τον αριθμό των διαρροών μνήμης σε εφαρμογές Compose κατά 60–70% σε σύγκριση με τη χειροκίνητη διαχείριση μέσω callbacks κύκλου ζωής. Το σύστημα παρακολουθεί το ίδιο τη στιγμή εξόδου από τη σύνθεση και εγγυάται την εκτέλεση του onDispose ακόμη και κατά το επείγον κλείσιμο της οθόνης.
| Πόρος | Τι κάνει το DisposableEffect | Χωρίς DisposableEffect |
|---|---|---|
| BroadcastReceiver | register + onDispose → unregister | Receiver παραμένει ενεργός |
| SensorManager | registerListener + onDispose → unregister | Αισθητήρας συνεχίζει να στέλνει δεδομένα |
| Observable (όχι Flow) | subscribe + onDispose → unsubscribe | Callback κρατά αναφορά |
| TextureView / SurfaceView | setCallback + onDispose → removeCallback | Διαρροή callback |
| Socket / Channel | open + onDispose → close | Σύνδεση παραμένει ανοιχτή |
Ένα από τα πιο χαρακτηριστικά παραδείγματα χρήσης του DisposableEffect — εργασία με αισθητήρες συσκευής (επιταχυνσιόμετρο, γυροσκόπιο, μαγνητόμετρο). Οι αισθητήρες απαιτούν υποχρεωτική ακύρωση εγγραφής κατά τον τερματισμό, διαφορετικά συνεχίζουν να καταναλώνουν ενέργεια μπαταρίας και να στέλνουν δεδομένα ακόμα και μετά το κλείσιμο της οθόνης.
Πρακτικό παράδειγμα: εφαρμογή μέτρησης γωνίας κλίσης. Το DisposableEffect(Unit) καταχωρεί έναν ακροατή επιταχυνσιόμετρου κατά την εμφάνιση του component και ακυρώνει την εγγραφή στο onDispose. Τα δεδομένα του αισθητήρα μεταφέρονται στην κατάσταση μέσω mutableStateOf, το οποίο ενημερώνει αυτόματα το UI. Αν η οθόνη κυλίεται στο LazyColumn και το στοιχείο εξαφανιστεί, το onDispose ενεργοποιείται άμεσα — ο αισθητήρας σταματά να στέλνει δεδομένα για αυτό το στοιχείο.
Κατά την αλλαγή τύπου αισθητήρα (π.χ. από επιταχυνσιόμετρο σε γυροσκόπιο), το κλειδί sensorType αλλάζει, το onDispose ακυρώνει την παλιά συνδρομή και το νέο μπλοκ DisposableEffect καταχωρεί τον νέο αισθητήρα. Χωρίς κλειδιά, θα έπρεπε να ελέγχετε χειροκίνητα ποιος αισθητήρας είχε καταχωρηθεί προηγουμένως και να καλείτε το unregisterListener με τον σωστό listener — που είναι επιρρεπές σε σφάλματα.
@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 είναι ιδανικό για την καταχώρηση ενός δέκτη για τη διάρκεια ζωής μιας συγκεκριμένης οθόνης. Κατά την είσοδο στην οθόνη, ο BroadcastReceiver καταχωρείται με το κατάλληλο IntentFilter, κατά την έξοδο — ακυρώνεται αυτόματα στο onDispose.
Τυπικό σενάριο — παρακολούθηση κατάστασης δικτύου. Το DisposableEffect καταχωρεί έναν δέκτη στο ConnectivityManager που ειδοποιεί για αλλαγές στη σύνδεση δικτύου. Κατά την αλλαγή κατάστασης (WiFi / δεδομένα κινητής / χωρίς δίκτυο) η κατάσταση του composable ενημερώνεται και το UI εμφανίζει τον αντίστοιχο δείκτη. Όταν η οθόνη κλείνει, το onDispose ακυρώνει εγγυημένα την καταχώρηση — ακόμα κι αν η εφαρμογή πάει στο παρασκήνιο.
Για δέκτες με ContextCompat.registerReceiver και σημαία RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED (Android 14+) η χρήση του 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.
Το τρίτο λάθος — δημιουργία νέων αντικειμένων μέσα στο DisposableEffect χωρίς remember. Αν μέσα στο efffect δημιουργούνται αντικείμενα (αισθητήρας, listener, δέκτης) σε κάθε κλήση και τα κλειδιά αλλάζουν συχνά, αυτό οδηγεί σε υπερβολική δημιουργία αντικειμένων και συλλογή απορριμμάτων. Καλύτερα να μεταφέρετε τη δημιουργία αντικειμένων σε remember ή remember { ... } εκτός DisposableEffect, και μέσα στο effect μόνο να καταχωρείτε και να ακυρώνετε.
Συχνές Ερωτήσεις
DisposableEffect λειτουργεί σύγχρονα και παρέχει onDispose για ρητό καθαρισμό πόρων. Το LaunchedEffect λειτουργεί ασύγχρονα σε ένα coroutine και το ακυρώνει αυτόματα κατά την αλλαγή κλειδιού ή έξοδο από τη σύνθεση. Αν ο πόρος απαιτεί κλήση μεθόδου cleanup (close, unregister, dispose) — χρησιμοποιήστε DisposableEffect. Αν η λειτουργία είναι συνάρτηση suspend — χρησιμοποιήστε LaunchedEffect.
Ναι, το onDispose είναι υποχρεωτικό — ο μεταγλωττιστής Kotlin απαιτεί την κλήση του μέσα στο μπλοκ DisposableEffect. Αν το onDispose δεν κληθεί, ο κώδικας δεν θα μεταγλωττιστεί. Αυτό έγινε σκόπιμα για να αποτραπεί η λήθη των προγραμματιστών και να διασφαλιστεί ότι κάθε ανοιχτός πόρος θα κλείσει κατά την έξοδο από τη σύνθεση.
Χρησιμοποιήστε try-catch μέσα στο μπλοκ DisposableEffect. Αν η καταχώρηση του πόρου μπορεί να προκαλέσει εξαίρεση (π.χ. ο αισθητήρας δεν βρέθηκε), τυλίξτε την σε try και χειριστείτε το σφάλμα στο UI μέσω ξεχωριστής κατάστασης. Το onDispose πρέπει να κληθεί ανεξάρτητα από την επιτυχία της αρχικοποίησης — τοποθετήστε το σε μπλοκ finally ή στο τέλος του τμήματος try.
Δεν συνιστάται. Για Flow είναι καλύτερο να χρησιμοποιήσετε το LaunchedEffect με collectLatest ή τη μέθοδο .collectAsState() με Lifecycle.repeatOnLifecycle. Το DisposableEffect δεν υποστηρίζει συναρτήσεις suspend, οπότε η εγγραφή σε Flow μέσα του θα απαιτούσε εκκίνηση ξεχωριστού coroutine μέσω CoroutineScope, το οποίο περιπλέκει τον κώδικα και αυξάνει τον κίνδυνο διαρροών.
Δεν υπάρχουν περιορισμοί, αλλά συνιστάται η ομαδοποίηση σχετικών πόρων σε ένα DisposableEffect με πολλαπλές λειτουργίες μέσα και ένα onDispose. Αν οι πόροι είναι ανεξάρτητοι (π.χ. αισθητήρας και BroadcastReceiver), καλύτερα να χωριστούν σε ξεχωριστά DisposableEffect με διαφορετικά κλειδιά — αυτό απλοποιεί τον εντοπισμό σφαλμάτων και αποτρέπει την ανεπιθύμητη αναδημιουργία όλων των πόρων κατά την αλλαγή ενός κλειδιού.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης