Memory Leaks in RecyclerView when used in Fragment by LeakCanary - android

A simple application with recyclerview in fragment has memory leak as per leaks app.
Memory Leak snapshot by Leak App
Stack of the Leak:
HelloTest.java:
public class HelloTest extends AppCompatActivity {
private TestFrag mFrag = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
mFrag = new TestFrag();
getSupportFragmentManager()
.beginTransaction()
.replace(android.R.id.content, mFrag )
.commit();
}
}
#Override
public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = AppClass.getRefWatcher(this);
refWatcher.watch(this);
}
}
Fragment:
public class TestFrag extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.activity_hello_test, container, false);
return view;
}
}
XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/comme_rv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</RelativeLayout>
By executing the above app code memory leak occurs when the app exits.
If same code is placed in activity instead of fragment then there is no leak.
Am I doing something wrong?

You shouldn't create anything in the onDestroy method. So you put your code like this:
public class HelloTest extends AppCompatActivity {
private TestFrag mFrag = null;
RefWatcher refWatcher;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
refWatcher = AppClass.getRefWatcher(this);
refWatcher.watch(this);
if (savedInstanceState == null) {
mFrag = new TestFrag();
getSupportFragmentManager()
.beginTransaction()
.replace(android.R.id.content, mFrag )
.commit();
}
}
#Override
public void onDestroy() {
if(refWatcher!=null){
// destroy refWatcher
}
super.onDestroy();
}
}

Related

flutter reuse fragment float above other views

