Why getContext() in fragment sometimes returns null? - android

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;
}

Related

I get Fragment not attached to a context. What context need to use?

I get the Exception from Firebase Crashlytics
Fatal Exception: java.lang.IllegalStateException: Fragment MyFragment{122418b (05b123e6-aa8d-4de4-8f7e-49c95018234b)} not attached to a context.
at androidx.fragment.app.Fragment.requireContext(Fragment.java:774)
at androidx.fragment.app.Fragment.getResources(Fragment.java:838)
at com.timskiy.pregnancy.fragments.MyFragment$1$1.run(MyFragment.java:156)
at android.os.Handler.handleCallback(Handler.java:907)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:216)
at android.app.ActivityThread.main(ActivityThread.java:7625)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:524)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:987)
Error line from fragment
imageView.setColorFilter(ContextCompat.getColor(getContext(), R.color.blue));
also tried
imageView.setColorFilter(getResources().getColor(R.color.blue));
I use viewPager in Activity and FragmentStatePagerAdapter. What context I need to use in fragment to setColorFilter? Thx
Add this in your fragment:
private Context mContext;
#Override
public void onAttach(Context context) {
super.onAttach(activity);
mContext = context;
}
#Override
public void onDetach() {
super.onDetach();
mContext = null;
}
And in your image view
imageView.setColorFilter(ContextCompat.getColor(mContext, R.color.blue));
You are getting this crash because you are trying to call getContext() from a fragment that has already been detached from the parent Activity.
From the stacktrace it appears that there is a call to MyFragment.java line 156 from a Handler, this leads me to assume that its some background work happening but its getting completed when the fragment has been detached.
A quick fix for this would be to check if the fragment is attached to activity before attempting to execute any line of code that modifies the view.
if (isAttachedToActivity()){
imageView.setColorFilter(ContextCompat.getColor(getContext(), R.color.blue));
}
or
if (isAttachedToActivity()){
imageView.setColorFilter(getResources().getColor(R.color.blue));
}
The isAttachedToActivity() looks like this:
public boolean isAttachedToActivity() {
boolean attached = isVisible() && getActivity() != null;
return attached;
}
Try to use application context to fetch app resources to prevent IllegalStateException (not attached to a context)
// Init global variable with the application context first:
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
if (appContext == null)
appContext = context.getApplicationContext();
}
Then use appContext var anywhere you want to get app resources ex:
imageView.setColorFilter(ContextCompat.getColor(appContext, R.color.blue));
Probably you're trying to reach ViewPager's child Fragmnet's components, when the fragment is not created yet, or it's already destroyed. Could you make your post more detailed?
In your fragment it is safe to use requireContext() / requireActivity() inside onViewCreated instead of getContext() / getActivity().
imageView.setColorFilter(ContextCompat.getColor(requireContext(), R.color.blue));

java.lang.IllegalStateException: Fragment not attached to Activity

