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:
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
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