androidandroid-fragmentsleakcanary

Adding a fragment and calling .replace() causing a memory leak in the added fragment


I have a BottomNavigationView with 3 tabs. On every tab click, it calls .replace()

private void initBottomNavigation() {

        mBottomNavigation.setOnNavigationItemSelectedListener(menuItem -> {
            switch (menuItem.getItemId()) {
                case R.id.bottomnav_admin_home:                <---- Tab 1
                        getSupportFragmentManager().beginTransaction()
                                .replace(
                                        R.id.admin_fragment_container,
                                        new AdminHomeFragment(),
                                        Constants.FRAGMENT_ADMIN_HOME
                                )
                                .commit();
                    }

                    ...Tab two
                    ....Tab three

                    return true;
    ```

In my 3rd tab, I have a button that adds a fragment on top of the fragment container:

enter image description here

When I click on the button, it calls this to add another Fragment ontop of the fragment container:

getParentFragmentManager().beginTransaction()
                .add(
                        R.id.admin_fragment_container,
                        new DeactivateUserFragment(),
                        Constants.FRAGMENT_ADMIN_DEACTIVATE_USER
                )
                .addToBackStack(null)
                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
                .commit();

When I am on the added Fragment, I click on the second tab which calls .replace(), it causes a memory leak in my added Fragment.

I've reduced down all the code in the added Fragment, but I can't figure out why it's still leaking.

Entire Code for added fragment:

public class DeactivateUserFragment extends Fragment{
    private static final String TAG = "FragmentDeactivatedUser";

    @BindView(R.id.recycler_view) RecyclerView mRecycler;

    @BindView(R.id.parent_layout) ViewGroup mParentLayout;

    @BindView(R.id.progress_bar) ProgressBar mProgressBar;

    private Context                mContext;
    private SimpleListUsersAdapter mAdapter;
    private ArrayList<UserInfo>    mList = new ArrayList<>();
    private DeactivateViewModel    mViewModel;
    private AuthStateManager mAuthStateManager;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = getContext();

    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_admin_deactivate_user, container, false);
        ButterKnife.bind(this, view);



        return view;
    }

    
    @OnClick(R.id.btn_back)
    public void onBackBtnClick(){
        getParentFragmentManager().popBackStack();
    }


}

The leak:

androidx.coordinatorlayout.widget.CoordinatorLayout instance
​     Leaking: YES (ObjectWatcher was watching this because com.example.
​     testproject.ui.admin.deactivate.DeactivateUserFragment received
​     Fragment#onDestroyView() callback (references to its views should be
​     cleared to prevent leaks))
​     Retaining 106.7 kB in 1469 objects
​     key = e50280e9-58bd-4ecd-bc1b-a2d6cc188639
​     watchDurationMillis = 108539
​     retainedDurationMillis = 103532
​     View not part of a window view hierarchy
​     View.mAttachInfo is null (view detached)
​     View.mID = R.id.parent_layout
​     View.mWindowAttachCount = 1
​     mContext instance of com.example.testproject.ui.admin.AdminActivity
​     with mDestroyed = false

Edit 1:________________________________

Why does removing these three lines no longer cause a memory leak?

@BindView(R.id.recycler_view) RecyclerView mRecycler;

    @BindView(R.id.parent_layout) ViewGroup mParentLayout;

    @BindView(R.id.progress_bar) ProgressBar mProgressBar;

I am not using the variables in any of the code, just having them binded. I am extremely confused how this is causes the leak

Edit 2: __________________________________

I've tried setting the views to null in onDestroyView() and unbinding Butterknife but still leaks.

https://github.com/JakeWharton/butterknife/issues/585

 @Override
    public void onDestroyView() {
        Log.d(TAG, "onDestroyView: Called");
        unbinder.unbind();
        mRecycler = null;
        mParentLayout = null;
        mProgressBar = null;
        super.onDestroyView();
    }

I've tried removing the views one by one... progress bar, recycler, and mParentLayout. Only until I remove all 3, does it not leak anymore

I removed Butterknife from the fragment and only use findViewById but still has same issue


Solution

  • The fix was setting a global variable for the butterknife's Binder in my Fragment and calling Unbind in the onDestroyView()

    private Unbinder unbinder;
    
    @Nullable
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fragment_admin_link_family, container, false);
            
            return view;
        }
    

    I also had my adapter binded

    private SimpleListUsersAdapter adapter;

    Inside Adapter class
    
    
     class ViewHolder extends RecyclerView.ViewHolder {
    
            @BindView(R.id.action_type)
            TextView userAction;
            @BindView(R.id.date)
            TextView date;
            @BindView(R.id.amount)
            TextView amount;
    
            ViewHolder(@NonNull View itemView) {
                super(itemView);
                ButterKnife.bind(this, itemView);
            }
        }
    

    So I needed to set the adapter to null also in onDestroyView

    Fragment Class
    
    @Override
        public void onDestroyView() {
            super.onDestroyView();
            adapter = null;
            unbinder.unbind();
    }
    

    Needed to do both to stop leak