I have two reuse fragments, and the second on is a flutter fragment, which behave strange.
above Android API 29, flutter fragment will render above first fragment after the detail activity pop out of stack .
under Android API 29, flutter fragment always render above all views in the main activity.
I create this repository to reproduce this error.
This unsolved question is similar to mine.
this code shows how I reuse fragments
private void switchTo(String tag) {
FragmentManager supportFragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = supportFragmentManager.beginTransaction();
if (tag.equals("home")) {
fragmentTransaction.show(homeFragment);
fragmentTransaction.hide(notificationsFragment);
} else {
fragmentTransaction.show(notificationsFragment);
fragmentTransaction.hide(homeFragment);
}
fragmentTransaction.commitNowAllowingStateLoss();
}
this is where flutter fragment add to the view
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
FragmentManager fragmentManager = getChildFragmentManager();
if (mRepGoalsFlutterFragment1 == null) {
mRepGoalsFlutterFragment1 = FlutterFragment.withNewEngine()
.build();
fragmentManager
.beginTransaction()
.add(R.id.card_view7,
mRepGoalsFlutterFragment1,
TAG_REP_GOALS_FLUTTER_FRAGMENT_1)
.commit();
}
return inflater.inflate(R.layout.fragment_notifications, container, false);
}
In my Fragment, I have to detachFlutterEngine myself to fix this problem.
private FlutterView myFlutterView;
private boolean isFlutterHidden = false;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
myFlutterView = FlutterBoostUtils.findFlutterView(view);
return view;
}
#Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
isFlutterHidden = hidden;
updateFlutterViewHidden();
}
#SuppressLint("VisibleForTests")
void updateFlutterViewHidden() {
if (myFlutterView == null) return;
if (isFlutterHidden) {
myFlutterView.setVisibility(View.GONE);
} else {
myFlutterView.setVisibility(View.VISIBLE);
}
}
#Override
public void onResume() {
super.onResume();
updateFlutterViewHidden();
}
Combine your posts with Github issue(https://github.com/flutter/flutter/issues/59968),the solution is create self class extends FlutterFragment, get flutterSurfaceView in the onFlutterSurfaceViewCreated.
private FlutterSurfaceView flutterView;
#Override
public void onFlutterSurfaceViewCreated(#NonNull FlutterSurfaceView flutterSurfaceView) {
super.onFlutterSurfaceViewCreated(flutterSurfaceView);
LogUtils.d(TAG, "onFlutterSurfaceViewCreated开始");
this.flutterView = flutterSurfaceView;
LogUtils.d(TAG, "onFlutterSurfaceViewCreated结束");
}
Then setVisibility in onResume.
#SuppressLint("解决FlutterFragment切换到前台显示的问题")
void updateFlutterViewVisibility() {
if (flutterView == null) {
return;
}
if (isFlutterHidden) {
flutterView.setVisibility(View.GONE);
} else {
flutterView.setVisibility(View.VISIBLE);
}
}
#Override
public void onResume() {
LogUtils.d(TAG, "onResume开始");
super.onResume();
updateFlutterViewVisibility();
LogUtils.d(TAG, "onResume结束");
}
#Override
public void onHiddenChanged(boolean hidden) {
LogUtils.d(TAG, "onHiddenChanged开始");
super.onHiddenChanged(hidden);
isFlutterHidden = hidden;
updateFlutterViewVisibility();
LogUtils.d(TAG, "onHiddenChanged结束");
}
Create your self class extends FlutterFragment like this:
FlutterFragment.CachedEngineFragmentBuilder(
YourFlutterFragment::class.java,
YourFlutterFragmentEngineId
)
.shouldAttachEngineToActivity(false)
.build<YourFlutterFragment>()

Remove Fragment 2 from BackStack and pass data to Fragment 1

Inside my MainActivity (Demo) I add FragmentDemo 1 to backstack. Inside this fragmentDemo 1, pressing a button opens a new FragmentDemo 2, where I have an edit text. On pressing the button on this second fragment, I want to remove it from backstack and send the data from editText back to FragmentDemo 1.
I am using a listener on Fragment 2, and implementing the methods, but when I run the code I have the following message. java.lang.ClassCastException: com.example.teacherapp.activities.Demo#5630fb7must implement Listener
Demo (Main Activity)
public class Demo extends AppCompatActivity implements FragmentInterface {
private TextView textView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
if (savedInstanceState == null) {
addFragment(new FragmentDemo1());
}
}
#Override
public void onMyFragment(Fragment fragment) {
addFragment(fragment);
}
private void addFragment(Fragment fragment){
getSupportFragmentManager()
.beginTransaction()
.add(R.id.demo_container,fragment)
.addToBackStack(null)
.commit();
}
}
FragmentInterface
public interface FragmentInterface {
void onMyFragment(Fragment fragment);
}
FragmentDemo 1
public class FragmentDemo1 extends Fragment implements FragmentInterface, FragmentDemo2.Fragment2CallBack {
Button btnFrag1;
TextView tvFrag1;
public FragmentDemo1() {
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_fragment_demo1, container, false);
return view;
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
btnFrag1 = view.findViewById(R.id.fragment1_button);
tvFrag1 = view.findViewById(R.id.fragment1_tv);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
FragmentManager fm = getFragmentManager();
btnFrag1.setText("Frag 1: " + String.valueOf(fm.getBackStackEntryCount()));
btnFrag1.setOnClickListener(v -> {
replaceFragment(new FragmentDemo2());
});
}
#Override
public void onMyFragment(Fragment fragment) {
replaceFragment(fragment);
}
private void replaceFragment(Fragment fragment) {
getFragmentManager().
beginTransaction().
replace(R.id.demo_container, fragment).
addToBackStack(null).
commit();
}
#Override
public void onDataSent(String myData) {
Toast.makeText(getContext(), "RECEIVED. "+myData, Toast.LENGTH_LONG).show();
}
}
FragmentDemo 2
public class FragmentDemo2 extends Fragment {
private Button btnFrag2;
private EditText etFrag2;
private Fragment2CallBack listener;
public FragmentDemo2(){}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_fragment_demo2,container,false);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
btnFrag2=view.findViewById(R.id.fragment2_button);
etFrag2=view.findViewById(R.id.fragment2_et);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
FragmentManager fm=getFragmentManager();
btnFrag2.setOnClickListener(v->{
String info=etFrag2.getText().toString();
listener.onDataSent(info);
fm.popBackStack();
});
}
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
try{
listener=(Fragment2CallBack) context;
}catch (ClassCastException e){
throw new ClassCastException(context.toString()+"must implement Listener");
}
}
public interface Fragment2CallBack{
void onDataSent(String s);
}
}
The result expected is to have fragment 2 removed from backstack and open fragment 1, with received data from fragment 2.
Fragment Back Stack Example.
Refer this url https://www.dev2qa.com/android-fragment-back-stack-example/
The Problem in your existing code
The Context you try to use in FragmentDemo2 is of Demo activity (not of FragmentDemo1) so you can not type cast it to listener when you are trying to attach
If you want to achieve the same goal with existing code, follow below step
1) Expose API from FragmentDemo2 that will allow to set Listener of type Fragment2Callback
public void setListener(Fragment2Callback li) {listener = li;}
2) From FragmentDemo1, when you create FragmentDemo2 instance, you also need to set itself as listener to FragmentDemo2
FragmentDemo2 frg = new FragemtDemo2()
frg.setListener(this);
replaceFragment(frg);
3) Then replaceFragment() from FragmentDemo1
4) remove casting of listener from onAttach() inside FragmentDemo2
So now you have FragmentDemo1 as listener inside FragmentDemo2, which you can use to communicate to fragmentDemo1
I hope this information help you

