onCreateView for tab being called whenever selecting adjacent tab? - java

I am having a weird issue where onCreateView is being called every time I navigate to an adjacent tab in my TabLayout. Here is my code:
news_feed.java:
private static TabLayout tabLayout;
#Override
public void onCreate(Bundle savedInstanceState) {
tabLayout = (TabLayout) findViewById(R.id.tab_layout);
tabLayout.addTab(tabLayout.newTab().setText("Public"));
tabLayout.addTab(tabLayout.newTab().setText("Friends"));
tabLayout.addTab(tabLayout.newTab().setText("My Tabs"));
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
final ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
final PagerAdapter adapter = new PagerAdapter
(getSupportFragmentManager(), tabLayout.getTabCount());
viewPager.setAdapter(adapter);
viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
PublicTab.java:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
System.out.println("Creating a new view");
fragmentView = inflater.inflate(R.layout.public_tab, container, false);
progressOverlay = fragmentView.findViewById(R.id.progress_overlay);
AndroidUtils.animateView(progressOverlay, View.VISIBLE, 0.9f, 200);
getPublicPosts(progressOverlay, fragmentView);
return fragmentView;
}
I have 3 other tabs that look exactly like PublicTab.java but with different layouts. I am not sure why onCreateView is being called so frequently. I thought in the lifecycle, onCreateView is only invoked for 2 reasons:
1. when we first initialize the tabs
2. if we come out from onStop or onPause method.
However, when switching between the tabs that are next to the actual tab, the println message is called like above and this is not what I want because I don't want to update the View of that fragment so frequently: only during the first 2 reasons above. Anyone know why this is happening? Any help would be appreciated! Thanks!

Use setoffScreenPageLimit
Android documentation:
public void setOffscreenPageLimit (int limit)
Set the number of pages that should be retained to either side of the
current page in the view hierarchy in an idle state. Pages beyond this
limit will be recreated from the adapter when needed
The default value is set to 1.

First and foremost of all, Viewpager default working property is this.
if you have around ten tabs and you are choice second tab.
viewpager will create tab2, tab1 and tab 3. This to provide smooth navigation between viewpagers. So whatever you do, your oncreate view will get called, because it will recreate the fragment everytime.
One thing you can do, is if you don't wan't to make a network call/or fetch data from online everytime a tab has been revisited after it's first creation.
you can do this.
save the data in TabLayout activity, use an interface create a setdata and getData method.
so whenever it reaches onCreateview. check if there is any data left in your get method, passing position to the method.
if exist display that data, else make a network call.

Related

ViewPager2 not working properly with Fragments and click events

I just wanna know if I'm doing something wrong since I'm kinda new to all this.
If there is anything else that you'll like me to add just let me know.
This is the repo branch where I'm trying to implement the ViewPager if you wanna see all the code.
Context
So I have 4 Categories represented with Fragments, each of this categories holds an ArrayList of items that each has a onItemClickListener that should reproduce some audio.
I'm trying to display the Fragments with a ViewPager but the problem is that when I scroll from a Fragment to another, then come back to the already created Fragment, it doesnt register the touch event, nothing happens, not even an error nor exception.
If I go to a newly created Fragment the touch works just fine.
Also, after switching back to an already created Fragment if I scroll even just a little bit to another Fragment and comeback or through the ArrayList of that Fragment for some reason it starts to recognize the touch in the ArrayList items again.
Similar questions that didn't really help
Fragments in ViewPager2 does not respond to clicks if scroll position is 0
ViewPager2 conflicting with SwipeRefreshLayout
Android ViewPager2 with fragment containing a recyclerview not scrolling
What I've tried
I tried to use a coordinatorlayout wrapping the ViewPager2 but there is no difference
I've been reading some of the official viewPager2 examples that are written in Kotlin but none of them seem to have a similar situation (also it's hard for me to read Kotlin code)
Code Snippets
word_list.xml:
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/root_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/tan_background" />
activity_main.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="MainActivity">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"/>
</FrameLayout>
This is one of the Fragments, the other three are basically the same, just the items in the arrayList change and some other minor things:
// ...Skipped some irrelevant code...
public class NumbersFragment extends Fragment {
private ArrayList<Word> mWords;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.word_list, container, false);
mWords = new ArrayList<>();
// ...Add all the items to the list...
// Make the adapter for the word items
WordAdapter adapter = new WordAdapter(getActivity(), mWords, R.color.category_numbers);
// Find the root view of the list
ListView listView = rootView.findViewById(R.id.root_list_view);
// Add adapter to the root list view
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.d("NumbersFragment", "CLICKED");
}
}
});
return rootView;
}
#Override
public void onPause() {
super.onPause();
Log.d("NumbersFragment", "Fragment paused");
}
}
This is the Category adapter, it manages the fragments:
public class CategoryAdapter extends FragmentStateAdapter {
private static final int NUM_CATEGORIES = 4;
// Required public constructor
public CategoryAdapter(#NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
}
#NonNull
#Override
public Fragment createFragment(int position) {
// Depending on which page the user is in,
// create a fragment of the corresponding category
switch (position) {
case 0:
return new NumbersFragment();
case 1:
return new FamilyFragment();
case 2:
return new ColorsFragment();
default:
return new PhrasesFragment();
}
}
#Override
public int getItemCount() {
return NUM_CATEGORIES;
}
}
And this is my MainActivity:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set the content of the activity to use the activity_main.xml layout file
setContentView(R.layout.activity_main);
// Find the view pager that will allow the user to swipe between fragments
ViewPager2 viewPager = findViewById(R.id.viewpager);
// Create an adapter that knows which fragment should be shown on each page
CategoryAdapter adapter = new CategoryAdapter(this);
//or CategoryAdapter adapter = new CategoryAdapter(getSupportFragmentManager(), getLifecycle());
// Set the adapter into the view pager
viewPager.setAdapter(adapter);
}
}
add this in your MainActivity viewPager.setOffscreenPageLimit(3); after creating viewpager
It’s because the ViewPager has a default offscreen limit of 1 ,and ViewPager2 has a default offscreen limit of 0.
In ViewPager2 when you switch tabs the previous tab will be automatically refreshed.
in ViewPager if you have 3 tabs or more when you switch to 3rd tab automatically first one will be destroyed and when you goes to 1st tab it will be recreated.
viewPager.setOffscreenPageLimit(3); from this line when you switch to a tab,the previous 3 tabs will be preloaded and next 3 tabs will be preloaded so nothing will be refreshed.

