Related
Because All fragment loaded with together In Pager Sliding tab , I need to use setUserVisibleHint() for laoding that fragment selected .
I have Problem with Context in the setUserVisibleHint() method :
It raised NPE Exception when I use getActivity for my Context. Thanks
Because getActivity will return null before fragment attached to Activity you need check this value will null or check fragment attached to activity or after onActivityActtached function.
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getActivity() != null) {
//do something
}
}
//or
boolean isAttached = false;
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
isAttached = true;
}
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isAttached) {
//do something
}
}
Its better to use newInstance Method and put your String over there. And then in fragment use
getArguments().getString("KEY")
I discovered a strange behaviour today.
I have my activity which connects to the GoogleApiClient in onStart() and disconnects in the onStop()
The activity uses a GridViewPager to show my fragments. To send messages through the Data Layer i use a callback interface between activity and fragment.
If i call sendMessage() from a button within the Activity layout it works fine. If sendMessage() is executed by the fragment using the callback interface sendMessage() shows the "not connected" Toast.
In both ways the same method in the Activity is called so how is it possible that it behaves different ?
I should mention that the problem only occours after the application is restarted for the first time.
Activity
public class WearPlex extends WearableActivity implements
NavigationRemoteCallbacks,
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
private List<Node> nodeList = new ArrayList<Node>();
private List<Fragment> fragmentList = new ArrayList<Fragment>();
private GoogleApiClient googleApiClient;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wear_plex);
setAmbientEnabled();
fragmentList.add(NavigationRemoteFragment.getInstance(this));
GridViewPager mGridPager = (GridViewPager)findViewById(R.id.gridViewPager);
mGridPager.setAdapter(new MainGridPageAdapter(getFragmentManager(), fragmentList));
googleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
#Override
protected void onStart() {
super.onStart();
googleApiClient.connect();
}
#Override
protected void onStop() {
googleApiClient.disconnect();
super.onStop();
}
#Override
public void onConnected(Bundle bundle) {
Toast.makeText(this, "Connected", Toast.LENGTH_SHORT).show();
nodeList.clear();
Wearable.NodeApi.getConnectedNodes(googleApiClient).setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {
#Override
public void onResult(NodeApi.GetConnectedNodesResult nodes) {
for (Node node : nodes.getNodes()) nodeList.add(node);
}
});
}
#Override
public void navigationRemoteSendCommand(String commandPath) {
sendMessage(commandPath, null);
}
public void debugOnClick(View view) {
sendMessage("/debug", null);
}
public void sendMessage(String path, byte[] data) {
if (googleApiClient.isConnected()) {
for (int i = 0; i < nodeList.size(); i++) {
if (nodeList.get(i).isNearby()) {
Toast.makeText(this, "Send message", Toast.LENGTH_SHORT).show();
Wearable.MessageApi.sendMessage(googleApiClient, nodeList.get(i).getId(), path, data);
}
}
} else {
Toast.makeText(this, "Not connected", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Toast.makeText(this, "Connection failed", Toast.LENGTH_SHORT).show();
}
Fragment
public class NavigationRemoteFragment extends Fragment {
private static NavigationRemoteFragment navigationRemoteFragment = null;
private NavigationRemoteCallbacks callbackHandler = null;
private ImageButton navBtnCenter;
public static NavigationRemoteFragment getInstance(NavigationRemoteCallbacks handler) {
if (navigationRemoteFragment == null) {
navigationRemoteFragment = new NavigationRemoteFragment();
navigationRemoteFragment.callbackHandler = handler;
}
return navigationRemoteFragment;
}
public NavigationRemoteFragment() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_navigation_remote, container, false);
navBtnCenter = (ImageButton)v.findViewById(R.id.navBtnCenter);
navBtnCenter.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
callbackHandler.navigationRemoteSendCommand("/debug");
}
});
return v;
}
}
Callback interface
public interface NavigationRemoteCallbacks {
public void navigationRemoteSendCommand(String commandPath);
}
EDIT 1 code for MainGridPageAdapter
public class MainGridPageAdapter extends FragmentGridPagerAdapter {
private List<Fragment> fragmentList = null;
public MainGridPageAdapter(FragmentManager fm, List<Fragment> fragmentList) {
super(fm);
this.fragmentList = fragmentList;
}
#Override
public Fragment getFragment(int i, int i1) {
if (i1 < fragmentList.size()) return fragmentList.get(i1);
return null;
}
#Override
public int getRowCount() {
return 1;
}
#Override
public int getColumnCount(int i) {
return fragmentList.size();
}
You don't show the code for MainGridPageAdapter so I don't know how it is managing fragments. You mention that the problem occurs after a restart. Looking at the code in WearPlex.onCreate(), I suspect that the problem is caused fragments that are holding a reference to an old, destroyed instance of the activity.
A poorly documented behavior of FragmentManager is that it saves its state across restarts. This is often overlooked, resulting in duplicate fragment instances after a restart. The correct pattern for managing fragment creation in the onCreate() method of the host activity is:
if (savedInstanceState == null) {
// Not a restart
// Create a new instance of the fragment
// Add it to the fragment manager
} else {
// Restart
// The fragment manager has saved and restored the fragment instances
// Use findFragmentById() to get the fragment if you need it
}
You are not using savedInstanceState in onCreate() to test for restart. Are you seeing more fragments than you expect after restart? If so, the original fragments are holding a reference to the old activity, which was stopped, and has a disconnected GoogleApiClient. If the NavBtn of one of those fragments is clicked, you will see the "not connected" toast.
Update
The problem is caused by the way you are creating new instances of NavigationRemoteFragment, specifically the use of static member navigationRemoteFragment. After a restart, when the activity is recreated, the code calls NavigationRemoteFragment.getInstance(this). getInstance() finds navigationRemoteFragment not null because it is static, and does not create a new fragment. The fragment returned is the old one, which holds a reference to the old activity, which has been stopped and has a disconnected GoogleApiClient.
This could be confirmed by using the isDestroyed method and adding a some debug logging:
#Override
public void navigationRemoteSendCommand(String commandPath) {
if (isDestroyed()) {
Log.w("TEST", "This is an old instance of the activity");
}
sendMessage(commandPath, null);
}
How could some part of my code be aware of Fragment instance become visible on a screen?
The following snippet will explain my question.
public class MyApp extends Application {
public static final String TAG = MyApp.class.getSimpleName();
#Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
...
#Override
public void onActivityResumed(Activity activity) {
Log.d(TAG, activity.getClass().getSimpleName() + " is on screen");
}
#Override
public void onActivityStopped(Activity activity) {
Log.d(TAG, activity.getClass().getSimpleName() + " is NOT on screen");
}
...
});
}
Here i can track when any activity within my app appears on the screen. Is there any way to extend this approach on Fragments?
Something like
Activity.getFragmentManager().registerFragmentLifecycleCallbacks();
UPD. I know nothing about activities implementations, do they use fragments at all and how do they use them (injection via xml, ViewPager etc.) The only thing I have within my class is an application context. Let's assume Activity and Fragment implementations are black boxes and i am not able to make any changes.
In your fragment, override onHiddenChanged(...) method:
#Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden) {
Log.d(TAG, ((Object) this).getClass().getSimpleName() + " is NOT on screen");
}
else
{
Log.d(TAG, ((Object) this).getClass().getSimpleName() + " is on screen");
}
}
Hope this work for you!
Without touching the Activity or Fragment code and assuming you don't know the tag or layout it is placed in, there is very little that you can do. The best that I can see is that you could get the FragmentManager in ActivityResumed and ActivityStopped callbacks (because here you have an Activity reference) and apply a BackstackChangedListener. This assumes that you use the backstack when changing between fragments.
The issue with what you are asking is that you want lifecycle callbacks for Fragments on the Application level when you have no control over the middle men, the Activities which are already starved for Fragment callbacks. They do most everything through their FragmentManager, and propagate their own lifecycle callbacks down to the Fragments so that the fragments will behave appropriately. The onResume and onPause callbacks in fragments only occur when they are first created or when the Activity experiences those callbacks. There is only one lifecycle callback for Fragments in Activities, onAttachFragment, which if you could override, would give you references to the Fragments that are attached to the Activity. But you said you can't change the Activity or the Fragment, and you want to know when the Fragments are shown.
So if you don't use the backstack, I don't think there's a way to do what you want.
For putting Fragments inside Activity i use SlidingTabLayout which Google uses. Inside it you have ViewPager and some Adapter to populate many Fragments. First of all you have to put this and this files in your project. Then here there is good tutorial for how you can implement SlidingTabLayout.
1) After you have implemented SlidingTabLayout in your Activity, you can detect when and which Fragment becomes visible from Activity:
mSlidingTabLayout.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
//Do nothing
}
#Override
public void onPageSelected(int position) {
if (position == 0) {
//Whenever first fragment is visible, do something
} else if (position == 1) {
//Whenever second fragment is visible, do something
} else if (position == 2) {
//Whenever third fragment is visible, do something
} else if (position == 3) {
//Whenever fourth fragment is visible, do something
}
}
#Override
public void onPageScrollStateChanged(int state) {
//Do nothing
}
});
2) You can detect if Fragment is visible from Fragment itself as i answered here, however this may get called before onCreateView() of Fragment, so check answer in the link:
#Override
public void setUserVisibleHint(boolean visible){
super.setUserVisibleHint(visible);
if (visible){
//when this Fragment is active, do something
}
}
3) You can change also change colors of indicators of each Tab like this from Activity:
mSlidingTabLayout.setCustomTabColorizer(new SlidingTabLayout.TabColorizer() {
#Override
public int getIndicatorColor(int position) {
if (position == 0) {
return getResources().getColor(R.color.orange);
} else if (position == 1) {
return getResources().getColor(R.color.redDimmed);
} else if (position == 2) {
return getResources().getColor(R.color.yellow);
} else if (position == 3) {
return getResources().getColor(R.color.green);
} else {
return getResources().getColor(R.color.redLight);
}
}
#Override
public int getDividerColor(int position) {
return getResources().getColor(R.color.defaultActionBarBg);
}
});
Use same way as activity
set flag in application class to check visiblity of fragment, use below code in fragment
#Override
public void onStart() {
super.onStart();
Log.e( "Fragment is visible", "Fragment is visible");
Application Class.isFragmentShow = true;
}
#Override
public void onPause() {
super.onPause();
Log.e("Fragment is not visible", "Fragment is not visible");
Application Class.isFragmentShow = false;
}
to communicate with fragment you have to call that activity in which fragment added then use below code
MainFragment fragment = (MainFragment) fragmentManager.findFragmentByTag("MainFragment");
fragment.setFilter();
Don't exist a default way to do, but you can make your own Callbacks, I made this and works fine, first need have a BaseFragment class where we'll handle all fragment events.
public class BaseFragment extends Fragment {
private String fragmentName;
private FragmentLifecycleCallbacks listener;
public void registerCallBacks(String fragmentName){
// handle the listener that implement 'MyApp' class
try{
listener = (FragmentLifecycleCallbacks) getActivity().getApplication();
} catch (ClassCastException e) {
throw new ClassCastException("Application class must implement FragmentLifecycleCallbacks");
}
// set the current fragment Name for the log
this.fragmentName = fragmentName;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if(listener!=null) {
listener.onAttachFragment(fragmentName);
}
}
#Override
public void onResume() {
super.onResume();
if(listener!=null) {
listener.onResumeFragment(fragmentName);
}
}
#Override
public void onStop() {
super.onStop();
if(listener!=null) {
listener.onStopFragment(fragmentName);
}
}
// 'MyApp' class needs implement this interface to handle all the fragments events
public interface FragmentLifecycleCallbacks{
void onStopFragment(String fragmentName);
void onResumeFragment(String fragmentName);
void onAttachFragment(String fragmentName);
}}
On 'MyApp' class implement the interface of BaseFragment
public class MyApp extends Application implements BaseFragment.FragmentLifecycleCallbacks{
public static final String TAG = MyApp.class.getSimpleName();
#Override
public void onCreate() {
super.onCreate();
}
#Override
public void onStopFragment(String fragmentName) {
Log.d(TAG, fragmentName + " is NOT on screen");
}
#Override
public void onResumeFragment(String fragmentName) {
Log.d(TAG, fragmentName + " is on screen");
}
#Override
public void onAttachFragment(String fragmentName) {
Log.d(TAG, fragmentName + " is attached to screen");
}}
And now each Fragment that you have need extends 'BaseFragment' and register to the global listener
public class FragmentA extends BaseFragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_simple, container, false);
// here register to the global listener
registerCallBacks(FragmentA.class.getName());
return rootView;
}}
Hope this helps!
Intercept onWindowFocusChanged() in the activity and propagate that to the interested fragment.
Try this
private Boolean isFragmentVisible()
{
if(getFragmentManager().findFragmentByTag("TAG") != null && getFragmentManager().findFragmentByTag("TAG").isVisible())
{
//The fragment is visible
return true;
}
return false;
}
Alternative way
private Boolean isFragmentVisible()
{
return getFragmentManager().findFragmentByTag("TAG") != null && getFragmentManager().findFragmentByTag("TAG").isVisible();
}
You can know the following with the built in method called "onActivityCreated(Bundle)" this method tells that the fragment has been created thus you get to know that the fragment appears on the screen Click here for reference
Hope it helps
I've looked through what's available without using a base Fragment or Activity class but couldn't find any. I've made an implementation that provides basic (onAdded / onRemoved) functionality for all fragments in your application. It is certainly possible to extend it to report the current state of the fragment (onAttach, onResume, onPause, onDetach, ...).
You can find the code along with a sample here: https://github.com/Nillerr/FragmentLifecycleCallbacks
It works both for non-support library Fragments and support library Fragments through different implementations. The support library class is safer to use and should perform better, because the non-support one uses Reflection to access the fragments, while the support library FragmentManager includes a getFragments() method.
If you are setting a Fragment to your View, you probably have a container where it will be shown. Given that this container is, say, a FrameLayout with id R.id.container, you can do that:
Fragment f = fragmentManager.findFragmentById(R.id.container);
if (f instanceof YourFragment) {
// TODO something when YourFragment is ready
}
Does this interface provide anything helpful to you?
https://github.com/soarcn/AndroidLifecyle/blob/master/lifecycle/src/main/java/com/cocosw/lifecycle/FragmentLifecycleCallbacks.java
It sounds like your best bet if you can't override the Fragment's own onResume() method is to create your own interface that extends ActivityLifecycleCallbacks, then put your logging code in the onFragmentResumed(Fragment yourFragment) method.
You can get a pointer to the Fragment by doing something like this:
int yourFragmentId = 0; //assign your fragment's ID to this variable; Fragment yourFragment.getId();
FragmentManager fm = activity.getFragmentManager();
Fragment f = fm.findFragmentById(yourFragmentId);
whereever u want to check if fragment is visible or not.. just check isMenuVisible() value.
this is fragment's method which i used to check visible fragment when i have to fire some http request from viewpager selected Item.
hope this helps.
in my case i was using this method in onActivityCreated().
In you fragment override method setMenuVisibility If you are using ViewPager and are swiping from left and right, this method is called when the visivility of the fragment gets changed.
Here is a sample from my project
public abstract class DemosCommonFragment extends Fragment {
protected boolean isVisible;
public DemosCommonFragment() {
}
#Override
public void setMenuVisibility(boolean menuVisible) {
super.setMenuVisibility(menuVisible);
isVisible = menuVisible;
// !!! Do Something Here !!!
}
}
Animation listener
I have NOT checked all use cases and there is an unhandled exception. You can play around with it to fit your use case. Please feel free to comment your opinions or use cases it did not solve.
NOTE: You can add fragmentWillDisappear and fragmentDidDisappear by handling for enter in onCreateAnimation.
Parent Fragment:
public class BaseFragment extends Fragment {
private Animation.AnimationListener animationListener;
private void setAnimationListener(Animation.AnimationListener animationListener) {
this.animationListener = animationListener;
}
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
fragmentWillAppear(animation);
}
#Override
public void onAnimationEnd(Animation animation) {
fragmentDidAppear(animation);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
}
#Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
AnimationSet animSet = new AnimationSet(true);
Animation anim = null;
try {
anim = AnimationUtils.loadAnimation(getActivity(), nextAnim);
} catch (Exception error) {
}
if (anim != null) {
anim.setAnimationListener(animationListener);
animSet.addAnimation(anim);
}
return animSet;
}
public void fragmentDidAppear(Animation animation) {
}
public void fragmentWillAppear(Animation animation) {
}
}
Child Fragment:
class ChildFragment extends BaseFragment {
#Override
public void fragmentDidAppear(Animation animation) {
super.fragmentDidAppear(animation);
}
#Override
public void fragmentWillAppear(Animation animation) {
super.fragmentWillAppear(animation);
}
}
I have an activity with two fragments.
Fragment A does a complex operation during onResume(), and Fragment B needs fragment A to be finished the complex operation, or else user interaction with Fragment B will cause a crash.
I want to put a progressbar spinning object in this activity until Fragment A is complete, and then reveal the layout with Fragment A and Fragment B side by side.
But I am unsure how to expose the completion of Fragment A's onResume to the activity.
In Fragment A I do have a fragmentlistener set up
public void onAttach(final Activity activity) {
super.onAttach(activity);
try {
this.mListener = (TopicListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.getClass().getName()
+ " must implement TopicListener");
}
}
But now what, thanks.
As suggested on Android Developer here, I would suggest you to not couple the fragments and use a callback through your activity class.
do this:
public class MyFragmentOne extends Fragment{
#Override
onResume()
{
//Do the complex task
((MyActivity)getActivity()).fragmentTaskCompleted();
}
}
public class MyActivity extends Activity
{
public void fragmentTaskCompleted()
{
//Show second fragment
}
}
Use your callback to send you to the activity.
Example of using callbacks with a map fragment
private MapListeners mLocation;
public interface MapListeners{
public void onMarkerClick(Marker m);
public void onLocationChanged(Location location);
}
#Override
public void onAttach(Activity activity){
super.onAttach(activity);
try{
mLocation = (MapListeners)activity;
}catch(ClassCastException e){
throw new ClassCastException(activity.toString() + " must implement OnMarkerClick and OnLocationChanged");
}
}
public boolean onMarkerClick(Marker marker) {
//marker is clicked so send a call to the activity like this
mLocation.onMarkerClick(marker);
return true;
}
In the activity make sure you implement the interface, in this case it would be MapListeners
then when a marker was clicked the onMarkerClick method you created gets called
public void onMarkerClick(Marker m) {
//do something
}
In developer console error reports sometimes I see reports with NPE issue. I do not understand what is wrong with my code. On emulator and my device application works good without forcecloses, however some users get NullPointerException in fragment class when the getActivity() method is called.
Activity
pulic class MyActivity extends FragmentActivity{
private ViewPager pager;
private TitlePageIndicator indicator;
private TabsAdapter adapter;
#Override
public void onCreate(Bundle savedInstanceState) {
pager = (ViewPager) findViewById(R.id.pager);
indicator = (TitlePageIndicator) findViewById(R.id.indicator);
adapter = new TabsAdapter(getSupportFragmentManager(), false);
adapter.addFragment(new FirstFragment());
adapter.addFragment(new SecondFragment());
indicator.notifyDataSetChanged();
adapter.notifyDataSetChanged();
// push first task
FirstTask firstTask = new FirstTask(MyActivity.this);
// set first fragment as listener
firstTask.setTaskListener((TaskListener) adapter.getItem(0));
firstTask.execute();
}
indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
Fragment currentFragment = adapter.getItem(position);
((Taskable) currentFragment).executeTask();
}
#Override
public void onPageScrolled(int i, float v, int i1) {}
#Override
public void onPageScrollStateChanged(int i) {}
});
}
AsyncTask class
public class FirstTask extends AsyncTask{
private TaskListener taskListener;
...
#Override
protected void onPostExecute(T result) {
...
taskListener.onTaskComplete(result);
}
}
Fragment class
public class FirstFragment extends Fragment immplements Taskable, TaskListener{
public FirstFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.first_view, container, false);
}
#Override
public void executeTask() {
FirstTask firstTask = new FirstTask(MyActivity.this);
firstTask.setTaskListener(this);
firstTask.execute();
}
#Override
public void onTaskComplete(T result) {
// NPE is here
Resources res = getActivity().getResources();
...
}
}
Maybe this error happens when applications resumed from background. In this case how I should handle this situation properly?
It seems that I found a solution to my problem.
Very good explanations are given here and here.
Here is my example:
pulic class MyActivity extends FragmentActivity{
private ViewPager pager;
private TitlePageIndicator indicator;
private TabsAdapter adapter;
private Bundle savedInstanceState;
#Override
public void onCreate(Bundle savedInstanceState) {
....
this.savedInstanceState = savedInstanceState;
pager = (ViewPager) findViewById(R.id.pager);;
indicator = (TitlePageIndicator) findViewById(R.id.indicator);
adapter = new TabsAdapter(getSupportFragmentManager(), false);
if (savedInstanceState == null){
adapter.addFragment(new FirstFragment());
adapter.addFragment(new SecondFragment());
}else{
Integer count = savedInstanceState.getInt("tabsCount");
String[] titles = savedInstanceState.getStringArray("titles");
for (int i = 0; i < count; i++){
adapter.addFragment(getFragment(i), titles[i]);
}
}
indicator.notifyDataSetChanged();
adapter.notifyDataSetChanged();
// push first task
FirstTask firstTask = new FirstTask(MyActivity.this);
// set first fragment as listener
firstTask.setTaskListener((TaskListener) getFragment(0));
firstTask.execute();
}
private Fragment getFragment(int position){
return savedInstanceState == null ? adapter.getItem(position) : getSupportFragmentManager().findFragmentByTag(getFragmentTag(position));
}
private String getFragmentTag(int position) {
return "android:switcher:" + R.id.pager + ":" + position;
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("tabsCount", adapter.getCount());
outState.putStringArray("titles", adapter.getTitles().toArray(new String[0]));
}
indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
Fragment currentFragment = adapter.getItem(position);
((Taskable) currentFragment).executeTask();
}
#Override
public void onPageScrolled(int i, float v, int i1) {}
#Override
public void onPageScrollStateChanged(int i) {}
});
The main idea in this code is that, while running your application normally, you create new fragments and pass them to the adapter. When you are resuming your application fragment manager already has this fragment's instance and you need to get it from fragment manager and pass it to the adapter.
UPDATE
Also, it is a good practice when using fragments to check isAdded before getActivity() is called. This helps avoid a null pointer exception when the fragment is detached from the activity. For example, an activity could contain a fragment that pushes an async task. When the task is finished, the onTaskComplete listener is called.
#Override
public void onTaskComplete(List<Feed> result) {
progress.setVisibility(View.GONE);
progress.setIndeterminate(false);
list.setVisibility(View.VISIBLE);
if (isAdded()) {
adapter = new FeedAdapter(getActivity(), R.layout.feed_item, result);
list.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
}
If we open the fragment, push a task, and then quickly press back to return to a previous activity, when the task is finished, it will try to access the activity in onPostExecute() by calling the getActivity() method. If the activity is already detached and this check is not there:
if (isAdded())
then the application crashes.
Ok, I know that this question is actually solved but I decided to share my solution for this. I've created abstract parent class for my Fragment:
public abstract class ABaseFragment extends Fragment{
protected IActivityEnabledListener aeListener;
protected interface IActivityEnabledListener{
void onActivityEnabled(FragmentActivity activity);
}
protected void getAvailableActivity(IActivityEnabledListener listener){
if (getActivity() == null){
aeListener = listener;
} else {
listener.onActivityEnabled(getActivity());
}
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (aeListener != null){
aeListener.onActivityEnabled((FragmentActivity) activity);
aeListener = null;
}
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (aeListener != null){
aeListener.onActivityEnabled((FragmentActivity) context);
aeListener = null;
}
}
}
As you can see, I've added a listener so, whenever I'll need to get Fragments Activity instead of standard getActivity(), I'll need to call
getAvailableActivity(new IActivityEnabledListener() {
#Override
public void onActivityEnabled(FragmentActivity activity) {
// Do manipulations with your activity
}
});
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(context);
mContext = context;
}
#Override
public void onDetach() {
super.onDetach();
mContext = null;
}
Edited, since onAttach(Activity) is depreciated & now onAttach(Context) is being used
Don't call methods within the Fragment that require getActivity() until onStart in the parent Activity.
private MyFragment myFragment;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
myFragment = new MyFragment();
ft.add(android.R.id.content, youtubeListFragment).commit();
//Other init calls
//...
}
#Override
public void onStart()
{
super.onStart();
//Call your Fragment functions that uses getActivity()
myFragment.onPageSelected();
}
I've been battling this kind of problem for a while, and I think I've come up with a reliable solution.
It's pretty difficult to know for sure that this.getActivity() isn't going to return null for a Fragment, especially if you're dealing with any kind of network behaviour which gives your code ample time to withdraw Activity references.
In the solution below, I declare a small management class called the ActivityBuffer. Essentially, this class deals with maintaining a reliable reference to an owning Activity, and promising to execute Runnables within a valid Activity context whenever there's a valid reference available. The Runnables are scheduled for execution on the UI Thread immediately if the Context is available, otherwise execution is deferred until that Context is ready.
/** A class which maintains a list of transactions to occur when Context becomes available. */
public final class ActivityBuffer {
/** A class which defines operations to execute once there's an available Context. */
public interface IRunnable {
/** Executes when there's an available Context. Ideally, will it operate immediately. */
void run(final Activity pActivity);
}
/* Member Variables. */
private Activity mActivity;
private final List<IRunnable> mRunnables;
/** Constructor. */
public ActivityBuffer() {
// Initialize Member Variables.
this.mActivity = null;
this.mRunnables = new ArrayList<IRunnable>();
}
/** Executes the Runnable if there's an available Context. Otherwise, defers execution until it becomes available. */
public final void safely(final IRunnable pRunnable) {
// Synchronize along the current instance.
synchronized(this) {
// Do we have a context available?
if(this.isContextAvailable()) {
// Fetch the Activity.
final Activity lActivity = this.getActivity();
// Execute the Runnable along the Activity.
lActivity.runOnUiThread(new Runnable() { #Override public final void run() { pRunnable.run(lActivity); } });
}
else {
// Buffer the Runnable so that it's ready to receive a valid reference.
this.getRunnables().add(pRunnable);
}
}
}
/** Called to inform the ActivityBuffer that there's an available Activity reference. */
public final void onContextGained(final Activity pActivity) {
// Synchronize along ourself.
synchronized(this) {
// Update the Activity reference.
this.setActivity(pActivity);
// Are there any Runnables awaiting execution?
if(!this.getRunnables().isEmpty()) {
// Iterate the Runnables.
for(final IRunnable lRunnable : this.getRunnables()) {
// Execute the Runnable on the UI Thread.
pActivity.runOnUiThread(new Runnable() { #Override public final void run() {
// Execute the Runnable.
lRunnable.run(pActivity);
} });
}
// Empty the Runnables.
this.getRunnables().clear();
}
}
}
/** Called to inform the ActivityBuffer that the Context has been lost. */
public final void onContextLost() {
// Synchronize along ourself.
synchronized(this) {
// Remove the Context reference.
this.setActivity(null);
}
}
/** Defines whether there's a safe Context available for the ActivityBuffer. */
public final boolean isContextAvailable() {
// Synchronize upon ourself.
synchronized(this) {
// Return the state of the Activity reference.
return (this.getActivity() != null);
}
}
/* Getters and Setters. */
private final void setActivity(final Activity pActivity) {
this.mActivity = pActivity;
}
private final Activity getActivity() {
return this.mActivity;
}
private final List<IRunnable> getRunnables() {
return this.mRunnables;
}
}
In terms of its implementation, we must take care to apply the life cycle methods to coincide with the behaviour described above by Pawan M:
public class BaseFragment extends Fragment {
/* Member Variables. */
private ActivityBuffer mActivityBuffer;
public BaseFragment() {
// Implement the Parent.
super();
// Allocate the ActivityBuffer.
this.mActivityBuffer = new ActivityBuffer();
}
#Override
public final void onAttach(final Context pContext) {
// Handle as usual.
super.onAttach(pContext);
// Is the Context an Activity?
if(pContext instanceof Activity) {
// Cast Accordingly.
final Activity lActivity = (Activity)pContext;
// Inform the ActivityBuffer.
this.getActivityBuffer().onContextGained(lActivity);
}
}
#Deprecated #Override
public final void onAttach(final Activity pActivity) {
// Handle as usual.
super.onAttach(pActivity);
// Inform the ActivityBuffer.
this.getActivityBuffer().onContextGained(pActivity);
}
#Override
public final void onDetach() {
// Handle as usual.
super.onDetach();
// Inform the ActivityBuffer.
this.getActivityBuffer().onContextLost();
}
/* Getters. */
public final ActivityBuffer getActivityBuffer() {
return this.mActivityBuffer;
}
}
Finally, in any areas within your Fragment that extends BaseFragment that you're untrustworthy about a call to getActivity(), simply make a call to this.getActivityBuffer().safely(...) and declare an ActivityBuffer.IRunnable for the task!
The contents of your void run(final Activity pActivity) are then guaranteed to execute along the UI Thread.
The ActivityBuffer can then be used as follows:
this.getActivityBuffer().safely(
new ActivityBuffer.IRunnable() {
#Override public final void run(final Activity pActivity) {
// Do something with guaranteed Context.
}
}
);
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// run the code making use of getActivity() from here
}
I know this is a old question but i think i must provide my answer to it because my problem was not solved by others.
first of all : i was dynamically adding fragments using fragmentTransactions.
Second: my fragments were modified using AsyncTasks (DB queries on a server).
Third: my fragment was not instantiated at activity start
Fourth: i used a custom fragment instantiation "create or load it" in order to get the fragment variable.
Fourth: activity was recreated because of orientation change
The problem was that i wanted to "remove" the fragment because of the query answer, but the fragment was incorrectly created just before. I don't know why, probably because of the "commit" be done later, the fragment was not added yet when it was time to remove it. Therefore getActivity() was returning null.
Solution :
1)I had to check that i was correctly trying to find the first instance of the fragment before creating a new one
2)I had to put serRetainInstance(true) on that fragment in order to keep it through orientation change (no backstack needed therefore no problem)
3)Instead of "recreating or getting old fragment" just before "remove it", I directly put the fragment at activity start.
Instantiating it at activity start instead of "loading" (or instantiating) the fragment variable before removing it prevented getActivity problems.
In Kotlin you can try this way to handle getActivity() null condition.
activity?.let { // activity == getActivity() in java
//your code here
}
It will check activity is null or not and if not null then execute inner code.