From 8e3a6cc11d8f3f7cc9255f18cdee8bbfcd22879f Mon Sep 17 00:00:00 2001 From: sudeshsv Date: Thu, 14 May 2026 12:20:36 +0530 Subject: [PATCH 1/5] Add memory leak examples for Android documentation * Add Pattern 1: UI Objects Retained Beyond Lifecycle examples * Add Pattern 2: Background Work Outlives the UI examples * Add Pattern 3: Unreleased External Registrations examples * Include Compose and Fragment-specific memory management best practices * Ensure snippets are idiomatic and comply with Spotless formatting These examples illustrate common architectural pitfalls and their corresponding lifecycle-aware resolutions using Coroutines, Flows, and DisposableEffect. --- .../performance/MemoryLeakSnippets.kt | 70 +++++ .../memoryleaks/MemoryLeakSnippets.kt | 254 ++++++++++++++++++ 2 files changed, 324 insertions(+) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/performance/MemoryLeakSnippets.kt create mode 100644 misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/performance/MemoryLeakSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/performance/MemoryLeakSnippets.kt new file mode 100644 index 000000000..8b169bc17 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/performance/MemoryLeakSnippets.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.compose.snippets.performance + +import android.annotation.SuppressLint +import android.location.LocationListener +import android.location.LocationManager +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue + +@SuppressLint("MissingPermission") +// [START android_compose_performance_memory_leak_location_with_leak] +@Composable +fun LocationScreenWithLeak(locationManager: LocationManager) { + var locationText by remember { mutableStateOf("Locating...") } + + // This registers the listener when entering composition, but leaves it attached to the OS when leaving + LaunchedEffect(locationManager) { + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000L, 1f) { location -> + locationText = "Lat: ${location.latitude}, Lng: ${location.longitude}" + } + } + + Text(text = locationText) +} +// [END android_compose_performance_memory_leak_location_with_leak] + +@SuppressLint("MissingPermission") +// [START android_compose_performance_memory_leak_location_recommended] +@Composable +fun LocationScreenRecommended(locationManager: LocationManager) { + var locationText by remember { mutableStateOf("Locating...") } + + // DisposableEffect provides an onDispose block for mandatory cleanup + DisposableEffect(locationManager) { + val listener = LocationListener { location -> + locationText = "Lat: ${location.latitude}, Lng: ${location.longitude}" + } + + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000L, 1f, listener) + + // Automatically executed when this Composable leaves the screen + onDispose { + locationManager.removeUpdates(listener) + } + } + + Text(text = locationText) +} +// [END android_compose_performance_memory_leak_location_recommended] diff --git a/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt b/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt new file mode 100644 index 000000000..7b06ba252 --- /dev/null +++ b/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt @@ -0,0 +1,254 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.snippets.memoryleaks + +import android.app.Activity +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.launch + +// Mock classes for snippets to compile +class User(val name: String) +class Api { + fun getUser(callback: (User) -> Unit) {} + suspend fun getUser(): User = User("Mock") +} +val api = Api() + +class UserFragmentBinding { + val root: View? = null + val name: TextView? = null + companion object { + fun bind(view: View): UserFragmentBinding = UserFragmentBinding() + fun inflate(inflater: LayoutInflater, container: ViewGroup?, b: Boolean): UserFragmentBinding = UserFragmentBinding() + } +} +val binding = UserFragmentBinding() +class UserViewModel { + val user: Flow = flow {} +} +val viewModel = UserViewModel() +annotation class Singleton +annotation class Inject +annotation class ApplicationContext +class ActivityImagePicker(val activity: Activity) + +// Pattern 1: Example 1 - Repository retains a UI callback + +// [START android_memory_leak_repository_callback_with_leak] +class UserRepositoryWithLeak { + private var listener: ((User) -> Unit)? = null + + fun fetchUser(callback: (User) -> Unit) { + // The repository retains the callback beyond the view lifecycle. + listener = callback + api.getUser { user -> + listener?.invoke(user) + } + } +} + +/* In the Fragment/UI layer: +repository.fetchUser { user -> + binding.name.text = user.name +} +*/ +// [END android_memory_leak_repository_callback_with_leak] + +// [START android_memory_leak_repository_callback_recommended] +class UserRepositoryRecommended { + suspend fun fetchUser(): User { + return api.getUser() + } +} + +/* In the Fragment/UI layer: +viewLifecycleOwner.lifecycleScope.launch { + val user = repository.fetchUser() + binding.name.text = user.name +} +*/ +// [END android_memory_leak_repository_callback_recommended] + +// Pattern 1: Example 2 - Singleton depends on a UI-scoped object + +// [START android_memory_leak_singleton_dependency_with_leak] +@Singleton +class ImageLoaderWithLeak @Inject constructor( + private val activityImagePicker: ActivityImagePicker +) + +class ActivityImagePickerWithLeak @Inject constructor( + // Injecting Activity here makes this dependency activity-scoped. + private val activity: Activity +) +// [END android_memory_leak_singleton_dependency_with_leak] + +// [START android_memory_leak_singleton_dependency_recommended] +// Option 1: Pass the Activity dynamically for UI-scoped tasks (like image picking) +@Singleton +class ImageLoaderRecommended1 @Inject constructor() { + fun pickImage(activity: Activity) { /* ... */ } +} + +// Option 2: Inject Application Context for non-UI/background tasks (like disk caching or sharedPreferences) +@Singleton +class ImageLoaderRecommended2 @Inject constructor( + @ApplicationContext private val context: Context +) +// [END android_memory_leak_singleton_dependency_recommended] + +// Pattern 2: Example 1 - Fragment collects Flow with the incorrect lifecycle + +// [START android_memory_leak_fragment_flow_with_leak] +class UserFragmentWithLeak : Fragment() { + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val binding = UserFragmentBinding.bind(view) + + lifecycleScope.launch { + // This coroutine is tied to the fragment lifecycle, not the view lifecycle. + viewModel.user.collect { user -> + binding.name?.text = user.name + } + } + } +} +// [END android_memory_leak_fragment_flow_with_leak] + +// [START android_memory_leak_fragment_flow_recommended] +class UserFragmentRecommended : Fragment() { + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val binding = UserFragmentBinding.bind(view) + + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.user.collect { user -> + binding.name?.text = user.name + } + } + } + } +} +// [END android_memory_leak_fragment_flow_recommended] + +// Pattern 2: Example 2 - Delayed work captures an Activity + +// [START android_memory_leak_delayed_work_with_leak] +// Singleton scope accepts a UI-bound callback +object UserRepositoryDelayedWithLeak { + private val repositoryScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + + // Accepts a callback that might capture a destroyed UI Context + fun fetchUserDataWithDelay(onComplete: (String) -> Unit) { + repositoryScope.launch { + delay(5_000) + onComplete("User Data") // If onComplete references the Activity, it leaks! + } + } +} + +class MainActivityWithLeak : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // The trailing lambda implicitly captures 'this' (MainActivity) to update the title + UserRepositoryDelayedWithLeak.fetchUserDataWithDelay { data -> + title = data + } + } +} +// [END android_memory_leak_delayed_work_with_leak] + +// [START android_memory_leak_delayed_work_recommended] +// Expose data as a Flow and let the UI handle the lifecycle scope +object UserRepositoryDelayedRecommended { + // A clean, stateless flow with no callback parameters + fun getUserData(): Flow = flow { + delay(5_000) + emit("User Data") + } +} + +class MainActivityRecommended : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Automatically cancels collection and releases MainActivity when destroyed + lifecycleScope.launch { + UserRepositoryDelayedRecommended.getUserData().collect { data -> + title = data + } + } + } +} +// [END android_memory_leak_delayed_work_recommended] + +// Pattern 3: Example 2 - Fragment view binding is not cleared + +// [START android_memory_leak_fragment_binding_with_leak] +class UserFragmentBindingWithLeak : Fragment() { + private var binding: UserFragmentBinding? = null + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + // The fragment keeps this binding field until it is cleared. + val binding = UserFragmentBinding.inflate(inflater, container, false) + this.binding = binding + return binding.root ?: View(context) + } +} +// [END android_memory_leak_fragment_binding_with_leak] + +// [START android_memory_leak_fragment_binding_recommended] +class UserFragmentBindingRecommended : Fragment() { + private var _binding: UserFragmentBinding? = null + // This property makes it easy to use the binding without constantly checking for null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = UserFragmentBinding.inflate(inflater, container, false) + return binding.root ?: View(context) + } + + override fun onDestroyView() { + _binding = null // Explicitly releases the view hierarchy from memory + super.onDestroyView() + } +} +// [END android_memory_leak_fragment_binding_recommended] From e240bff177c193fd5755db4f6534a1ea269886d8 Mon Sep 17 00:00:00 2001 From: sudeshsv Date: Fri, 15 May 2026 11:52:23 +0530 Subject: [PATCH 2/5] Address PR feedback for memory leak examples * Refactor Repository examples to use actual ViewModel implementations instead of commented-out pseudo-code. * Split the recommended Repository callback snippet into pt1 and pt2. * Add explanatory comments to delay() calls clarifying their purpose to emulate network/processing. --- .../memoryleaks/MemoryLeakSnippets.kt | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt b/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt index 7b06ba252..705d98e30 100644 --- a/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt +++ b/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt @@ -26,13 +26,18 @@ import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle +import androidx.lifecycle.ViewModel import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import androidx.lifecycle.viewModelScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch @@ -77,27 +82,42 @@ class UserRepositoryWithLeak { } } -/* In the Fragment/UI layer: -repository.fetchUser { user -> - binding.name.text = user.name +class UserViewModelWithLeak(private val repository: UserRepositoryWithLeak) : ViewModel() { + private val _userState = MutableStateFlow(null) + val userState: StateFlow = _userState.asStateFlow() + + fun loadUser() { + // The ViewModel passes a callback that retains a reference to itself. + // The repository holds this callback even after the ViewModel is cleared. + repository.fetchUser { user -> + _userState.value = user + } + } } -*/ // [END android_memory_leak_repository_callback_with_leak] -// [START android_memory_leak_repository_callback_recommended] +// [START android_memory_leak_repository_callback_recommended_pt1] class UserRepositoryRecommended { suspend fun fetchUser(): User { return api.getUser() } } - -/* In the Fragment/UI layer: -viewLifecycleOwner.lifecycleScope.launch { - val user = repository.fetchUser() - binding.name.text = user.name +// [END android_memory_leak_repository_callback_recommended_pt1] + +class UserViewModelRecommended(private val repository: UserRepositoryRecommended) : ViewModel() { + private val _userState = MutableStateFlow(null) + val userState: StateFlow = _userState.asStateFlow() + +// [START android_memory_leak_repository_callback_recommended_pt2] + fun loadUser() { + // viewModelScope automatically cancels if the ViewModel is cleared + viewModelScope.launch { + val user = repository.fetchUser() + _userState.value = user + } + } +// [END android_memory_leak_repository_callback_recommended_pt2] } -*/ -// [END android_memory_leak_repository_callback_recommended] // Pattern 1: Example 2 - Singleton depends on a UI-scoped object @@ -170,7 +190,7 @@ object UserRepositoryDelayedWithLeak { // Accepts a callback that might capture a destroyed UI Context fun fetchUserDataWithDelay(onComplete: (String) -> Unit) { repositoryScope.launch { - delay(5_000) + delay(5_000) // Emulate network or long processing onComplete("User Data") // If onComplete references the Activity, it leaks! } } @@ -193,7 +213,7 @@ class MainActivityWithLeak : AppCompatActivity() { object UserRepositoryDelayedRecommended { // A clean, stateless flow with no callback parameters fun getUserData(): Flow = flow { - delay(5_000) + delay(5_000) // Emulate network or long processing emit("User Data") } } From 535b12077e7ceeac363a02e8291ef6d009efb9f3 Mon Sep 17 00:00:00 2001 From: sudeshsv Date: Sat, 16 May 2026 02:38:16 +0530 Subject: [PATCH 3/5] Refine memory leak examples for documentation * Align snippet structure and names with original document's retention chains. * Rename ImageLoader examples to ImagePickerRecommended and ImageCacheRecommended for clarity. * Replace delay() in delayed work examples with api.getUser() for realism. * Add missing Pattern 3 comment in Compose snippets file. * Use repeatOnLifecycle in Activity flow collection. --- .../performance/MemoryLeakSnippets.kt | 2 + .../memoryleaks/MemoryLeakSnippets.kt | 59 ++++++++++--------- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/performance/MemoryLeakSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/performance/MemoryLeakSnippets.kt index 8b169bc17..e8d858a23 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/performance/MemoryLeakSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/performance/MemoryLeakSnippets.kt @@ -28,6 +28,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +// Pattern 3: Example 1 - Compose registers a system service listener without cleanup + @SuppressLint("MissingPermission") // [START android_compose_performance_memory_leak_location_with_leak] @Composable diff --git a/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt b/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt index 705d98e30..1388a2b0d 100644 --- a/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt +++ b/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt @@ -33,7 +33,6 @@ import androidx.lifecycle.viewModelScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -58,10 +57,6 @@ class UserFragmentBinding { } } val binding = UserFragmentBinding() -class UserViewModel { - val user: Flow = flow {} -} -val viewModel = UserViewModel() annotation class Singleton annotation class Inject annotation class ApplicationContext @@ -82,15 +77,16 @@ class UserRepositoryWithLeak { } } -class UserViewModelWithLeak(private val repository: UserRepositoryWithLeak) : ViewModel() { - private val _userState = MutableStateFlow(null) - val userState: StateFlow = _userState.asStateFlow() +class UserFragmentCallbackWithLeak : Fragment() { + private val repository = UserRepositoryWithLeak() - fun loadUser() { - // The ViewModel passes a callback that retains a reference to itself. - // The repository holds this callback even after the ViewModel is cleared. + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val binding = UserFragmentBinding.bind(view) + + // The Fragment passes a callback that retains a reference to the binding. + // The repository holds this callback even after the Fragment's view is destroyed. repository.fetchUser { user -> - _userState.value = user + binding.name?.text = user.name } } } @@ -104,13 +100,13 @@ class UserRepositoryRecommended { } // [END android_memory_leak_repository_callback_recommended_pt1] -class UserViewModelRecommended(private val repository: UserRepositoryRecommended) : ViewModel() { +class UserViewModel(private val repository: UserRepositoryRecommended) : ViewModel() { private val _userState = MutableStateFlow(null) val userState: StateFlow = _userState.asStateFlow() // [START android_memory_leak_repository_callback_recommended_pt2] fun loadUser() { - // viewModelScope automatically cancels if the ViewModel is cleared + // viewModelScope automatically cancels if the user leaves the screen viewModelScope.launch { val user = repository.fetchUser() _userState.value = user @@ -119,6 +115,8 @@ class UserViewModelRecommended(private val repository: UserRepositoryRecommended // [END android_memory_leak_repository_callback_recommended_pt2] } +val viewModel = UserViewModel(UserRepositoryRecommended()) + // Pattern 1: Example 2 - Singleton depends on a UI-scoped object // [START android_memory_leak_singleton_dependency_with_leak] @@ -136,13 +134,13 @@ class ActivityImagePickerWithLeak @Inject constructor( // [START android_memory_leak_singleton_dependency_recommended] // Option 1: Pass the Activity dynamically for UI-scoped tasks (like image picking) @Singleton -class ImageLoaderRecommended1 @Inject constructor() { +class ImagePickerRecommended @Inject constructor() { fun pickImage(activity: Activity) { /* ... */ } } // Option 2: Inject Application Context for non-UI/background tasks (like disk caching or sharedPreferences) @Singleton -class ImageLoaderRecommended2 @Inject constructor( +class ImageCacheRecommended @Inject constructor( @ApplicationContext private val context: Context ) // [END android_memory_leak_singleton_dependency_recommended] @@ -156,8 +154,8 @@ class UserFragmentWithLeak : Fragment() { lifecycleScope.launch { // This coroutine is tied to the fragment lifecycle, not the view lifecycle. - viewModel.user.collect { user -> - binding.name?.text = user.name + viewModel.userState.collect { user -> + binding.name?.text = user?.name } } } @@ -171,8 +169,8 @@ class UserFragmentRecommended : Fragment() { viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.user.collect { user -> - binding.name?.text = user.name + viewModel.userState.collect { user -> + binding.name?.text = user?.name } } } @@ -188,10 +186,13 @@ object UserRepositoryDelayedWithLeak { private val repositoryScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) // Accepts a callback that might capture a destroyed UI Context - fun fetchUserDataWithDelay(onComplete: (String) -> Unit) { + fun fetchUserData(onComplete: (String) -> Unit) { repositoryScope.launch { - delay(5_000) // Emulate network or long processing - onComplete("User Data") // If onComplete references the Activity, it leaks! + // network or database call that suspends + val user = api.getUser() + // If the Activity was destroyed while waiting for the API, + // invoking this callback will leak the Activity! + onComplete(user.name) } } } @@ -201,7 +202,7 @@ class MainActivityWithLeak : AppCompatActivity() { super.onCreate(savedInstanceState) // The trailing lambda implicitly captures 'this' (MainActivity) to update the title - UserRepositoryDelayedWithLeak.fetchUserDataWithDelay { data -> + UserRepositoryDelayedWithLeak.fetchUserData { data -> title = data } } @@ -213,8 +214,8 @@ class MainActivityWithLeak : AppCompatActivity() { object UserRepositoryDelayedRecommended { // A clean, stateless flow with no callback parameters fun getUserData(): Flow = flow { - delay(5_000) // Emulate network or long processing - emit("User Data") + val user = api.getUser() + emit(user.name) } } @@ -224,8 +225,10 @@ class MainActivityRecommended : AppCompatActivity() { // Automatically cancels collection and releases MainActivity when destroyed lifecycleScope.launch { - UserRepositoryDelayedRecommended.getUserData().collect { data -> - title = data + repeatOnLifecycle(Lifecycle.State.STARTED) { + UserRepositoryDelayedRecommended.getUserData().collect { data -> + title = data + } } } } From 6058047456870aadb7f3d3423c4c065efb3e0b30 Mon Sep 17 00:00:00 2001 From: sudeshsv Date: Mon, 18 May 2026 14:44:35 +0530 Subject: [PATCH 4/5] Remove inaccurate fragment view binding leak example for Unreleased External Registration Pattern --- .../memoryleaks/MemoryLeakSnippets.kt | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt b/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt index 1388a2b0d..05f6d94c4 100644 --- a/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt +++ b/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt @@ -234,44 +234,3 @@ class MainActivityRecommended : AppCompatActivity() { } } // [END android_memory_leak_delayed_work_recommended] - -// Pattern 3: Example 2 - Fragment view binding is not cleared - -// [START android_memory_leak_fragment_binding_with_leak] -class UserFragmentBindingWithLeak : Fragment() { - private var binding: UserFragmentBinding? = null - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - // The fragment keeps this binding field until it is cleared. - val binding = UserFragmentBinding.inflate(inflater, container, false) - this.binding = binding - return binding.root ?: View(context) - } -} -// [END android_memory_leak_fragment_binding_with_leak] - -// [START android_memory_leak_fragment_binding_recommended] -class UserFragmentBindingRecommended : Fragment() { - private var _binding: UserFragmentBinding? = null - // This property makes it easy to use the binding without constantly checking for null - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = UserFragmentBinding.inflate(inflater, container, false) - return binding.root ?: View(context) - } - - override fun onDestroyView() { - _binding = null // Explicitly releases the view hierarchy from memory - super.onDestroyView() - } -} -// [END android_memory_leak_fragment_binding_recommended] From 23d97a3d0d503ee629c12056b84872ac67706242 Mon Sep 17 00:00:00 2001 From: sudeshsv Date: Tue, 19 May 2026 14:00:38 +0530 Subject: [PATCH 5/5] Use ComponentActivity in memory leak examples * Replace AppCompatActivity with ComponentActivity in MainActivityWithLeak and MainActivityRecommended to make the snippets more Compose-friendly. * Update copyright year in snippet files. --- .../compose/snippets/performance/MemoryLeakSnippets.kt | 2 +- .../example/snippets/memoryleaks/MemoryLeakSnippets.kt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/performance/MemoryLeakSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/performance/MemoryLeakSnippets.kt index e8d858a23..175adff07 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/performance/MemoryLeakSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/performance/MemoryLeakSnippets.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Android Open Source Project + * Copyright 2026 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt b/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt index 05f6d94c4..6d7510714 100644 --- a/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt +++ b/misc/src/main/java/com/example/snippets/memoryleaks/MemoryLeakSnippets.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Android Open Source Project + * Copyright 2026 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView -import androidx.appcompat.app.AppCompatActivity +import androidx.activity.ComponentActivity import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModel @@ -197,7 +197,7 @@ object UserRepositoryDelayedWithLeak { } } -class MainActivityWithLeak : AppCompatActivity() { +class MainActivityWithLeak : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -219,7 +219,7 @@ object UserRepositoryDelayedRecommended { } } -class MainActivityRecommended : AppCompatActivity() { +class MainActivityRecommended : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)