We all know that when using ViewPager with Fragment and FragmentPagerAdapter we get 3 Fragment loaded: the visible one, and both on each of its sides.
So, if I have 7 Fragments and I'm iterating through them to see which 3 of them are the ones that are loaded, and by that I mean onCreateView() has already been called, how can I determine this?
EDIT: The Fragment doesn't have to be the one that the ViewPager is showing, just that onCreateView() has already been called.
Well logically, this would be a reasonable test if onCreateView has been called:
myFragment.getView() != null;
Assuming you a have a reference to all of the fragments in the pager iterate, them and check if they have a view.
http://developer.android.com/reference/android/app/Fragment.html#getView()
Update
The above answer assumes that your fragments always create a view, and are not viewless fragments. If they are then I suggest sub classing the fragment like so:
public abstract class SubFragment extends Fragment
{
protected boolean onCreateViewCalled = false;
public boolean hasOnCreateViewBeenCalled()
{
return onCreateViewCalled;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup Container, Bundle state){
onCreateViewCalled = true;
return null;
}
}
Just bear in mind that further sub classes will have to call super or set the flag themselves should they override onCreateView as well.
I added an interface to Fragment. Looks like:
protected OnCreateViewCallback createViewCallback = null;
public void setCreateViewCallback(OnCreateViewCallback createViewCallback) {
this.createViewCallback = createViewCallback;
}
public interface OnCreateViewCallback {
void onCreateView();
}
In my onCreateView():
//initialize your view.
if (createViewCallback != null) {
createViewCallback.onCreateView();
createViewCallback = null;
}
return mainView;
From my activity:
if (ocrFragment.getView() == null) {
ocrFragment.setCreateViewCallback(new MainScreenFragment.OnCreateViewCallback() {
#Override
public void onCreateView() {
ocrFragment.ocrImage(picture, false);
}
});
} else {
ocrFragment.ocrImage(picture, false);
}
If you are trying to perform something after onCreateView is called, use onViewCreated:
Called immediately after onCreateView(LayoutInflater, ViewGroup,
Bundle) has returned, but before any saved state has been restored in
to the view. This gives subclasses a chance to initialize themselves
once they know their view hierarchy has been completely created. The
fragment's view hierarchy is not however attached to its parent at
this point.
public void onViewCreated(View view, Bundle savedInstanceState) {
MyActivity myActivity = (MyActivity) getActivity();
MyActivity.newAsyncTask(mPar);
}
You could also check for Fragment.isVisible() because a Fragment is in visible state when it's in the offscreen page limit of a ViewPager.
Edit: But it just really depends on what you really want to achieve with your question. Perhaps some kind of update to all UIs in your Fragments when their UI is ready?
EDIT:
Just another addition, you could listen to onViewCreated() and set a flag. Or notify your Activity and do further work (getActivity() will return your Activity at this point). But really, better state what you want to accomplish with your question.
Related
I am using this method but it is not working for first fragment but on swiping from second to first fragment it working fine. please help me in this.
thanks
#Override
public void setUserVisibleHint(boolean isVisibleToUser)
{
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser){ //do Something
}
}
This is how it works
View view;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
//inflate view layout
view =inflater.inflate(R.layout.your_fragment, container, false);
// return view
return view;
}
and use this
#Override
public void setUserVisibleHint(boolean isUserVisible)
{
super.setUserVisibleHint(isUserVisible);
// when fragment visible to user and view is not null then enter here.
if (isUserVisible && view != null)
{
onResume();
}
}
and inside onResume put this code
#Override
public void onResume() {
super.onResume();
if (!getUserVisibleHint()) {
return;
}
//do your stuff here
}
You really shouldn't rely on the order in which setUserVisibleHint is called when using the support version. From the docs:
Note: This method may be called outside of the fragment lifecycle. and thus has no ordering guarantees with regard to fragment lifecycle method calls.
A similar question has some approaches on this.
I faced this issue when I was loading data on viewPager Fragments as and when they were visible.To load data only when a particular fragment was visible , I relied on
setUserVisibleHint(isVisibleToUser:Boolean) without realising at what point in fragment lifecycle it was being called.
As a result, I was all clueless about a day and two, why the hell where all my variables(present in onCreateView()) were null.Only After going through some stack Answers I realised the mistake I was committing.
setUserVisibleHint() was called even before onCreateView() was called.
So the work around is this . See the highest voted answer here.The guy managed it with Booleans.
Hope it helps all the future visitors and save their time.
In my project, I want to set visibility of fragments buttons from MainActivity. But the problem is, it gives NullPointerException(). I also maked listBtn & gridBtn as static. I used below code :
FirstFragment fragment = (FirstFragment)getSupportFragmentManager().findFragmentById(R.id. <frameLayout Id>);
main_page_fragment.listBtn.setVisibility(View.GONE);
main_page_fragment.gridBtn.setVisibility(View.GONE);
You cannot access to your fragment view from Activity class because activity uses its own view (ex: R.layout.activity_main). Rather you can set visibility in your corresponding fragment class which will do the same job.
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.details, container, false);
Button listBtn = (Button)view.findviewById(R.id.listBrn);
Button gridBtn = (Button)view.findviewById(R.id.gridBrn);
listBtn.setVisibility(View.GONE);
gridBtn.setVisibility(View.GONE);
return view;
}
Fragment onCreateView callback is called after onCreate method of activity, so i think you have tried to get access from it. That views will be accessible only after onResumeFragments callback is called, you should perform your actions with fragments there.
Another tip is that you strongly should not call views of fragments directly like you did or via static reference to views that's the worst. You should avoid such dependencies on fragments inner implementation. Instead of it, better is create some method like setInitialState (the name depends on your business logic) and just call it from activity.
So result code:
In activity:
private FirstFragment fragment;
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//init fragment here
}
#Override
protected void onResumeFragments() {
super.onResumeFragments();
fragment.setInitialState();
}
In fragment:
//this will be called on fragment #onResume step, so views will be ready here.
public void setInitialState() {
listBtn.setVisibility(View.GONE);
gridBtn.setVisibility(View.GONE);
}
If you add your fragments dynamically from MainActivity like so:
YourFragment fragment = new YourFragment();
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.fragmentContainer, fragment, YOUR_TAG)
.commit();
Then you can define method in your fragment like so:
public void hideButtons()
{
yourBtn.setVisibility(View.GONE);
}
And call it from activity:
fragment.hideButtons();
I struggle with this for several hours and I found a much simpler solution.
Inside the fragment, simply make a public function (outside the on create view method) with the behavior that you want.
fun hideElement() {
binding.button.visibility = View.GONE
}
And then in main activity access to the fragment and call the function.
binding.bottomNavigation.setOnNavigationItemSelectedListener {
when (it.itemId){
R.id.someFragment -> someFragment.hideElement()
}
}
I use a FragmentPagerAdapter to switch from fragments. I need some functions to be called when a fragmentswitch is made and had some troubles with OnPause and OnResume, so as suggested by THIS question I have implemented an interface OnPageSelectListener :
public interface OnPageSelectListener {
void onPageSelected();
void onPageNotVisible();
}
It calls the function OnPageSelected whenever this page comes to the foreground. Works nice, except that I want to call a function on my adapter. I thought that would work, except that my adapter returns NULL all the times (even though it is initialized and data is loaded in my listview as prefered).
public class AfterCheckFragment extends Fragment implements OnPageSelectListener{
private ListView listView;
private List<Check> checkList;
private CheckListAdapter adapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_check, container, false);
System.out.println("VIEW create called");
//(.. some other stuff, not relevant for question..)
//initializing the adapter
listView = (ListView) view.findViewById(R.id.listView);
adapter = new CheckListAdapter(checkList,getActivity(),trainPosition);
listView.setAdapter(adapter);
adapter.handleButtonVisibility();
return view;
}
#Override
public void onPageSelected() {
if(this.adapter != null) {
System.out.println("adapter not null");
this.adapter.checkForActive();
}else{
System.out.println("Adapter is NULL");
}
}
#Override
public void onPageNotVisible() { //page is moved to backgroung
System.out.println("AFTER not active any more ");
}
}
Now is my question: Why does adapter (or any other object in the fragment) return null when I return to my fragment? When the fragmentPager is initialized the onActivityCreate function of the fragment is called one time, but after that not any more, and the adapter return null....
you have to call the onPageSelected() after initialization of the adapter and setAdapter() otherwise adapter will return null always
Here is why I think your CheckListAdapter (i'll call it listAdapter) is null:
You give the pagerAdapter to the ViewPager
The ViewPager asks the pagerAdapter for a new Fragment
The ViewPager tells the FragmentManager to use it
onPageSelected gets called
You try and use listAdapter. It hasn't been initialized yet at this point. (NPE)
The FragmentManager drags the Fragment through all its stages.
onCreateView gets called. Your listAdapter is created.
Don't try and use internal data of a fragment outside of it. It is meant to work as a standalone unit, it won't be very good if you use it differently. Since the fragment is initialized at a later stage, you can't use it like you intend.
You can try and do what you want to do in the fragment, rather than the pagerAdapter, or write a method in the hosting Activity and call it from the fragment when ready, or even launch an event.
ViewPager will create and destroy fragments as the user changes pages (see ViewPager.setOffscreenPageLimit()). So onActivityCreated() is only called on the fragment when it is being restored or set up for the first time. Hence, fragments can be created without ever having onActivityCreated() called.
Instead of onActivityCreated(), I would recommend overriding onViewCreated() and setting up your adapter there. No fragment can be displayed without having a view created, so this is a good place to do that kind of stuff.
If you have your OnPageSelectListener logic working, that's good. I found the best way to know when your fragment is actually in front of the user is by overriding setPrimaryItem() in the FragmentPagerAdapter. Getting the page out of view event is a little trickier, since you have to keep a reference to the fragment from the previous setPrimaryItem() call.
This is because Viewpager calls OnpageSelected way before Fragments in oncreateView()/onActivityCreated() is called .
The best way for you is to inflate your views in the constructor of the Fragment and set the Adapters.
Or
Use a member variable to store whether the Fragment is active or not. And use the variable in oncreateview() to call function on your adapter.
Why don't you use a viewpager.addOnPageChangeListener, in you pager , after setting its adapter and the setOffscreenPageLimit() instead of implements it on your fragment?
Heres a sample code:
viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
if(position == 1){ // if you want the second page, for example
//Your code here
}
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
Make it in your Activity, where you setup your ViewPager, of course.
for me i had to call this on my viewpager:
myViewPager.setSaveFromParentEnabled(false);
I have added a horizontal scroll view and in the layout I have added reference to two fragments such that both the fragments are visible in the scroll.
the problem is when I enter values in the left layout I want the value to be displayed in the right layout.
I am using an interface for the same, but its not working. as in I keep getting null pointer exception for the same.. How to get around this??
this is the main class fragment
public static SliderRightAdapter sliderRightAdapterFragment; <-- this calls the second layout
#Override
public View onCreateView(LayoutInflater inflater,
#Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
// TODO Auto-generated method stub
View view = inflater.inflate(R.layout.f_pp, container, false);
sliderRightAdapterFragment = (SliderRightAdapter) getActivity()
.getSupportFragmentManager().findFragmentById(
R.id.secondary_sliderProduct_description);
return view;
}
.
.
.
.
this is where i am getting null pointer exception on sliderRightAdapterfragment
interfaceObject.onButtonClick(CGS, sliderRightAdapterFragment);
and this is where it is initialized...
sliderRightAdapterFragment = PDFinterface.sliderRightAdapterFragment;
From what I'm seeing, you're letting the fragments communicate directly with each other.
I'd recommend against it, since this can quickly lead to maintaining unused references, as fragments can have quite diverse lifecycles. The idea behind fragments is to have small segments of functionality which can communicate with their parent some information.
I think a better way to approach would be to use the parent activity as a form of 'manager' between these two fragments.
Instead of maintaining a static reference to the other fragment, store a normal reference to the activity, preferably in the form of a listener interface the activity implements. Then you'd use the findFragmentById/Tag to notify the other fragment of the change. All in all, this would look something like this:
Fragment:
public interface ActivityListener {
void onText(String text);
}
private ActivityListener listener;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if(activity instanceof ActivityListener) {
listener = (ActivityListener) activity;
}
// else some error
}
#Override
public void onDetach(Activity activity) {
// to make absolutely sure you don't maintain a reference to a killed activity
listener = null;
}
Activity:
#Override
public void onText(String text) {
Fragment frag = getSupportFragmentManager.findFragmentById(<someId>);
// notify with text information
}
Something like this should work fine.
We have a web service which serves up an XML file via a HTTP Post.
I am downloading and parsing this xml file into an object to populate some views inside a couple of fragments held in a FragmentPagerAdapter. I get this XML file via an AsyncTask and it tells my fragments the process has finished via a listener interface.
From there, I populate the view inside the fragment with data returned from the web service. This is all fine until the orientation changes. From what I understand, the ViewPager's adapter is supposed to retain the fragments it's created, which is fine, and which I want to happen, and I know the fragment's onCreateView method is still called to return the view. I've spent the last day or so hunting through posts here and the Google docs etc and I can't find a concrete method that lets me do what I want to do: retain the fragment, and it's already populated view so that I can simply restore it when the orientation changes and avoid unneccesary calls to the web service.
Some code snippets:
In the main activities onCreate:
mViewPager = (ViewPager) findViewById(R.id.viewpager);
if (mViewPager != null) {
mViewPager.setAdapter(new PagerAdapter(getSupportFragmentManager()));
}
if (savedInstanceState == null) {
if (CheckCredentials()) {
Refresh(0,0);
} else {
ShowCredentialsDialog(false);
}
}
Refresh method in main activity...
public void Refresh(Integer month, Integer year) {
if (mUpdater == null) {
mUpdater = new UsageUpdater(this);
// mUpdater.setDataListener(this);
}
if (isConnected()) {
mUpdater.Refresh(month, year);
usingCache = false;
mProgress.show();
} else {
mUpdater.RefreshFromCache();
usingCache = true;
}
}
This is the entire Fragment in question, minus some of the UI populating code as it's not important to show the setting of text in textviews etc...
public class SummaryFragment extends Fragment implements Listeners.GetDataListener {
private static final String KEY_UPDATER = "usageupdater";
private UsageUpdater mUpdater;
private Context ctx;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
this.ctx = activity;
}
private View findViewById(int id) {
return ((Activity)ctx).findViewById(id);
}
public void onGetData() {
// AsyncTask interface method, will be called from onPostExecute.
// Populate view from here
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_usagesummary, container, false);
mUpdater = (UsageUpdater) getArguments().getSerializable(KEY_UPDATER);
mUpdater.setDataListener(this);
return view;
}
}
If I understand any of this 'issue' it's that I'm returning an empty view in onCreateView but I don't know how to retain the fragment, return it's view prepopulated with data and manage all web service calling from the main activity.
In case you can't tell, Android is not a primary language for me and this probably looks a shambles. Any help is appreciated I'm getting rather frustrated.
If you're not using any alternative resources when the Activity is re-created, you could try handling the rotation event yourself by using configChange flags in your AndroidManifest:
<activity
...
android:configChanges="orientation|screenSize"
... />
There is no way to keep the same, pre-populated Views if your Activity is re-created since this would cause a Context leak:
http://www.curious-creature.org/2008/12/18/avoid-memory-leaks-on-android/