Select third page as default in TabLayout + ViewPager2

I'm trying to make third tab as default, but below piece of code does not work:
public class MessagesFragment extends Fragment {
private FragmentsAdapter fragmentsAdapter;
private ViewPager2 viewPager;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_messages, container, false);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
fragmentsAdapter = new FragmentsAdapter(this);
viewPager = view.findViewById(R.id.pager);
viewPager.setAdapter(fragmentsAdapter);
viewPager.setCurrentItem(2);
TabLayout tabLayout = view.findViewById(R.id.tab_layout);
TabLayoutMediator.TabConfigurationStrategy tabConfigurationStrategy = new TabLayoutMediator.TabConfigurationStrategy() {
#Override
public void onConfigureTab(#NonNull TabLayout.Tab tab, int position) {
switch (position) {
case FragmentsAdapter.MESSAGES_NEW:
tab.setText(R.string.navMessagesNew);
break;
case FragmentsAdapter.MESSAGES_READ:
tab.setText(R.string.navMessagesRead);
break;
case FragmentsAdapter.SETTINGS:
tab.setText(R.string.navSettings);
break;
default:
throw new IllegalArgumentException("There is no fragment for that position = " + position);
}
}
};
new TabLayoutMediator(tabLayout, viewPager, tabConfigurationStrategy).attach();
}
}
I'm using TabLayout and ViewPager2. All the time 1st tab is default.
Can anyone advise how to make a 3rd of my tab as default?
Thanks
I had the same problem, it looks like there is a problem with viewPager2, if you set the tab as soon as you add attach the TabLayoutMediator (using setCurrentItem) it has a weird behavior, select the tab but doesn't update the page of the viewPager.
But if you do it later, in a button or after a couple of seconds calling the method viewPager.setCurrentItem(2) works fine, in may case since I have to know to which tab I have to go I asked to my viewModel to tell me with tab I have to display and when the observer is nofity the tab is changing correctly.
At least for me this works but I can see the first tab load (the deault tab 0) and then the tab change for the one I selected, since this is not viable for me, I change to use viewPager (instead of viewPager2) and set it to the tabLayout as usual, tab_layout.setupWithViewPager(view_pager).
With the regular viewPager works fine.
Your code is correct:
viewPager.setCurrentItem(2);
viewPager.setCurrentItem(2, false);
tabLayout.getTabAt(2).select();
tabLayout.selectTab(tabLayout.getTabAt(2));
these are the same things
viewPager.setCurrentItem(2)
Modify to
viewPager.t.post {
viewPager.setCurrentItem(2, true)
}

setChecked and setSelected in CheckBox not working

