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...
Related
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
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();
}
}
I have fragment contains recyclerview in main activity
Fragment.java
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View view = inflater.inflate(R.layout.activity_main, container, false);
initInstances(view);
setRetainInstance(true);
return view;
}
private void initInstances(View view) {
mRecyclerView = (RecyclerView) view.findViewById(R.id.dialogList);
mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
adapter = new MyAdapter(items);
mRecyclerView.setAdapter(mAdapter);
}
MainActivity.java
in onCreate method:
myFragment = (MyFragment) getSupportFragmentManager().findFragmentByTag(MyFragment.TAG);
if (MyFragment == null) {
MyFragment = new MyFragment();
getSupportFragmentManager().beginTransaction().add(R.id.content_frame,
myFragment, MyFragment.TAG).commit();
}
But when orientation changed, my RecyclerView redrawn.
I've tried save state with overriding onSaveInstanceState and onRestoreInstanceState like this How to prevent custom views from losing state across screen orientation changes or Saving and restoring view state android, but to no avail for me
How to correctly save state of RecyclerView and adapter's items when orientation change?
SOLUTION:
My fragment extends from BaseFragment with some methods:
public class BaseFragment extends Fragment{
Bundle savedState;
public BaseFragment() {
super();
if (getArguments() == null)
setArguments(new Bundle());
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (!restoreStateFromArguments()) {
onFirstTimeLaunched();
}
}
protected void onFirstTimeLaunched() {
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
saveStateToArguments();
}
public void saveStateToArguments() {
if (getView() != null)
savedState = saveState();
if (savedState != null) {
Bundle b = getArguments();
b.putBundle("internalSavedViewState8954201239547", savedState);
}
}
private boolean restoreStateFromArguments() {
Bundle b = getArguments();
savedState = b.getBundle("internalSavedViewState8954201239547");
if (savedState != null) {
onRestoreState(savedState);
return true;
}
return false;
}
private Bundle saveState() {
Bundle state = new Bundle();
onSaveState(state);
return state;
}
protected void onRestoreState(Bundle savedInstanceState) {
}
protected void onSaveState(Bundle outState) {
}
#Override
public void onStart() {
super.onStart();
}
#Override
public void onStop() {
super.onStop();
}
#Override
public void onDestroyView() {
super.onDestroyView();
saveStateToArguments();
}
In my fragment I override methods and save state of mLayoutManager
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
protected void onSaveState(Bundle outState) {
super.onSaveState(outState);
outState.putParcelable("myState", mLayoutManager.onSaveInstanceState());
}
#Override
protected void onRestoreState(Bundle savedInstanceState) {
super.onRestoreState(savedInstanceState);
mLayoutManager.onRestoreInstanceState(savedInstanceState.getParcelable("myState"));
}
I searched how to get the scroll position of the RecyclerView, but didn't see where I can retrieve it, so I think the best solution is to handle rotation changes directly in your java code, and preventing your activity to restart on orientation change.
To prevent restarting on orientation change, you can follow this solution.
After preventing your Activity from restart, you can build 2 xml layout files, one for portrait mode, and one for landscape mode, you add a FrameLayout in both xml files, holding the place for your RecyclerView.
When the onConfigurationChange(Configuration newConfig) method is called, you call setContentView(int layoutFile) with the appropriate LayoutFile following the new orientation, and add your RecyclerView you are holding in a member of your class in the FrameLayout.
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();
}
I am trying to understand when i should use the oncreate method or the oncreateview method.
I am a little bit confused. First i had some code including statements like findViewById() in the OnCreate() method. But it always responded with a null pointer Exception, then someone told me i should put it in the OnCreateView() method. It worked, but i do not understand when and what for code i should put in the OnCreate() or when and what i should put in the OnCreateView(). Could someone please explain this to me.
In my code, the methodology is the following:
ACTIVITY code:
#Override
public void onCreate(Bundle saveInstanceState)
{
super.onCreate(saveInstanceState);
this.setContentView(R.layout.activity_container);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (saveInstanceState == null)
{
getSupportFragmentManager().beginTransaction()
.replace(R.id.activity_container_container, new MyFragment()).addToBackStack(null).commit();
}
getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener()
{
public void onBackStackChanged()
{
int backCount = getSupportFragmentManager().getBackStackEntryCount();
if (backCount == 0)
{
finish();
}
}
});
}
#Override
public void myInterface()
{
System.out.println("Do stuff;");
}
FRAGMENT code:
private int titleId;
private Button placeholderButton;
private MyInterface activity_myInterface;
public MyFragment()
{
super();
this.titleId = R.string.my_actionbar_title;
}
#Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
try
{
activity_myInterface = (MyInterface)activity;
}
catch(ClassCastException e)
{
Log.e(this.getClass().getSimpleName(), "MyInterface interface needs to be implemented by Activity.", e);
throw e;
}
}
//this is an interface defined by me
#Override
public int getActionBarTitleId()
{
return titleId;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_myfragment, container, false);
placeholderButton = (Button)view.findViewById(R.id.myfragment_placeholderbtn);
placeholderButton.setOnClickListener(this);
return view;
}
#Override
public void onClick(View v)
{
if(v == placeholderButton)
{
System.out.println("Do more stuff");
}
}
Where R.layout.activity_container is:
<?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:orientation="vertical" >
<FrameLayout
android:id="#+id/activity_container_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
This way the Activity is responsible for containing the Fragment (and this fragment is created in onCreate()) and the Fragment is responsible for the display of UI and the event handling. You can have more than one fragment on an Activity, though, that's what they primarily were designed for.
If you are using an Activity, then you can just stay away from the onCreateView.
Just stick to the default activity lifecycle:
The layout (content) is inflated (created) at the onCreate method.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book);
}
Then once it has been inflated you are ready to use it and manipulate views that belong to it.
findViewById() returned null for you because the layout was not ready to be used and that's because it was not yet inflated. You can use findViewById() after setContentView() in onCreate.
findViewById() is a method that can be pretty hard on the performance (if called many times), so you should get all the views you need in onCreate and save them to an instance variable like:
TextView textView1, textView2;
Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book);
textView1 = findViewById(R.id.textview1);
textView2 = findViewById(R.id.textview2);
...
}
And use them from onStart:
#Override
protected void onStart() {
super.onStart();
if (textView1 != null) textView1.setText("myText");
if (textView2 != null) textView2.setText("some other text");
}