How to properly get RecyclerView Adapter from Fragment?

I have a Fragment that displays a list using my class ListRecyclerViewAdapter that extends RecyclerView.Adapter:
public class ListFragment extends Fragment {
private ListRecyclerViewAdapter adapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
adapter = new ListRecyclerViewAdapter(context, itemsToDisplay);
...
}
public ListRecyclerViewAdapter getListAdapter() {
return adapter;
}
}
When I try to get the Adapter from an Activity that has the above Fragment, I get null:
public class DataActivity extends FragmentActivity {
#Override
protected void onResume() {
super.onResume();
listFragment = new ListFragment(itemsToDisplay);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_list_container, listFragment).commit();
ListRecyclerViewAdapter wantedAdapter = listFragment.getAdapter();
}
}
Why is that? It looks to me like the Fragment's method onCreateView() was not called at the point when I call listFragment.getAdapter().
Fragment is not created when you are trying to call the method.So check is the fragment is attached before accessing the adapter.
public ListRecyclerViewAdapter getListAdapter() {
if(this.isAdded() && adapter!=null){
return adapter;
}
return null;
}
Also load the fragment in oncreate of activity and call the adpter reference method in onresume
public class DataActivity extends FragmentActivity {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listFragment = new ListFragment(itemsToDisplay);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_list_container, listFragment).commit();
}
#Override
protected void onResume() {
super.onResume();
ListRecyclerViewAdapter wantedAdapter = listFragment.getAdapter();
}
}
As soon as it created, it called listfragment adapter. so, the UI creation will take some time , but the flow flowed to that line. so it return null. you can acheive this in following way.
public class ListFragment extends Fragment {
LoadAdapter loadAdapter;
private ListRecyclerViewAdapter adapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
adapter = new ListRecyclerViewAdapter(context, itemsToDisplay);
...
}
public ListRecyclerViewAdapter getListAdapter() {
return adapter;
}
interface LoadAdapter {
public void onAdapterLoaded();
}
public void setOnAdapterLoad(LoadAdapter loadAdapter){
this.loadAdapter = loadAdapter;
}
#Override
public void onResume(){
loadAdapter.onAdapterLoaded();
}
}
Now the activity code as follows:
public class DataActivity extends FragmentActivity {
#Override
protected void onResume() {
super.onResume();
listFragment = new ListFragment(itemsToDisplay);
listFragment.setOnAdapterLoad(new LoadAdapter(){
#Override
public void onAdapterLoaded(){
ListRecyclerViewAdapter wantedAdapter = listFragment.getAdapter();
}
});
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_list_container, listFragment).commit();
}
}
Hope this helps.

Cannot see Fragment

I have a static v4.app.Fragment in an AppCompatActivity's xml, and everything shows up when I launch the app, but once I enter another activity and comeback, or switch to another app and switch back, the fragment cannot be seen, while the Log.d of the fragment shows that the fragment exists somewhere. I have also tried using getsupportfragmentmanager.add.commit to programmatically add the fragment, and it has the same result
activity_main.xml
...
<FrameLayout
android:id="#+id/content_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<fragment
android:id="#+id/fragment"
android:name="xxx.xxx.xxx.Fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
<android.support.design.widget.FloatingActionButton
...
MainActivity.java
#BindView(R.id.toolbar) Toolbar toolbar;
#BindView(R.id.fab) FloatingActionButton fab;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(MainActivity.this);
...
No onResume, onCreateView, etc
XFragment.java
public class XFragment extends Fragment {
#BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout swipeRefreshLayout;
#BindView(R.id.recyclerView) RecyclerView recyclerView;
// and a lot of other variables
public XFragment() {
}
public static XFragment newInstance(boolean isSearch) {
XFragment xFragment = new XFragment();
return xFragment;
}
#Override
public void onResume() {
Log.d(TAG, "onResume");
super.onResume();
//initiate variables
inflateRecyclerView();
//etc
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(TAG, "onCreateView");
View view = inflater.inflate(R.layout.fragment_x, container, false);
ButterKnife.bind(this, view);
return view;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Parcelable parcelable = llm.onSaveInstanceState();
outState.putParcelable("LM", parcelable);
}
#Override
public void onViewStateRestored(#Nullable Bundle savedInstanceState) {
Log.d(TAG, "onViewStateRestored");
super.onViewStateRestored(savedInstanceState);
if (savedInstanceState != null) {
llm = savedInstanceState.onRestoreInstanceState(savedInstanceState.getParcelable("LM"));
}
}
public void inflateRecyclerView() {
//use retrofit to call api
}
P.S.: when I add it dynamically, I hide the , findViewById in the activity, then use
XFragment f = new XFragment.newInstance();
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, f).commit();
Sorry, I know what is causing the problem. I have been using a library call DynamicBox, and it seems that it is causing the problem. I don't know if I have do something wrong or there are bugs on that library yet.
Yay...