I have an Activity which has 5 fragments in pager. I used
viewPager.setOffscreenPageLimit(fragmentList.size());
to be able to create all fragment in one time
I also have a listener which will pass parameter(object) to all fragment once I have a newIntent.
in one of my fragment I have CheckBox which should be selected according to the parameter from activity.
and other views which I were able to change the value of their texts and background.
just this check box I have set it on But when I see it, I have see it is off, all other events works well.
here is the activity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(R.style.AppTheme_PopupOverlay);
setContentView(R.layout.activity_configurations);
ViewPager viewPager = findViewById(R.id.container);
TabLayout tabLayout = findViewById(R.id.tabs);
setNdef(getIntent().getExtras());
String type = getNdef().getString("id");
List<Fragment> fragmentList = createFragments(type);
SectionsPagerAdapter pagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager(), fragmentList);
viewPager.setAdapter(pagerAdapter);
fillTableLayout(tabLayout, type);
viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
tabLayout.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(viewPager));
tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
viewPager.setOffscreenPageLimit(fragmentList.size());
readNfcDialog = createReadDialog();
writeNfcDialog = createWriteDialog();
readNfcDialog.show();
setForeground();
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
ClassNfcTag wepTag = new ClassNfcTag(intent);
String type = wepTag.defineClassTag();
OmsRepeaterTag omsRepeaterTag = new OmsRepeaterTag(intent);
byteMap = omsRepeaterTag.readTag();
if (byteMap != null) {
setTagOmsConfiguration(OmsConfiguration.fromByteArray(byteMap));
for (OmsListener listener : onReceiveOmsList) {
listener.gotOms(getInTagOmsConfiguration());
}
}
}
and here is one of the fragments
#Override
public void gotOms(OmsConfiguration configuration) {
setConfiguration(configuration);
if (isAdded()) {
boolean status = getConfiguration().getDeleteRsl();
delete.setChecked(status);
delete.setSelected(status);
}
}
as a solution I have tried to use Switch instead of CheckBox and it worked well.
and I also tried to set the pager to start from exactly the fragment which has the CheckBox and it also work.
viewPager.setCurrentItem(checkBoxFragmentPosition);
I also tried to debug the code and It shows me that the checkBox is checked and when I tried to change it to checked by touching it (programmatically it is checked, in UI I see it unchecked) it change itself from unchecked to unchecked .and then with the next touch changed to checked.
just for the people who will read this question, simply this was an unresolved bug in android.
For my case I used an toggleButton instead checkBox.

Android - How to restore state of ViewPager's items

I'm having trouble with restoring state of a View inside a ViewPager. The content of the ViewPager is a view extending FrameLayout.
The problem is the FrameLayout.onRestoreInstanceState() is not being called if added programmatically into the ViewPager
Here's the code of my Activity.java
private ViewPager vPager;
private MainPagerAdapter mAdapter;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
setContentView(R.layout.activity_layout);
// all the findViewById stuff
CustomView cv1 = new CustomView(this);
CustomView cv2 = new CustomView(this);
cv1.setId(R.id.custom_view_id_1);
cv2.setId(R.id.custom_view_id_2);
mAdapter = MainPagerAdapter();
mAdapter.addView(cv1);
mAdapter.addView(cv2);
vPager.setAdapter(mAdapter);
}
MainPagerAdapter is a class from the accepted answer of this question
Source code for CustomView.java
#Override
protected Parcelable onSaveInstanceState() {
Log.d(TAG, "onSaveInstanceState() called");
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
Log.d(TAG, "onRestoreInstanceState() called");
}
Here's my findings so far:
onSaveInstanceState() will be called but onRestoreInstanceState() is not
When I tried to add the View directly to the root of the Activity, it calls both functions.
I found out that Activity's onRestoreState function will be called before the ViewPager calls the adapter's instantiateItem() function. So when the activity restore its state, the ViewPager doesn't have any children yet, thus the savedState doesn't belong to anyone
So I figure out that I need to make one of two things to work:
Make sure the ViewPager instantiate the item before trying to restore the state, or
Calls the CustomView's onRestoreInstanceState() manually.
I somehow managed to make option number 2, but is there any way to do option number 1?
If I understood your question, you can save your ViewPager items state using mPage.setOffscreenPageLimit(4); 4 is the number of my Fragments inside ViewPager.

Nested ViewPager returns null after rotation

