I'm getting an NPE when I start an activity in my application. It doesn't happen right away when I boot my phone and start debugging on it. After several dozen new builds are pushed to my phone it eventually starts crashing with this error every single time. I can remedy it temporarily by opening a few other activities first before I activate the errant one.
Any ideas what could cause this? It's a somewhat complicated Activity with a DrawerLayout, and a fragment that contains a SwipeRefreshLayout with a ListView.
Exception
java.lang.RuntimeException: Unable to start activity ComponentInfo{}: java.lang.NullPointerException: Attempt to read from field 'boolean android.support.v4.app.BackStackRecord.mAddToBackStack' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2298)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2360)
at android.app.ActivityThread.access$800(ActivityThread.java:144)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1279)
Activity Code
public class TastingsActivity extends BaseActivity implements TastingListFragment.Callbacks {
public static final String TAG = TastingsActivity.class.getSimpleName();
private TastingListFragment mTastingListFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tastings);
//create fragment
// Check that the activity is using the layout version with
// the fragment_container FrameLayout
if (findViewById(R.id.container) != null) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
// Create a new Fragment to be placed in the activity layout
TastingListFragment fragment = new TastingListFragment();
// In case this activity was started with special instructions from an
// Intent, pass the Intent's extras to the fragment as arguments
fragment.setArguments(getIntent().getExtras());
// Add the fragment to the 'fragment_container' FrameLayout
getSupportFragmentManager().beginTransaction()
.add(R.id.container, fragment).commit();
}
}
#Override
protected int getSelfNavDrawerItem() {
return NAVDRAWER_ITEM_TASTINGS;
}
//================================================================================
// Handlers
//================================================================================
#Override
public void onTastingSelected(Tasting tasting) {
Intent intent = new Intent(this, TastingActivity.class);
intent.putExtra(TastingDetailsFragment.EXTRA_TASTING_ID, tasting.getId());
startActivity(intent);
}
}
Related
I know this question is very common and I have read so many different answers but none fits in my problem. In my application, I have an activity and in rhye activity I load a fragment. I also send some data(in the form of Bundle) to the fragment. So my Problem is when the screen is rotated, I save the fragment in onSaveInstanceState Activity method and check in onCreate Method weather savedInstance is null or not and on that basis I load the fragment.
Activity code :
#Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
outState.putParcelable(Const.TAG_REQ_CUSTOM,DetailsItems);
outState.putString(Const.TAG_FLOW, Const.TAG_MAIN_FLOW);
getSupportFragmentManager().putFragment(outState,"current_fragment",fragment);
}
onCreate Method :
if (findViewById(R.id.fragment_frame) != null) {
if (savedInstanceState != null) {
// this invoke when screen rotate but the app crash
DetailsItems = savedInstanceState.getParcelable(Const.TAG_REQ_CUSTOM);
String flow = savedInstanceState.getString(Const.TAG_FLOW);
ft = getSupportFragmentManager().getFragment(savedInstanceState,"current_fragment");
mFragmentManager=getSupportFragmentManager();
mFragmentTransaction = mFragmentManager.beginTransaction();
bundle= new Bundle();
bundle.putString(Const.TAG_FLOW, flow);
bundle.putParcelable(Const.TAG_REQ_BOOKING_DETAILS, bookingDetailsItems);
ft.setArguments(bundle);
mFragmentTransaction.replace(R.id.fragment_frame, ft).commit();
}else{
// load fragment on first time
}
}
So my Question is: Where do I have to save the custom Object(in parent Activity or in fragment) ?
When my saved Instance is not null than app crashesh and logs is :
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()' on a null object reference
You should use ViewModel. ViewModel is specifically made for this purpose.
From the docs:
ViewModel is a class that is responsible for preparing and managing the data for an Activity or a Fragment. It also handles the communication of the Activity / Fragment with the rest of the application (e.g. calling the business logic classes).
use this code in Activity :
if (findViewById(R.id.fragment_frame) != null) {
if (savedInstanceState != null) {
fragment =getSupportFragmentManager().getFragment(savedInstanceState,"current_fragment");
}else{
// load fragment on first time
}
}
and in fragment :
//save custom object
#Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
outState.putParcelable("key",customObject);
}
//now retrieve here
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null)
customObject= savedInstanceState.getParcelable("key");
}
Take a look at onRetainNonConfigurationInstance() and getLastNonConfigurationInstance()
From docs:
Called by the system, as part of destroying an activity due to a configuration change, when it is known that a new instance will immediately be created for the new configuration. You can return any object you like here, including the activity instance itself, which can later be retrieved by calling getLastNonConfigurationInstance() in the new activity instance.
I am using FragmentTransaction.replace() to swap fragments in and out.
The app starts up first time with no problem.
An IllegalStateException is thrown when rotating the device because of a conflict between the savedInstanceState
and commiting a new fragment transaction.
No AsyncTask is involved.
One StackOverflow question suggests to put the setContentView() call
in onResumeFragments(), but this seems to have no effect. Same with onPostResume().
Another StackOverflow question says to override onConfigurationChanged(). This works in that sense that it the exception doesn't occur
because the Activity is not restarted. However, this prevents fragments that have different portrait
and landscape layouts from switching between these layouts. Calling setContentView() in onConfigurationChanged()
causes a similar error (IllegalArgumentException: Binary XML file line #25: Duplicate id 0x12345678, tag null, or parent id with another fragment)
Using fragmentTransaction.commitAllowingStateLoss() instead of .commit() causes IllegalStateException: Activity has been destroyed.
How do I get this to work?
More exception info:
java.lang.RuntimeException: Unable to start
activity ComponentInfo{myapp/myap.MainActivity}:
android.view.InflateException: Binary XML file line #25: Error
inflating class fragment at
myapp.MainActivity.onResumeFragments(MainActivity.java:450)
Caused by: java.lang.IllegalStateException: Can not perform this action after
onSaveInstanceState at > android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1533)
at
myapp.fragments.FragmentChange.onFragmentChange(FragmentChange.java:128)
at
myapp.MainActivity.onNavigationDrawerItemSelected(MainActivity.java:490)
at
myapp.fragments.NavigationDrawerFragment.selectItem(NavigationDrawerFragment.java:197)
at
myapp.fragments.NavigationDrawerFragment.onCreate(NavigationDrawerFragment.java:78)
at myapp.MainActivity.onResumeFragments(MainActivity.java:450)
The sequence in the code upon rotating the device is:
MainActivity.onPause()
MainActivity.saveInstanceState()
NavigationDrawerFragment.onSaveInstanceState()
MainActivity.onStop()
MainActivity.onDestroy()
MainActivity.onCreate()
super.onCreate(savedInstanceState);
MainActivity.onResumeFragments()
setContentView()
NavigationDrawerFragment.onCreate()
MainActivity.onNavigationDrawerItemSelected()
fragmentTransaction.commit();
MainActivity:
public class MainActivity extends AppCompatActivity implements
NavigationDrawerFragment.NavigationDrawerCallbacks {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
#Override
public void onNavigationDrawerItemSelected(int position) {
...
FragmentChangeEvent fragmentChangeEvent = new FragmentChangeEvent(null);
FragmentChange fragmentChange = FragmentChange.getInstance( getSupportFragmentManager());
fragmentChange.onFragmentChange(fragmentChangeEvent);
...
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
super.onSaveInstanceState(outState);
}
#Override
public void onResumeFragments() {
super.onResumeFragments();
// causes onNavigationDrawerItemSelected() to be called, exception thrown
setContentView(myapp.R.layout.activity_main);
mNavigationDrawerFragment = (NavigationDrawerFragment)
getSupportFragmentManager().findFragmentById(myapp.R.id.navigation_drawer);
mNavigationDrawerFragment.setUp( // Set up the drawer
myapp.R.id.navigation_drawer,
(DrawerLayout) findViewById(myapp.R.id.drawer_layout));
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if current fragment is "Individual" { // pseudocode
setContentView(R.layout.activity_main); // causes IllegalArgumentException
}
}
}
NavigationDrawerFragment
public class NavigationDrawerFragment extends Fragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);
if (savedInstanceState != null) {
mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
mFromSavedInstanceState = true;
}
// Select either the default item (0) or the last selected item.
selectItem(mCurrentSelectedPosition);
}
private void selectItem(int position) {
mCurrentSelectedPosition = position;
if (mDrawerListView != null) {
mDrawerListView.setItemChecked(position, true);
}
if (mDrawerLayout != null) {
mDrawerLayout.closeDrawer(mFragmentContainerView);
}
if (mCallbacks != null) {
// calls MainActivity.onNavigationDrawerItemSelected()
mCallbacks.onNavigationDrawerItemSelected(position);
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition);
}
}
FragmentChange
public class FragmentChange implements FragmentChangeListener {
public static FragmentChange getInstance(FragmentManager fragmentManager) {
if (instance == null) {
instance = new FragmentChange(fragmentManager);
}
return instance;
}
// constructor
private FragmentChange(FragmentManager fragmentManager) {
mFragmentManager = fragmentManager;
}
#Override
public void onFragmentChange(FragmentChangeEvent fragmentChangeEvent) {
...
mPosition = fragmentChangeEvent.getPosition();
FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
Fragment fragment = EmployeesVerticalFragment.newInstance();
fragmentTransaction.replace(myapp.R.id.container, fragment);
fragmentTransaction.commit(); // IllegalState exception here
...
}
}
A greatly reduced form of the project on github which reproduces the IllegalStateException:
Some comments:I can see you have gone "above and beyond" trying to fix it (and have scanned stackoverflow for many tips).
Your code works the first time (even if you are in landscape b4 starting app).
The rotation fails to inflate the fragment (second time around) (in activity_main), and also reports the "commit after save" error (that's weird,how on Earth are we getting 2 fatal errors ? maybe on another thread, or ""save" is killing the inflator, but how can it carry on?).
NavigationDrawerFragment has a LOT of important code in it (that normally goes in the Activity).
Analysis:
Your MainActivity class extends AppCompatActivity:
public class MainActivity extends AppCompatActivity implements
NavigationDrawerFragment.NavigationDrawerCallbacks {
Normally you extend FragmentActivity:
public class MainActivity extends FragmentActivity implements
NavigationDrawerFragment.NavigationDrawerCallbacks {
This is because you inflate activity_main, which contains a fragment.
setContentView(com.example.replacefragments.R.layout.activity_main);
Inheritance:
Activity <- FragmentActivity <- AppCompatActivity <- ActionBarActivity
'<-' means inheritance here.
One reason why you would need to consider FragmentActivity specifically is if you want to use nested fragments (a fragment holding another fragment), as that was not supported in native fragments until API Level 17.
I think this is what you are doing here (in activity_main):
<fragment android:id="#+id/navigation_drawer
Here is a good template app that should make your life easier:
android-sliding-menu-using-navigation-drawer/
FIXING IT:
Replace the fragment in activity_main.xml with the ListView.
I have discarded NavigationDrawerFragment, and injected some code from the template above, then re-introduced bit-by-bit your code from NavigationDrawerFragment, until it falls over.
It now works with rotation, but not as you or I would like (preserving fragment state, NavigationDrawerFragment some functionality), so I'm still working on it.
protraitlandscape after rotation
Hot tip:
The .commit sounds final does it not ? Sounds as if it should synchronous? Neither are true.
The .commit puts the transaction in a pending queue, to truly execute it you need:
getSupportFragmentManager().executePendingTransactions();
Here's a link to the error log of the posted code on rotation:
link error log
Method 1. Avoid config changes. Add android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|keyboard" in you manifest activity.
<activity
android:name=".ui.accounts.YouActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|keyboard"
android:theme="#style/AppTheme.NoActionBar" />
Method 2.
a. First of all do not miss place code from default places like setContentView() in onConfigurationChanged().
b. Replace fragment with tag or by id in xml
c. Don't replace again if fragment found by id or tag.
d. Make sure you are dismissing any dailog inside fragment onPause.
The FragmentManager is an
Interface for interacting with Fragment objects inside of an Activity.
It strikes me as a particular bad idea to have save it in a static field and reuse and old FragmentManager for a new activity. This will necessarily lead to Activity has been destroyed, when the new activity interact with the manager from the old activity.
In your code, replace
FragmentChange.getInstance(getFragmentManager());
by
new FragmentChange(getFragmentManager());
I have a simple problem.
My problem is that I have two activities:
Activity A
Activity B
In Activity A I display 4-5 fragments. That is the main activity (Navigation Drawer) so I display 4-5 fragments in it.
From all fragments it redirects to Activity B.
But I want to display the last opened fragment when I come back from Activity B.
Now it directly opens the first fragment, which is the default. I want to open the last opened fragment when the user returns to the first activity.
Please help me...
You can use onSaveInstanceState in Activity A to save information about last opened fragment and onRestoreInstanceState/onCreate to restore this information. For example:
private static final String LAST_OPENED_FRAGMENT_REF = "LAST_OPENED_FRAGMENT_REF";
private static final int HOME_FRAGMENT = 0;
private static final int OTHER_FRAGMENT = 1;
private int currentOpenedFragment = HOME_FRAGMENT;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
if (savedInstanceState != null) {
currentOpenedFragment = savedInstanceState.getInt(LAST_OPENED_FRAGMENT_REF);
}
if (navigationView != null) {
Fragment fragment = initFragmentByType(currentOpenedFragment);
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragment_container, fragment)
.commit();
setupDrawerContent(navigationView);
}
...
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(LAST_OPENED_FRAGMENT_REF, currentOpenedFragment);
}
private Fragment initFragmentByType(int type) {
switch(type) {
case HOME_FRAGMENT: return new Home();
case OTHER_FRAGMENT: return new Other();
default: throw new IllegalArgumentException("There is no type: " + type);
}
}
And don't forget to update currentOpenedFragment field in onNavigationItemSelected callback.
If your first activity changes with the second one, then fragments of the former activity will destroy themself because of the activity lifecycle.
You should just open first activity using startActivity and store the last active fragment before it goes the letter activity.
You don't use finish while call second activity. For exp: `
Intent i = new Intent(getApplicationContext(), ActivityB.class);
startActivity(i);
//finish(); `
This might have already answered but I am still troubling with a function like this. Let's say I have activity A and activity B. B holds a viewpager with several fragments in it. I would like to call a function in the fragment held by activity B from activity A.
I used callbacks many times to communicate between activites and fragments but every single time it was only the fragment and its holder activity. I do not want to make a static method (the callback listener cannot be static anyway) so it causes a headache for me. The simple static solution to make a static method in the fragment and have it called from the other actually works very well, but I am not sure if it was a good idea as I need to change several things static.
So communicating between Activity B and its fragments is ok, but I cannot call this method in Activity A.
Activity B:
public class ActivityB extends FragmentActivity implements Fragment1.OnWhateverListener
{
...
#Override
public void onWhateverSelected(int position) {
//stuff, here I can call any function in Fragment 1
}
}
The following code snippet is a wrong solution (doesnt even work) but makes a better picture what I would like to do.
Activity A:
ActivityB ab = new ActivityB ();
ab.onWhateverSelected(number);
So how can I do this?
Thank you!
EDIT
Activity A: the method I call
Bundle args = new Bundle();
args.putString("ID", id); // the data to send
Intent frag_args = new Intent(Intent.ACTION_VIEW);
frag_args.setClass(this, MainActivity.class);
frag_args.putExtra("args", args);
startActivity(frag_args);
Activity B:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
...
processIntent(getIntent()); //last line of onCreate, always gets called here
}
#Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
processIntent(intent); // this never gets called here only in OnCreate
}
private void processIntent(Intent intent) {
Bundle args = intent.getBundleExtra("args");
if (args != null) { // check if ActivityB is started to pass data to fragments
String id = args.getString("ID");
Log.i("ID_FROM", "id: " + id); //works well
if (id != null) {
List<Fragment> fragments = new ArrayList<Fragment>();
fragments = getSupportFragmentManager().getFragments();
//NULLPOINTER for the following line
FragmentMainDiscover fr = (FragmentMainDiscover) fragments.get(0);
fr.RefreshHoverView(id);
}
}
}
You are right to stay away from statics. Way too risky, for visual objects that may or may not be on screen.
I would recommend going through activity B, since it is the parent of your target fragment. Create an Intent that starts activity B, and include an intent extra that tells activity B what it should do to the target fragment. Then activity B can make sure that the fragment is showing, and pass the information on to it.
One other idea to pass the info to the fragment is to use setArguments, rather than direct calls. This is a nice approach because Android will restore the arguments automatically if the activity and its fragments are removed from memory.
Does this make sense? Do you want the code?
EDIT
To use arguments, you still need to have activity A go through activity B. This is because activity A doesn't know if activity B, and all its fragments, is running unless it sends it an Intent. But you can include data targeted for the fragments, by putting them inside the intent. Like this:
public class ActivityA extends Activity {
public static final String KEY_FRAG = "frag"; // tells activity which fragment gets the args
public static final String KEY_ARGS = "args";
public static final String KEY_MY_PROPERTY = "myProperty";
public void foo() {
Bundle args = new Bundle();
args.putString(KEY_FRAG, "frag1Tag"); // which fragment gets the data
args.putCharSequence(KEY_MY_PROPERTY, "someValue"); // the data to send
// Send data via an Intent, to make sure ActivityB is running
Intent frag_args = new Intent(Intent.ACTION_VIEW);
frag_args.setClass(this, ActivityB.class);
frag_args.putExtra(KEY_ARGS, args);
startActivity(frag_args);
}
}
public class ActivityB extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//TODO configure activity including fragments
processIntent(getIntent()); // this call is in case ActivityB was not yet running
}
#Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
processIntent(intent); // this call is in case ActivityB was already running
}
private void processIntent(Intent intent) {
Bundle args = intent.getBundleExtra(ActivityA.KEY_ARGS);
if (args != null) { // check if ActivityB is started to pass data to fragments
String fragTag = args.getString(ActivityA.KEY_FRAG);
if (fragTag != null) {
Fragment frag = getSupportFragmentManager().findFragmentByTag(fragTag);
frag.setArguments(args);
//TODO either show the fragment, or call a method on it to let it know it has new arguments
}
}
}
}
public class Fragment1 extends Fragment {
public static final String TAG = "frag1Tag";
#Override
public void onResume() {
super.onResume();
Bundle args = getArguments();
String value = args.getString(ActivityA.KEY_MY_PROPERTY);
...
}
}
My application consists of several fragments. Up until now I've had references to them stored in a custom Application object, but I am beginning to think that I'm doing something wrong.
My problems started when I realized that all my fragment's references to mActivity becomes null after an orientation change. So when I call getActivity() after an orientation change, a NullPointerException is thrown.
I have checked that my fragment's onAttach() is called before I make the call to getActivity(), but it still returns null.
The following is a stripped version of my MainActivity, which is the only activity in my application.
public class MainActivity extends BaseActivity implements OnItemClickListener,
OnBackStackChangedListener, OnSlidingMenuActionListener {
private ListView mSlidingMenuListView;
private SlidingMenu mSlidingMenu;
private boolean mMenuFragmentVisible;
private boolean mContentFragmentVisible;
private boolean mQuickAccessFragmentVisible;
private FragmentManager mManager;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/*
* Boolean variables indicating which of the 3 fragment slots are visible at a given time
*/
mMenuFragmentVisible = findViewById(R.id.menuFragment) != null;
mContentFragmentVisible = findViewById(R.id.contentFragment) != null;
mQuickAccessFragmentVisible = findViewById(R.id.quickAccessFragment) != null;
if(!savedInstanceState != null) {
if(!mMenuFragmentVisible && mContentFragmentVisible) {
setupSlidingMenu(true);
} else if(mMenuFragmentVisible && mContentFragmentVisible) {
setupSlidingMenu(false);
}
return;
}
mManager = getSupportFragmentManager();
mManager.addOnBackStackChangedListener(this);
final FragmentTransaction ft = mManager.beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if (!mMenuFragmentVisible && mContentFragmentVisible) {
/*
* Only the content fragment is visible, will enable sliding menu
*/
setupSlidingMenu(true);
onToggle();
ft.replace(R.id.contentFragment, getCustomApplication().getSportsFragment(), SportsFragment.TAG);
} else if (mMenuFragmentVisible && mContentFragmentVisible) {
setupSlidingMenu(false);
/*
* Both menu and content fragments are visible
*/
ft.replace(R.id.menuFragment, getCustomApplication().getMenuFragment(), MenuFragment.TAG);
ft.replace(R.id.contentFragment, getCustomApplication().getSportsFragment(), SportsFragment.TAG);
}
if (mQuickAccessFragmentVisible) {
/*
* The quick access fragment is visible
*/
ft.replace(R.id.quickAccessFragment, getCustomApplication().getQuickAccessFragment());
}
ft.commit();
}
private void setupSlidingMenu(boolean enable) {
/*
* if enable is true, enable sliding menu, if false
* disable it
*/
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// launch the fragment that was clicked from the menu
}
#Override
public void onBackPressed() {
// Will let the user press the back button when
// the sliding menu is open to display the content.
if (mSlidingMenu != null && mSlidingMenu.isMenuShowing()) {
onShowContent();
} else {
super.onBackPressed();
}
}
#Override
public void onBackStackChanged() {
/*
* Change selected position when the back stack changes
*/
if(mSlidingMenuListView != null) {
mSlidingMenuListView.setItemChecked(getCustomApplication().getSelectedPosition(), true);
}
}
#Override
public void onToggle() {
if (mSlidingMenu != null) {
mSlidingMenu.toggle();
}
}
#Override
public void onShowContent() {
if (mSlidingMenu != null) {
mSlidingMenu.showContent();
}
}
}
The following is a stripped version of the CustomApplication. My thoughts behind this implementation was to guarantee only one instance of each fragment throughout my application's life cycle.
public class CustomApplication extends Application {
private Fragment mSsportsFragment;
private Fragment mCarsFragment;
private Fragment mMusicFragment;
private Fragment mMoviesFragment;
public Fragment getSportsFragment() {
if(mSsportsFragment == null) {
mSsportsFragment = new SportsFragment();
}
return mSsportsFragment;
}
public Fragment getCarsFragment() {
if(mCarsFragment == null) {
mCarsFragment = new CarsFragment();
}
return mCarsFragment;
}
public Fragment getMusicFragment() {
if(mMusicFragment == null) {
mMusicFragment = new MusicFragment();
}
return mMusicFragment;
}
public Fragment getMoviesFragment() {
if(mMoviesFragment == null) {
mMoviesFragment = new MoviesFragment();
}
return mMoviesFragment;
}
}
I am very interested in tips on how to best implement multiple fragments and how to maintain their states. For your information, my applicaion consists of 15+ fragments so far.
I have done some research and it seems that FragmentManager.findFragmentByTag() is a good bet, but I haven't been able to successfully implement it.
My implementation seems to work good except for the fact that mActivity references become null after orientation changes, which lets me to believe that I may have some memory leak issues as well.
If you need to see more code, please let me know. I purposely avoided including fragment code as I strongly believe issues are related to my Activity and Application implementations, but I may be wrong.
Thanks for your time.
My thoughts behind this implementation was to guarantee only one instance of each fragment throughout my application's life cycle
This is probably part, if not all, of the source of your difficulty.
On a configuration change, Android will re-create your fragments by using the public zero-argument constructor to create a new instance. Hence, your global-scope fragments will not "guarantee only one instance of each fragment".
Please delete this custom Application class. Please allow the fragments to be re-created naturally, or if they need to live for the life of a single activity, use setRetainInstance(true). Do not attempt to reuse fragments across activities.
I don't see where are you using the reference to mActivity. But don't hold a reference to it. Always use getActivity since the Activity can be recreated after orientation change. Also, don't ever set the fragment's fields by setters or by assigning always use a Bundle and Arguments
Best practice for instantiating a new Android Fragment
Also you can use setRetainInstance(true) to keep all the fragment's members during orientation change.
Understanding Fragment's setRetainInstance(boolean)
To resolve this problem you have to use the activity object provided by onAttach method of fragment so when you change the orientation fragment is recreated so onAttach give you the current reference
you can use onAttach(Context context) to create a private context variable in fragment like this
#Override
public void onAttach(Context context) {
this.context = context;
super.onAttach(context);
}
on changing orientation, onAttach gives you new reference to the context, if you want reference to activity, you can typecast context to activity.
Context can also be reassigned inside onCreate in fragments as OnCreate is called when device is rotated
private Context mContext;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//get new activity reference here
mContext = getActivity();
}
pass this mContext throughout the fragment
If you don't setRetainInstance(true) in onCreate ... the collection e.g List<Object>, Vector<Object> in Application class will get null. Make sure you setRetainInstance(true) to make them alive.