I got a memory leak in my login page and I can`t figure why. I am using Leak Canary to identify the leak and this was the leak trace I got.
D/LeakCanary: ┬───
│ GC Root: Input or output parameters in native code
│
├─ okio.AsyncTimeout class
│ Leaking: NO (PathClassLoader↓ is not leaking and a class is never leaking)
│ ↓ static AsyncTimeout.$class$classLoader
├─ dalvik.system.PathClassLoader instance
│ Leaking: NO (InternalLeakCanary↓ is not leaking and A ClassLoader is never leaking)
│ ↓ PathClassLoader.runtimeInternalObjects
├─ java.lang.Object[] array
│ Leaking: NO (InternalLeakCanary↓ is not leaking)
│ ↓ Object[].[957]
├─ leakcanary.internal.InternalLeakCanary class
│ Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
│ ↓ static InternalLeakCanary.resumedActivity
├─ com.eim.rdoApplication.ui.activity.MainActivity instance
│ Leaking: NO (LoginFragment↓ is not leaking and Activity#mDestroyed is false)
│ mApplication instance of com.eim.rdoApplication.AppApplication
│ mBase instance of androidx.appcompat.view.ContextThemeWrapper, not wrapping known Android context
│ ↓ MainActivity.mActivityResultRegistry
├─ androidx.activity.ComponentActivity$2 instance
│ Leaking: NO (LoginFragment↓ is not leaking)
│ Anonymous subclass of androidx.activity.result.ActivityResultRegistry
D/LeakCanary: │ this$0 instance of com.eim.rdoApplication.ui.activity.MainActivity with mDestroyed = false
│ ↓ ComponentActivity$2.mKeyToCallback
├─ java.util.HashMap instance
│ Leaking: NO (LoginFragment↓ is not leaking)
│ ↓ HashMap.table
├─ java.util.HashMap$Node[] array
│ Leaking: NO (LoginFragment↓ is not leaking)
│ ↓ HashMap$Node[].[2]
├─ java.util.HashMap$Node instance
│ Leaking: NO (LoginFragment↓ is not leaking)
│ ↓ HashMap$Node.value
├─ androidx.activity.result.ActivityResultRegistry$CallbackAndContract instance
│ Leaking: NO (LoginFragment↓ is not leaking)
│ ↓ ActivityResultRegistry$CallbackAndContract.mCallback
├─ androidx.fragment.app.FragmentManager$11 instance
│ Leaking: NO (LoginFragment↓ is not leaking)
│ Anonymous class implementing androidx.activity.result.ActivityResultCallback
│ ↓ FragmentManager$11.this$0
├─ androidx.fragment.app.FragmentManagerImpl instance
│ Leaking: NO (LoginFragment↓ is not leaking)
│ ↓ FragmentManagerImpl.mParent
├─ com.eim.rdoApplication.ui.fragment.login.LoginFragment instance
│ Leaking: NO (Fragment#mFragmentManager is not null)
D/LeakCanary: │ componentContext instance of dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper,
│ wrapping activity com.eim.rdoApplication.ui.activity.MainActivity with mDestroyed = false
│ ↓ LoginFragment.mAnimationInfo
│ ~~~~~~~~~~~~~~
├─ androidx.fragment.app.Fragment$AnimationInfo instance
│ Leaking: UNKNOWN
│ Retaining 319257 bytes in 3340 objects
│ ↓ Fragment$AnimationInfo.mFocusedView
│ ~~~~~~~~~~~~
├─ com.google.android.material.textfield.TextInputEditText instance
│ Leaking: UNKNOWN
│ Retaining 319171 bytes in 3339 objects
│ View not part of a window view hierarchy
│ View.mAttachInfo is null (view detached)
│ View.mID = R.id.inputEditTextEmailField
│ View.mWindowAttachCount = 1
│ mContext instance of androidx.appcompat.view.ContextThemeWrapper, wrapping activity com.eim.rdoApplication.ui.
│ activity.MainActivity with mDestroyed = false
│ ↓ TextInputEditText.mParent
D/LeakCanary: │ ~~~~~~~
├─ android.widget.FrameLayout instance
│ Leaking: UNKNOWN
│ Retaining 2231 bytes in 14 objects
│ View not part of a window view hierarchy
│ View.mAttachInfo is null (view detached)
│ View.mWindowAttachCount = 1
│ mContext instance of androidx.appcompat.view.ContextThemeWrapper, wrapping activity com.eim.rdoApplication.ui.
│ activity.MainActivity with mDestroyed = false
│ ↓ FrameLayout.mParent
│ ~~~~~~~
├─ com.google.android.material.textfield.TextInputLayout instance
│ Leaking: UNKNOWN
│ Retaining 204872 bytes in 2728 objects
│ View not part of a window view hierarchy
│ View.mAttachInfo is null (view detached)
│ View.mID = R.id.textInputLayoutEmail
│ View.mWindowAttachCount = 1
│ mContext instance of androidx.appcompat.view.ContextThemeWrapper, wrapping activity com.eim.rdoApplication.ui.
│ activity.MainActivity with mDestroyed = false
D/LeakCanary: │ ↓ TextInputLayout.mParent
│ ~~~~~~~
╰→ androidx.constraintlayout.widget.ConstraintLayout instance
Leaking: YES (ObjectWatcher was watching this because com.eim.rdoApplication.ui.fragment.login.LoginFragment
D/LeakCanary: received Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks))
Retaining 4897 bytes in 71 objects
key = 0d74b072-904d-4aba-9a0e-4e17260ffc96
watchDurationMillis = 9630
retainedDurationMillis = 4627
View not part of a window view hierarchy
View.mAttachInfo is null (view detached)
View.mWindowAttachCount = 1
mContext instance of dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper, wrapping
activity com.eim.rdoApplication.ui.activity.MainActivity with mDestroyed = false
METADATA
Build.VERSION.SDK_INT: 27
Build.MANUFACTURER: motorola
LeakCanary version: 2.5
App process name: com.eim.aplicativo_rdo
What could be causing this leak? Any help ? I am working with one activity and multiple fragments being handled by navigation component.
From what I could understand, the main problem is that my textInputlayoutEmail somehow is not part of a view hierarchy at some point of the lifecycle of my fragment. This is my Fragment Code :
private const val ERROR_LOGIN = "ERROR_LOGIN"
@AndroidEntryPoint
class LoginFragment : Fragment() {
private val navController by lazy {findNavController()}
private val loginViewModel: LoginViewModel by navGraphViewModels(R.id.navigation_graph){
defaultViewModelProviderFactory
}
private var _binding: FragmentLoginBinding? = null
private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentLoginBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
setLoadingView()
setGoToForgotPasswordFragmentButton()
setLoginButton()
super.onViewCreated(view, savedInstanceState)
}
private fun setLoginButton() {
binding.buttonSignIn.setOnClickListener {
if(areFieldsValid()){
loginViewModel.emailField = binding.inputEditTextEmailField.text.toString()
loginViewModel.passwordField = binding.editTextPasswordField.text.toString()
loginUser(loginViewModel.let { UserDataRequest(it.emailField, it.passwordField) })
}
}
}
private fun areFieldsValid(): Boolean {
return !(binding.editTextPasswordField.text.isNullOrBlank()||binding.inputEditTextEmailField.text.isNullOrBlank())
}
private fun setGoToForgotPasswordFragmentButton() {
binding.forgotPasswordEditText.setOnClickListener{
val action = LoginFragmentDirections.actionLoginFragmentToForgotPasswordFragment()
navController.navigate(action)
}
}
private fun setLoadingView() {
binding.apply {
loadingIconLoginFragment.visibility = View.INVISIBLE
loadingBgLoginFragment.visibility = View.INVISIBLE
}
}
private fun loginUser(loginUserRequest: UserDataRequest){
viewLifecycleOwner.lifecycleScope.launch {
loginViewModel.loginUser(loginUserRequest).collect {
when(it.status) {
Resource.Status.LOADING -> {
withContext(Dispatchers.Main) {
setLoadingInterface()
}
}
Resource.Status.ERROR -> {
withContext(Dispatchers.Main){
val errorMessage = "Não foi possível realizar seu Login"
clearLoadingInterface()
showErrorInterface(errorMessage, ERROR_LOGIN)
}
}
Resource.Status.SUCCESS -> {
withContext(Dispatchers.Main) {
clearLoadingInterface()
val action = LoginFragmentDirections.actionLoginFragmentToGeneralHomeFragment(0, "", "")
navController.navigate(action)
}
}
}
}
}
}
private fun setLoadingInterface() {
binding.apply {
loadingBgLoginFragment.visibility = View.VISIBLE
loadingIconLoginFragment.visibility = View.VISIBLE
}
rotateAnimation()
}
private fun showErrorInterface(errorMessage: String, errorType: String) {
clearLoadingInterface()
val dialog = NetworkErrorDialog.newInstance(errorMessage, errorType)
dialog.show(childFragmentManager, dialog.tag)
}
private fun clearLoadingInterface() {
binding.apply {
loadingBgLoginFragment.visibility = View.GONE
loadingIconLoginFragment.apply {
visibility = View.GONE
clearAnimation()
}
}
}
private fun rotateAnimation() {
val animation = AnimationUtils.loadAnimation(activity?.applicationContext, R.anim.rotate)
binding.loadingIconLoginFragment.startAnimation(animation)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
In my views so I don`t know what else can I do to clear this reference. If anyone could show me in which part of the code I am holding a reference to this view, would help me a lot. Thanks