I am rarely getting this error while making an API call.
java.lang.IllegalStateException: Fragment not attached to Activity
I tried putting the code inside isAdded() method to check whether fragment is currently added to its activity but still i rarely gets this error. I fail to understand why I am still getting this error. How can i prevent it?
Its showing error on the line-
cameraInfo.setId(getResources().getString(R.string.camera_id));
Below is the sample api call that i am making.
SAPI.getInfo(getActivity(),
new APIResponseListener() {
#Override
public void onResponse(Object response) {
cameraInfo = new SInfo();
if(isAdded()) {
cameraInfo.setId(getResources().getString(R.string.camera_id));
cameraInfo.setName(getResources().getString(R.string.camera_name));
cameraInfo.setColor(getResources().getString(R.string.camera_color));
cameraInfo.setEnabled(true);
}
}
#Override
public void onError(VolleyError error) {
mProgressDialog.setVisibility(View.GONE);
if (error instanceof NoConnectionError) {
String errormsg = getResources().getString(R.string.no_internet_error_msg);
Toast.makeText(getActivity(), errormsg, Toast.LENGTH_LONG).show();
}
}
});
This error happens due to the combined effect of two factors:
The HTTP request, when complete, invokes either onResponse() or onError() (which work on the main thread) without knowing whether the Activity is still in the foreground or not. If the Activity is gone (the user navigated elsewhere), getActivity() returns null.
The Volley Response is expressed as an anonymous inner class, which implicitly holds a strong reference to the outer Activity class. This results in a classic memory leak.
To solve this problem, you should always do:
Activity activity = getActivity();
if(activity != null){
// etc ...
}
and also, use isAdded() in the onError() method as well:
#Override
public void onError(VolleyError error) {
Activity activity = getActivity();
if(activity != null && isAdded())
mProgressDialog.setVisibility(View.GONE);
if (error instanceof NoConnectionError) {
String errormsg = getResources().getString(R.string.no_internet_error_msg);
Toast.makeText(activity, errormsg, Toast.LENGTH_LONG).show();
}
}
}
Fragment lifecycle is very complex and full of bugs, try to add:
Activity activity = getActivity();
if (isAdded() && activity != null) {
...
}
I Found Very Simple Solution isAdded() method which is one of the fragment method to identify that this current fragment is attached to its Activity or not.
we can use this like everywhere in fragment class like:
if(isAdded())
{
// using this method, we can do whatever we want which will prevent **java.lang.IllegalStateException: Fragment not attached to Activity** exception.
}
Exception: java.lang.IllegalStateException: Fragment
DeadlineListFragment{ad2ef970} not attached to Activity
Category: Lifecycle
Description: When doing time-consuming operation in background thread(e.g, AsyncTask), a new Fragment has been created in the meantime, and was detached to the Activity before the background thread finished. The code in UI thread(e.g.,onPostExecute) calls upon a detached Fragment, throwing such exception.
Fix solution:
Cancel the background thread when pausing or stopping the
Fragment
Use isAdded() to check whether the fragment is attached
and then to getResources() from activity.
i may be late but may help someone .....
The best solution for this is to create a global application class instance and call it in the particular fragment where your activity is not being attached
as like below
icon = MyApplication.getInstance().getString(R.string.weather_thunder);
Here is application class
public class MyApplication extends Application {
private static MyApplication mInstance;
private RequestQueue mRequestQueue;
#Override
public void onCreate() {
super.onCreate();
mInstance = this;
}
public static synchronized MyApplication getInstance() {
return mInstance;
}
}
In Fragment use isAdded()
It will return true if the fragment is currently attached to Activity.
If you want to check inside the Activity
Fragment fragment = new MyFragment();
if(fragment.getActivity()!=null)
{ // your code here}
else{
//do something
}
Hope it will help someone
This error can happen if you are instantiating a fragment that somehow can't be instantiated:
Fragment myFragment = MyFragment.NewInstance();
public classs MyFragment extends Fragment {
public void onCreate() {
// Some error here, or anywhere inside the class is preventing it from being instantiated
}
}
In my case, i have met this when i tried to use:
private String loading = getString(R.string.loading);
So the base idea is that you are running a UI operation on a fragment that is getting in the onDetach lifecycle.
When this is happening the fragment is getting off the stack and losing the context of the Activity.
So when you call UI related functions for example calling the progress spinner and you want to leave the fragment check if the Fragment is added to the stack, like this:
if(isAdded){ progressBar.visibility=View.VISIBLE }
This will solve your problem.
Add This on your Fragemnt
Activity activity;
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
activity = context instanceof Activity ? (Activity) context : null;
}
Then change getContext() , getActivity() , requireActivity() or requireContext() with activity
I adopted the following approach for handling this issue. Created a new class which act as a wrapper for activity methods like this
public class ContextWrapper {
public static String getString(Activity activity, int resourceId, String defaultValue) {
if (activity != null) {
return activity.getString(resourceId);
} else {
return defaultValue;
}
}
//similar methods like getDrawable(), getResources() etc
}
Now wherever I need to access resources from fragments or activities, instead of directly calling the method, I use this class. In case the activity context is not null it returns the value of the asset and in case the context is null, it passes a default value (which is also specified by the caller of the function).
Important This is not a solution, this is an effective way where you can handle this crash gracefully. You would want to add some logs in cases where you are getting activity instance as null and try to fix that, if possible.
this happen when the fragment does not have a context ,thus the getActivity()method return null.
check if you use the context before you get it,or if the Activity is not exist anymore . use context in fragment.onCreate and after api response usually case this problem
Sometimes this exception is caused by a bug in the support library implementation. Recently I had to downgrade from 26.1.0 to 25.4.0 to get rid of it.
This issue occurs whenever you call a context which is unavailable or null when you call it. This can be a situation when you are calling main activity thread's context on a background thread or background thread's context on main activity thread.
For instance , I updated my shared preference string like following.
editor.putString("penname",penNameEditeText.getText().toString());
editor.commit();
finish();
And called finish() right after it. Now what it does is that as commit runs on main thread and stops any other Async commits if coming until it finishes. So its context is alive until the write is completed. Hence previous context is live , causing the error to occur.
So make sure to have your code rechecked if there is some code having this context issue.