Android: Fragments in FragmentPagerAdapter do not display data in landscape after device turned on

I had created tabview with swipe using Fragments and FragmentPagerAdapter. In the fragment's hosting activity, I had added a fragment and that fragment shows tabview using TabHost. 1st tab has a listview that displays data from DB using CursorLoader and the 2nd one is mapView.
Everything works fine in portrait mode.
Problem occurs in following case:
User is using app in landscape mode. He opened tabbed view screen. Data is displayed in the listview from the cursor loader. Mapview is also displayed in the second tab. So far everything is working as required.
He left the app as is.(Means, he did not pressed back button, home button or switched to another app)
Screen went off after some time.
When user unlocks the device, my app's tabbed view will be visible again. But,now listview does not show any data and mapview also disappeared.
ClientListFragment
public class ClientListFragment extends SwipeRefreshListFragment {
private static final String TAG = "ClientListFragment";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate");
setHasOptionsMenu(true);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setColorScheme(R.color.gplus_color_1, R.color.gplus_color_2,
R.color.gplus_color_3, R.color.gplus_color_4);
}
#Override
public void onResume() {
super.onResume();
Log.e(TAG, "onResume");
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.e(TAG, "onActivityCreated");
}
#Override
public void onPause() {
// Log.i(TAG, "onPause");
super.onPause();
}
#Override
public void onStop() {
super.onStop();
Log.e(TAG, "onStop");
}
#Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy");
}
}
Map Fragment
public class ClientMapFragment extends Fragment {
private static final String TAG = "ClientMapFragment";
private GoogleMap googleMap;
#Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.map_fragment, container, false);
googleMap = ((MapFragment) getFragmentManager().findFragmentById(
R.id.map)).getMap();
googleMap.setMyLocationEnabled(true);
return v;
}
#Override
public void onStart() {
super.onStart();
}
#Override
public void onResume() {
super.onResume();
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public void onStop() {
super.onStop();
}
#Override
public void onPause() {
super.onPause();
}
}
Activity
public class ClientActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
boolean isTablet = getResources().getBoolean(R.bool.isTablet);
if (isTablet)
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
else
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
setContentView(R.layout.activity_clients);
ClientFragment clientFragment = new ClientFragment();
Bundle bundle = new Bundle();
bundle.putBoolean("ShouldChangeActionBarTitle", true);
clientFragment.setArguments(bundle);
getFragmentManager()
.beginTransaction()
.add(R.id.fragmentContainer, clientFragment,
getResources().getString(R.string.clients_fragment))
.commit();
}
}
FragmentpagerAdapter
public class TabsPagerAdapter extends FragmentStatePagerAdapter {
private static final String TAG = "TabsPagerAdapter";
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
FragmentManager fm;
public TabsPagerAdapter(FragmentManager fm) {
super(fm);
this.fm = fm;
mFragments.add(new ClientListFragment());
mFragments.add(new ClientMapFragment());
}
#Override
public Fragment getItem(int index) {
Log.e(TAG, "getItem");
return mFragments.get(index);
}
#Override
public int getCount() {
return mFragments.size();
}
}
Update 1
So, after doing hours of research I came to a conclusion that, it is problem with fragment's state in the FragmentPagerAdapter. When screen is turned off fragmentpager's saveState is called.. When the device is unlocked again, fragment's will be restored from previous state. I verified that LoaderManager callbacks are called, and data was also fetched from DB. But, didn't appeared on screen.
I didn't even bothered about looking at my activities code at once. Everytime, a new fragment was created in onCreate(). The fix is very simple.
if (savedInstanceState == null) {
Log.e(TAG, "onCreate savedInstanceState == null");
ClientFragment clientFragment = new ClientFragment();
Bundle bundle = new Bundle();
bundle.putBoolean("ShouldChangeActionBarTitle",
shouldChangeActionBarTitle);
clientFragment.setArguments(bundle);
getFragmentManager()
.beginTransaction()
.add(R.id.fragmentContainer, clientFragment,
getResources().getString(R.string.clients_fragment))
.commit();
}

Categories

Resources