Constraint Layout Leaking in Fragment - Leak Canary - Leak in TextInputLayout

Viewed 660

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

0 Answers
Related