Fragment's reference to mActivity becomes null after orientation change. Ineffective fragment state maintenance

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.

getActivity returns null in Fragment

I have a fragment which is basically a list view. The parent activity calls a method to retrieve a list of roster items from a service. When the data returns from the service I call updateRosterItems on the fragment passing through and ArrayList of Roster items. The problem is that it works the first time through, but then when I select a different tab, and then come back to the tab with the fragment, the getActivity() returns null and I can't hook up the data to the ArrayAdapter.
This is the code for the updateRosterItems function:
public void updateRosterList(ArrayList<RosterInfo> rosterItems)
{
if(_adapter == null)
{
_adapter = new RosterItemAdapter(getActivity(), R.layout.roster_listview_item, rosterItems);
}
Activity activity = getActivity();
if(activity != null)
{
ListView list = (ListView)activity.findViewById(R.id.lstRosterItems);
list.setAdapter(_adapter);
_adapter.notifyDataSetChanged();
}
}
I've read about similar issues caused by code being called before the fragment is attached. I guess my question is, is there a way to delay the call to the updateRosterList until after the onAttach is called? The solution I'm toying with is that if getActivity() returns null then store the data in private variable in the fragment, and in the onAttach method check if there is data in the varialbe and then call the update on the adapter. This seems a bit hacky though. Any ideas?
UPDATE: I've managed to get it working by doing this. I'm quite new to Android development and it seems a bit hacky to me as a solution. Is there a better way? Basically the updateRosterList function is the one that is called from outside of the fragment.
public class RosterListFragment extends Fragment {
RosterItemAdapter _adapter = null;
private ArrayList<RosterInfo> _items;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.inflate(R.layout.roster_listview, container, false);
}
#Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
if(_items != null)
{
performUpdateRosterList(_items);
}
}
public void updateRosterList(ArrayList<RosterInfo> rosterItems)
{
Activity activity = getActivity();
if(activity != null)
{
performUpdateRosterList(rosterItems);
}
else
{
_items = rosterItems;
}
}
private void performUpdateRosterList(ArrayList<RosterInfo> rosterItems)
{
Activity activity = getActivity();
if(_adapter == null)
{
_adapter = new RosterItemAdapter(activity, R.layout.roster_listview_item, rosterItems);
}
ListView list = (ListView)activity.findViewById(R.id.lstRosterItems);
list.setAdapter(_adapter);
_adapter.notifyDataSetChanged();
}
}
You are correct, the activity isn't yet attached. There's two ways to handle this.
Don't make the changes until after the activity has been attached. Perhaps just save off rosterItems, and have it updated later.
Pass in the context into your updater function.
Personally, I would say the first is probably be better path, but either one could work fine.

getActivity() returns null in Fragment function

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;
}

Categories

Resources