I need a reference to VerticalViewPager in PageFragSanningellerKonsekvens.
I got two ViewPagers: one for horizontal swiping that goes on for ever, and one for vertical swiping that loads three fragments (mid, swipe up, swipe down) that loads content. The horizontal ViewPager loads a new instance of VerticalViewPager on every swipe. I need a reference in the actual fragment that is loaded by the VerticalViewPager, so that I can swipe vertically by tapping a button. I also need a reference to the horizontal ViewPager to allow the first page to have a button that triggers a horizontal swipe.
I've solved it by sending the reference as an argument like this:
FragSanningEllerKonsekvens - The class that initializes the VerticalViewPager.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_sanning_eller_konsekvens, container, false);
initComponents();
setComponents();
return view;
}
private void initComponents() {
verticalViewPager = (VerticalViewPager) view.findViewById(R.id.verticalviewpager);
mStatementPagerAdapter = new TruthDarePagerAdapter(getChildFragmentManager()).newInstance(getChildFragmentManager(),mViewPager, verticalViewPager);
}
Its transfers in TruthOrDarePagerAdapter:
private ViewPager mViewPager;
private VerticalViewPager mVerticalViewPager;
public static TruthDarePagerAdapter newInstance(FragmentManager fm, ViewPager mViewPager, VerticalViewPager mVerticalViewPager){
TruthDarePagerAdapter horizontalPagerAdapter = new TruthDarePagerAdapter(fm);
horizontalPagerAdapter.setViewPager(mViewPager);
horizontalPagerAdapter.setVerticalViewPager(mVerticalViewPager);
return horizontalPagerAdapter;
}
public VerticalViewPager getVerticalViewPager() {
return mVerticalViewPager;
}
public void setVerticalViewPager(VerticalViewPager mVerticalViewPager) {
this.mVerticalViewPager = mVerticalViewPager;
}
private void setViewPager(ViewPager mvViewPager){
this.mViewPager = mvViewPager;
}
private ViewPager getViewPager(){
return mViewPager;
}
#Override
public Fragment getItem(int position) {
PageFragSanningellerKonsekvens frag = new PageFragSanningellerKonsekvens().newInstance(getViewPager(), getVerticalViewPager());
.
.
.
return frag;
}
PageFragSanningellerKonsekvens - The class that needs the reference.
public static PageFragSanningellerKonsekvens newInstance(ViewPager mViewPager, VerticalViewPager mVerticalViewPager){
PageFragSanningellerKonsekvens frag = new PageFragSanningellerKonsekvens();
frag.setViewPager(mViewPager);
frag.setVerticalViewPager(mVerticalViewPager);
return frag;
}
private void setupMenu() {
viewTop = view.findViewById(R.id.sek_top);
viewBottom = view.findViewById(R.id.sek_bottom);
viewTop.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getVerticalViewPager().setCurrentItem(0, true);
}
});
viewBottom.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getVerticalViewPager().setCurrentItem(2, true);
}
});
}
The code above works excellent until I rotate the device. After rotation the getVerticalViewPager() returns null. In other parts of the application I've used this pattern without any problems: on rotation the ViewPager is sent as a parameter and the application reloads without dropping the reference.
So the conclusion I've drawn is that the VerticalViewPager doesn't get reinitialized on rotation, while PageFragSanningellerKonsekvens is.
My question is; how can I solve it? Can I force it to be reinitialized, or can I obtain a reference to it in some other way?
EDIT 1: I'll think its related to getItem() in the FragmentStatePagerAdapter. It will be called to allocate 3 fragments at every given time. I think this might nullify the getVerticalViewPager() method.
EDIT 2: I moved some code to allow the ViewPager and the button to be initialized in the same class, thus making it somewhat null-proof. Thanks for the help!
Ok this might not be a complete answer but I think it would be too long a comment.
I think you are right that the null pointer happens at getItem in the adapter. (a logcat report to confirm this would be nice).
The reason for this (I think) is that the adapter tries to recreate the inner fragments before the inflation of the ViewPager is complete. Normally I create similar adapters in onCreate because it normally do not rely on anything but a fragmentmanager.
Having this in mind, and the fact that you might have a hard time avoiding the null pointer in the current design, I propose a new design.
Am only at my phone right now so is hard to give you the code I have in mind. For now I just sketch the idea:
Move the getter/setter of ViewPagers from the adapter to FragSanningEllerKonsekvens, leaving the creation of TruthOrDarePagerAdapter only depend on fragment manager
Now do the same for PageFragSanningellerKonsekvens making it simply PageFragSanningellerKonsekvens.newInstance() in getItem of the adapter
Finally, you are now missing the reference to the ViewPagers in PageFragSanningellerKonsekvens PageFragSanningellerKonsekvens, but remember you now have the getters of both ViewPagers in the parent FragSanningEllerKonsekvens
So the buttons now become:
((FragSanningEllerKonsekvens)getParentFragment()).getVerticalViewPager().setCurrentItem(0, true);
And
((FragSanningEllerKonsekvens)getParentFragment()).getVerticalViewPager().setCurrentItem(2, true);

Categories