I have a fragment (F1) with a public method like this
public void asd() {
if (getActivity() == null) {
Log.d("yes","it is null");
}
}
and yes when I call it (from the Activity), it is null...
FragmentTransaction transaction1 = getSupportFragmentManager().beginTransaction();
F1 f1 = new F1();
transaction1.replace(R.id.upperPart, f1);
transaction1.commit();
f1.asd();
It must be something that I am doing very wrong, but I don't know what that is.
commit schedules the transaction, i.e. it doesn't happen straightaway but is scheduled as work on the main thread the next time the main thread is ready.
I'd suggest adding an
onAttach(Activity activity)
method to your Fragment and putting a break point on it and seeing when it is called relative to your call to asd(). You'll see that it is called after the method where you make the call to asd() exits. The onAttach call is where the Fragment is attached to its activity and from this point getActivity() will return non-null (nb there is also an onDetach() call).
The best to get rid of this is to keep activity reference when onAttach is called and use the activity reference wherever needed, for e.g.
#Override
public void onAttach(Context context) {
super.onAttach(activity);
mContext = context;
}
#Override
public void onDetach() {
super.onDetach();
mContext = null;
}
This happened when you call getActivity() in another thread that finished after the fragment has been removed. The typical case is calling getActivity() (ex. for a Toast) when an HTTP request finished (in onResponse for example).
To avoid this, you can define a field name mActivity and use it instead of getActivity(). This field can be initialized in onAttach() method of Fragment as following:
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof Activity){
mActivity =(Activity) context;
}
}
In my projects, I usually define a base class for all of my Fragments with this feature:
public abstract class BaseFragment extends Fragment {
protected FragmentActivity mActivity;
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof Activity){
mActivity =(Activity) context;
}
}
}
Happy coding,
The other answers that suggest keeping a reference to the activity in onAttach are just suggesting a bandaid to the real problem. When getActivity returns null it means that the Fragment is not attached to the Activity. Most commonly this happens when the Activity has gone away due to rotation or the Activity being finished but the Fragment still has some kind of callback listener registered preventing it from being garbage collected. When the listener gets called if you need to do something with the Activity but the Activity is gone there isn't much you can do. In your code you should just check getActivity() != null and if it's not there then don't do anything. If you keep a reference to the Activity that is gone you are preventing the Activity from being garbage collected. Any UI things you might try to do won't be seen by the user. I can imagine some situations where in the callback listener you want to have a Context for something non-UI related, in those cases it probably makes more sense to get the Application context. Note that the only reason that the onAttach trick isn't a big memory leak is because normally after the callback listener executes it won't be needed anymore and can be garbage collected along with the Fragment, all its View's and the Activity context. If you setRetainInstance(true) there is a bigger chance of a memory leak because the Activity field will also be retained but after rotation that could be the previous Activity not the current one.
Since Android API level 23, onAttach(Activity activity) has been deprecated. You need to use onAttach(Context context). http://developer.android.com/reference/android/app/Fragment.html#onAttach(android.app.Activity)
Activity is a context so if you can simply check the context is an Activity and cast it if necessary.
#Override
public void onAttach(Context context) {
super.onAttach(context);
Activity a;
if (context instanceof Activity){
a=(Activity) context;
}
}
PJL is right.
I have used his suggestion and this is what i have done:
defined global variables for fragment:
private final Object attachingActivityLock = new Object();
private boolean syncVariable = false;
implemented
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
synchronized (attachingActivityLock) {
syncVariable = true;
attachingActivityLock.notifyAll();
}
}
3 . I wrapped up my function, where I need to call getActivity(), in thread, because if it would run on main thread, i would block the thread with the step 4. and onAttach() would never be called.
Thread processImage = new Thread(new Runnable() {
#Override
public void run() {
processImage();
}
});
processImage.start();
4 . in my function where I need to call getActivity(), I use this (before the call getActivity())
synchronized (attachingActivityLock) {
while(!syncVariable){
try {
attachingActivityLock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
If you have some UI updates, remember to run them on UI thread. I need to update ImgeView so I did:
image.post(new Runnable() {
#Override
public void run() {
image.setImageBitmap(imageToShow);
}
});
The order in which the callbacks are called after commit():
Whatever method you call manually right after commit()
onAttach()
onCreateView()
onActivityCreated()
I needed to do some work that involved some Views, so onAttach() didn't work for me; it crashed. So I moved part of my code that was setting some params inside a method called right after commit() (1.), then the other part of the code that handled view inside onCreateView() (3.).
I am using OkHttp and I just faced this issue.
For the first part #thucnguyen was on the right track.
This happened when you call getActivity() in another thread that finished after the fragment has been removed. The typical case is calling getActivity() (ex. for a Toast) when an HTTP request finished (in onResponse for example).
Some HTTP calls were being executed even after the activity had been closed (because it can take a while for an HTTP request to be completed). I then, through the HttpCallback tried to update some Fragment fields and got a null exception when trying to getActivity().
http.newCall(request).enqueue(new Callback(...
onResponse(Call call, Response response) {
...
getActivity().runOnUiThread(...) // <-- getActivity() was null when it had been destroyed already
IMO the solution is to prevent callbacks to occur when the fragment is no longer alive anymore (and that's not just with Okhttp).
The fix: Prevention.
If you have a look at the fragment lifecycle (more info here), you'll notice that there's onAttach(Context context) and onDetach() methods. These get called after the Fragment belongs to an activity and just before stop being so respectively.
That means that we can prevent that callback to happen by controlling it in the onDetach method.
#Override
public void onAttach(Context context) {
super.onAttach(context);
// Initialize HTTP we're going to use later.
http = new OkHttpClient.Builder().build();
}
#Override
public void onDetach() {
super.onDetach();
// We don't want to receive any more information about the current HTTP calls after this point.
// With Okhttp we can simply cancel the on-going ones (credits to https://github.com/square/okhttp/issues/2205#issuecomment-169363942).
for (Call call : http.dispatcher().queuedCalls()) {
call.cancel();
}
for (Call call : http.dispatcher().runningCalls()) {
call.cancel();
}
}
Where do you call this function? If you call it in the constructor of Fragment, it will return null.
Just call getActivity() when the method onCreateView() is executed.
Do as follows. I think it will be helpful to you.
private boolean isVisibleToUser = false;
private boolean isExecutedOnce = false;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_my, container, false);
if (isVisibleToUser && !isExecutedOnce) {
executeWithActivity(getActivity());
}
return root;
}
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
this.isVisibleToUser = isVisibleToUser;
if (isVisibleToUser && getActivity()!=null) {
isExecutedOnce =true;
executeWithActivity(getActivity());
}
}
private void executeWithActivity(Activity activity){
//Do what you have to do when page is loaded with activity
}
Those who still have the problem with onAttach(Activity activity), Its just changed to Context -
#Override
public void onAttach(Context context) {
super.onAttach(context);
this.context = context;
}
In most cases saving the context will be enough for you - for example if you want to do getResources() you can do it straight from the context. If you still need to make the context into your Activity do so -
#Override
public void onAttach(Context context) {
super.onAttach(context);
mActivity a; //Your activity class - will probably be a global var.
if (context instanceof mActivity){
a=(mActivity) context;
}
}
As suggested by user1868713.
Another good solution would be using Android's LiveData with MVVM architecture.
You would define a LiveData object inside your ViewModel and observe it in your fragment, and when LiveData value is changed, it would notify your observer (fragment in this case) only if your fragment is in active state, so it would be guaranteed that you would make your UI works and access the activity only when your fragment is in active state. This is one advantage that comes with LiveData
Of course when this question was first asked, there was no LiveData. I am leaving this answer here because as I see, there is still this problem and it could be helpful to someone.
Call getActivity() method inside the onActivityCreated()
I have solved my problem this way.I have passed getApplicationContext from the previous class which has already access of getApplicationContext.I have passed Inputstream object to my new class Nutrients.
try{
InputStream is= getApplicationContext().getAssets().open("nutrient_list.json");
Nutrients nutrients=Nutrients.getNutrients(topRecognition,is);
} catch (IOException e) {
e.printStackTrace();
}
Write a common method that will ensure you will never get null Activity.
public class BaseFragment extends Fragment {
private Context contextNullSafe;
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
/*View creation related to this fragment is finished here. So in case if contextNullSafe is null
* then we can populate it here.In some discussion in - https://stackoverflow.com/questions/6215239/getactivity-returns-null-in-fragment-function
* and https://stackoverflow.com/questions/47987649/why-getcontext-in-fragment-sometimes-returns-null,
* there are some recommendations to call getContext() or getActivity() after onCreateView() and
* onViewCreated() is called after the onCreateView() call every time */
if (contextNullSafe == null) getContextNullSafety();
}
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
contextNullSafe = context;
}
/**CALL THIS IF YOU NEED CONTEXT*/
public Context getContextNullSafety() {
if (getContext() != null) return getContext();
if (getActivity() != null) return getActivity();
if (contextNullSafe != null) return contextNullSafe;
if (getView() != null && getView().getContext() != null) return getView().getContext();
if (requireContext() != null) return requireContext();
if (requireActivity() != null) return requireActivity();
if (requireView() != null && requireView().getContext() != null)
return requireView().getContext();
return null;
}
/**CALL THIS IF YOU NEED ACTIVITY*/
public FragmentActivity getActivityNullSafety() {
if (getContextNullSafety() != null && getContextNullSafety() instanceof FragmentActivity) {
/*It is observed that if context it not null then it will be
* the related host/container activity always*/
return (FragmentActivity) getContextNullSafety();
}
return null;
}
Related
I have an activity that contains a fragment , and this fragment plays youtube videos using android youtube api and I register an event listener for the youtube video when it finishes.
On the onVideoEnded event I get a reference to the activity to do something some action and this is how I get the activity inside my fragment :
private Activity theActivity;
#Override
public void onAttach(Context context) {
super.onAttach(context);
theActivity = (Activity) context;
}
/*
* Deprecated on API 23
* Use onAttachToContext instead
*/
#SuppressWarnings("deprecation")
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (Build.VERSION.SDK_INT < 23) {
theActivity = activity;
}
}
#Override
public void onDetach() {
super.onDetach();
theActivity = null;
}
protected Activity getTheActivity() {
Activity activity = getActivity();
if (activity != null)
return activity;
else return this.theActivity;
}
As you can see on my getTheActivity method I check first the getActivity method , and if it returns a null value , I get the variable I got from onAttach when the fragment was created.
However on devices like samsung getTheActivity method still returns null !
I think it's probably a low memory issue !
How can I get a reference to my activity under any circumstances from my fragment ?
Add these two lines in your manifest file in the application tag
android:hardwareAccelerated="false"
android:largeHeap="true"
I have code that looks like this:
(Note: Notice the line at the bottom where I get reports of a NullPointerException. Also, this has been heavily edited for this posting but I believe all of the relevant code is included)
public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
public SettingsActivity activity;
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof Activity) {
activity = (SettingsActivity) context;
}
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getActivity());
}
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String prefKey) {
// The line below gets many NullPointerException reports
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity);
}
}
I thought by setting the activity variable in onAttach() would almost assure that is is not null. (instead of using getActivity())
The instances I have of getActivity() have 0 reports of ever being null. It seems I just need to use that instead. But I would like to know how activity is null and is changing it to getActivity() the way to go?
FWIW, I am not using a onDetach() in this class.
Just a guess: It seems to me that onAttach() can be called well before onSharedPreferenceChanged() might be called, so maybe the activity becomes null by then?
There is always the possibility the activity variable is not null and it is something else in that line, but this seems to be the most obvious to me?
You should use
#Override
public void onAttach (Activity activity) {
super.onAttach(activity)
}
instead
#Override
public void onAttach (Context context) {
super.onAttach(context)
}
Althought the first function was deprecated but it still work as well. Let try.
Why getContext() sometimes returns null? I pass context to LastNewsRVAdapter.java as an argument. But LayoutInflater.from(context) sometimes crashes. I'm getting a few crash reports on play console. Below is crash report.
java.lang.NullPointerException
com.example.adapters.LastNewsRVAdapter.<init>
java.lang.NullPointerException:
at android.view.LayoutInflater.from (LayoutInflater.java:211)
at com.example.adapters.LastNewsRVAdapter.<init> (LastNewsRVAdapter.java)
at com.example.fragments.MainFragment$2.onFailure (MainFragment.java)
or .onResponse (MainFragment.java)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall$1$1.run
(ExecutorCallAdapterFactory.java)
at android.os.Handler.handleCallback (Handler.java:808)
at android.os.Handler.dispatchMessage (Handler.java:103)
at android.os.Looper.loop (Looper.java:193)
at android.app.ActivityThread.main (ActivityThread.java:5299)
at java.lang.reflect.Method.invokeNative (Method.java)
at java.lang.reflect.Method.invoke (Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run
(ZygoteInit.java:825)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:641)
at dalvik.system.NativeStart.main (NativeStart.java)
This is LastNewsRVAdapter.java constructor.
public LastNewsRVAdapter(Context context, List<LatestNewsData>
latestNewsDataList, FirstPageSideBanner sideBanner) {
this.context = context;
this.latestNewsDataList = latestNewsDataList;
inflater = LayoutInflater.from(context);
this.sideBanner = sideBanner;
}
This is the code onCreateView inside Fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
final View view = inflater.inflate(R.layout.fragment_main_sonku_kabar, container, false);
tvSonkuKabar = view.findViewById(R.id.textview_sonku_kabar_main);
tvNegizgiKabar = view.findViewById(R.id.textview_negizgi_kabar_main);
refresh(view);
final SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.mainRefreshSonkuKabar);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
refresh(view);
swipeRefreshLayout.setRefreshing(false);
}
});
setHasOptionsMenu(true);
return view;
}
This is refresh method inside Fragment
private void refresh(final View view) {
sideBanner = new FirstPageSideBanner();
final RecyclerView rvLatestNews = (RecyclerView) view.findViewById(R.id.recViewLastNews);
rvLatestNews.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
rvLatestNews.setNestedScrollingEnabled(false);
App.getApiService().getLatestNews().enqueue(new Callback<LatestNews>() {
#Override
public void onResponse(Call<LatestNews> call, Response<LatestNews> response) {
if (response.isSuccessful() && response.body().isSuccessfull()){
adapter = new LastNewsRVAdapter(getContext(), response.body().getData(), sideBanner);
rvLatestNews.setAdapter(adapter);
tvSonkuKabar.setVisibility(View.VISIBLE);
}
}
#Override
public void onFailure(Call<LatestNews> call, Throwable t) {
}
});
As the accepted answer says, you must look into the way you are using and caching context. Somehow you are leaking context that is causing Null Pointer Exception.
Below Answer is as per the first revision of the question.
Use onAttach() to get Context. Most of the cases, you don't need this solution so use this solution only if any other solution does not work. And you may create a chance to activity leak, so be cleaver while using it. You may require to make context null again when you leave from fragment
// Declare Context variable at class level in Fragment
private Context mContext;
// Initialise it from onAttach()
#Override
public void onAttach(Context context) {
super.onAttach(context);
mContext = context;
}
This context will be available in onCreateView, so You should use it.
From Fragment Documentation
Caution: If you need a Context object within your Fragment, you can call getContext(). However, be careful to call getContext() only
when the fragment is attached to an activity. When the fragment is not
yet attached, or was detached during the end of its lifecycle,
getContext() will return null.
First of all, as you can see on this link, the method onCreateView() inside the fragment's lifecycle comes after onAttach(), so you should have already a context at that point. You may wonder, why does getContext() return null then? the problem lies on where you are creating your adapter:
App.getApiService().getLatestNews().enqueue(new Callback<LatestNews>() {
#Override
public void onResponse(Call<LatestNews> call, Response<LatestNews> response) {
if (response.isSuccessful() && response.body().isSuccessfull()){
adapter = new LastNewsRVAdapter(getContext(), response.body().getData(), sideBanner);
rvLatestNews.setAdapter(adapter);
tvSonkuKabar.setVisibility(View.VISIBLE);
}
}
#Override
public void onFailure(Call<LatestNews> call, Throwable t) {
}
});
Though you are specifying a callback in onCreateView(), that does not mean the code inside that callback will run at that point. It will run and create the adapter after the network call is done. With that in mind, your callback may run after that point in the lifecycle of the fragment. What I mean is that the user can enter that screen (fragment) and go to another fragment or return to the previous one before the network request finishes (and the callback runs). If that happens, then getContext() could return null if the user leaves the fragment (onDetach() may have been called).
Besides, you can have memory leaks also, in case the activity is destroyed before your network request finishes. So you have two issues there.
My suggestions to solve those issues are:
in order to avoid the null pointer exception and the memory leak, you should cancel the network request when the onDestroyView() inside the fragment is being called (retrofit returns an object that can cancel the request: link).
Another option that will prevent the null pointer exception is to move the creation of the adapter LastNewsRVAdapter outside the callback and keep a reference to it in the fragment. Then, use that reference inside the callback to update the content of the adapter: link
So getContext() is not called in onCreateView(). It's called inside the onResponse() callback which can be invoked anytime. If it's invoked when your fragment is not attached to activity, getContext() will return null.
The ideal solution here is to Cancel the request when the activity/fragment is not active (like user pressed back button, or minimized the app).
Another solution is to simply ignore whatever is in onResponse() when your fragment is not attached to your activity, like this: https://stackoverflow.com/a/10941410/4747587
When you are sure fragment is attached to its host(onResume, onViewCreated, etc) use this instead of getContext() :
requireContext()
It will not be inspected by lint, but it will throw an Exception if context detached!
Then you should check nullity by if clouse (or some thing) or be sure that if the program reaches this line, it isn't null.
In retrofit calls or retrofit (and may others in the same way), it will return a disposable that must be cleared or disposed before onDestroy.
Write a common method that will ensure you will never get null Context.
public class BaseFragment extends Fragment {
private Context contextNullSafe;
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
/*View creation related to this fragment is finished here. So in case if contextNullSafe is null
* then we can populate it here.In some discussion in - https://stackoverflow.com/questions/6215239/getactivity-returns-null-in-fragment-function
* and https://stackoverflow.com/questions/47987649/why-getcontext-in-fragment-sometimes-returns-null,
* there are some recommendations to call getContext() or getActivity() after onCreateView() and
* onViewCreated() is called after the onCreateView() call every time */
if (contextNullSafe == null) getContextNullSafety();
}
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
contextNullSafe = context;
}
/**CALL THIS IF YOU NEED CONTEXT*/
public Context getContextNullSafety() {
if (getContext() != null) return getContext();
if (getActivity() != null) return getActivity();
if (contextNullSafe != null) return contextNullSafe;
if (getView() != null && getView().getContext() != null) return getView().getContext();
if (requireContext() != null) return requireContext();
if (requireActivity() != null) return requireActivity();
if (requireView() != null && requireView().getContext() != null)
return requireView().getContext();
return null;
}
/**CALL THIS IF YOU NEED ACTIVITY*/
public FragmentActivity getActivityNullSafety() {
if (getContextNullSafety() != null && getContextNullSafety() instanceof FragmentActivity) {
/*It is observed that if context it not null then it will be
* the related host/container activity always*/
return (FragmentActivity) getContextNullSafety();
}
return null;
}
when I start the app the first time the code below works just fine. But when leaving the app and opening it again I get an error saying getActivity() returns null.
I'm doing this code in a Fragment:
(getActivity()).runOnUiThread(new Runnable() {
#Override
public void run() {
enableMenu();
openMenu();
navigateToFragment(new BlankFragment());
}
});
What to do ?
How can I get the Activity ?
Create object of Activity and assign that on the onAttach Method like below.
Some times getActivity gives null so its a better way to make activity instance in onAttach and use that instance.
private Activity mActivity;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mActivity = activity;
}
Now use this object instead of the getActivity()
The method onAttach(Activity activity) is now deprecated.
You should use this one:
#Override
public void onAttach(Context context) {
super.onAttach(context);
activity = getActivity();
}
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.