android disable fragment 0 in fragmentpager - java

I'd like to be able to not allow switching to one of my 3 fragments in my custom FragmentPagerAdapter when certain conditions are met. Unfortunately I failed to find any help.
I'd prefer to not remove and re-add the fragment (item 0), as this criterium can change inside the FragmentActivity any time and I'd need the fragment to be able to receive and handle messages.
Any suggestions? Please ask if something's unclear.
Edit: Sharing some code
Main activity:
#Override
protected void onResume() {
super.onResume();
...
_sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager(), this, cardnumber, pin);
_viewPager = (ViewPager) findViewById(R.id.pager);
_viewPager.setAdapter(_sectionsPagerAdapter);
_viewPager.setOnPageChangeListener(_sectionsPagerAdapter);
_viewPager.setCurrentItem(1);
_viewPager.setOffscreenPageLimit(2);
This seems a bit rude, but it works
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
Bundle args = new Bundle();
switch (position) {
case CUSTOMER_DETAILS_PAGE:
// fragment = _fragments.get(TRLIST_PAGE);
fragment = new UserDetailFragment();
args.putString("TAG", "details");
break;
case BALANCE_PAGE:
// fragment = _fragments.get(BALANCE_PAGE);
fragment = new BalanceFragment();
args.putString("TAG", "balance");
break;
case TRLIST_PAGE:
// fragment = _fragments.get(TRLIST_PAGE);
fragment = new TrListFragment();
args.putString("TAG", "trlist");
break;
default:
break;
}
_listeners.put(position, (MyTabSelectedListener) fragment);
// fragment = (Fragment) _listeners.get(position);
args.putString("cardnumber", _cardnumber);
args.putString("pin", _pin);
fragment.setArguments(args);
return fragment;
}
I want to limit access to the CUSTOMER_DETAILS_PAGE when it would be empty anyway.

After reading corsair992-s answer here https://stackoverflow.com/a/23967701/3153792 and looking at the CWAC-Pager source code here https://github.com/commonsguy/cwac-pager I managed to find what I was looking for.
The key was to change the getItemPosition function (which I didn't even use before) to get the TAG argument (set in getItem) from the fragments and identify them that way:
#Override
public int getItemPosition(Object item) {
Fragment fragment = (Fragment) item;
String title = fragment.getArguments().getString("TAG");
int position = tabs.indexOf(title);
if (position >= 0) {
return position;
} else {
return POSITION_NONE;
}
First I define tags for each tab and a list to hold them in order of appearance:
public static final String CUSTOMER_DETAILS_PAGE = "detail";
public static final String BALANCE_PAGE = "balance";
public static final String TRLIST_PAGE = "trlist";
List<String> tabs = new ArrayList<String>();
Afterwards, I add them in the constructor in the original order:
tabs.add(CUSTOMER_DETAILS_PAGE);
tabs.add(BALANCE_PAGE);
tabs.add(TRLIST_PAGE);
When I need to hide a tab, I just remove it from the list and notify the adapter:
public void disableUserDetail() {
tabs.remove(tabs.indexOf(CUSTOMER_DETAILS_PAGE));
notifyDataSetChanged();
}
My fragment specifically goes to the left side (the beginning) but it would be easy to change:
public void enableUserDetail() {
tabs.add(0, CUSTOMER_DETAILS_PAGE);
notifyDataSetChanged();
}
I had to change getItem and getPageTitle to use the tabs list and TAG constants as identifier (which I also include as arguments for getItemPosition):
#Override
public Fragment getItem(int position) {
String id = tabs.get(position);
Fragment fragment = null;
Bundle args = new Bundle();
if (id.equals(CUSTOMER_DETAILS_PAGE)) {
fragment = new UserDetailFragment();
args.putString("TAG", CUSTOMER_DETAILS_PAGE);
...
#Override
public CharSequence getPageTitle(int position) {
Locale l = Locale.getDefault();
String id = tabs.get(position);
if (id.equals(CUSTOMER_DETAILS_PAGE)) {
return _ctx.getString(R.string.title_section1).toUpperCase(l);
...

Related

MapFragment. When inflating a layout: java.lang.IllegalStateException: Fragment already added

I get the next error: java.lang.IllegalStateException: Fragment already added.
This is a layout getting inflated each time I change tab, as seen in this photo:
![FirstTab]http://prntscr.com/h4e8pc
![SecondTab]http://prntscr.com/h4ebyn
So i get the idea of the problem, but I've tried many ways to fix it with no result. I've tried to use .replace instead of .add, that way it doesnt crash but the map is not loaded. And also tried to delete the fragment as soon as I switch to that tab but the result is similar to the .replace one.
case R.id.navigation_dashboard:
frame = (FrameLayout) findViewById(R.id.content);
frame.removeAllViewsInLayout();
LayoutInflater.from(getApplicationContext())
.inflate(R.layout.activity_prueba_dashboard, frame, true);
fragmentTransaction =
getFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.content2, mMapFragment);
fragmentTransaction.commit();
mMapFragment.getMapAsync(my_maps_class);
I believe it's much cleaner to use viewpager in that situation. Check this code :
public class HomeViewPagerAdapter extends FragmentPagerAdapter {
public static final int FRAGMENT_A_INDEX = 0;
public static final int FRAGMENT_B_INDEX = 1;
public static final int FRAGMENT_C_INDEX = 2;
public static final int FRAGMENTS_COUNT = 3;
public HomeViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case FRAGMENT_A_INDEX:
return FragmentA.newInstance();
case FRAGMENT_B_INDEX:
return FragmentB.newInstance();
case FRAGMENT_C_INDEX:
return FragmentC.newInstance();
default:
return null;
}
}
#Override
public int getCount() {
return FRAGMENTS_COUNT;
}
}
and then link the adapter with the ViewPager in the hosting appcompat activity as here :
HomeViewPagerAdapter homeTabsAdapter = new HomeViewPagerAdapter(getSupportFragmentManager());
homeTabsViewPager.setAdapter(homeTabsAdapter);
and you can easily switch between fragments by calling :
homeTabsViewPager.setCurrentItem(HomeViewPagerAdapter.FRAGMENT_A_INDEX, false);
You seen to be trying replacing the content of the same Fragment, in this case you dont need the FragmentTransaction.
The solution is to create a second fragment on which you have the map, in this case the Transaction is needed.

Sliding between fragments

I'm developing an app and currently I am trying to add ScreenSlide to it. I managed to do this using a tutorial, but usual sliding between x pages is not quite what I'm looking for.
With code provided below I can slide between 5 pages, but pages aligned in a straight line and you can't go from first page straight to 5th and vise versa.
In my app I have 4 pages. When I slide left I switch between first 2 pages, when I slide right I switch between 2 last pages. On image below you can see how my current code switches page and under it - my goal.
public class MainActivity extends FragmentActivity {
private static final int NUM_PAGES = 5;
private ViewPager mPager;
private ScreenSlidePageFragment[] pages = new ScreenSlidePageFragment[NUM_PAGES];
private PagerAdapter mPagerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_screen_slide);
mPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
}
#Override
public void onBackPressed() {
if (mPager.getCurrentItem() == 0) {
super.onBackPressed();
} else {
mPager.setCurrentItem(mPager.getCurrentItem() - 1);
}
}
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return NUM_PAGES;
}
#Override
public Fragment getItem(int position) {
boolean moveRight = mPager.getCurrentItem() < position;
boolean moveLeft = mPager.getCurrentItem() > position;
switch(position){
case 5:
if(moveRight){
return geLog.w("i"
+ "Info", Integer.toString(position));
//return getPageByPosition(2);
if(moveLeft)
return getPageByPosition(2);
}
return new ScreenSlidePageFragment();
}
private Fragment getPageByPosition(int position){
int index = position - 1;
if(index < 0 || index > NUM_PAGES-1)
throw new InvalidParameterException("requested position is invalid");
if(pages[index] == null)
pages[index] = new ScreenSlidePageFragment();
return pages[index];
}
}
}
[UPDATE]
I've managed to write a code that allows me to infinitely slide to the right between 6 different pages. Left side is limited though - I can slide only to the first page(so if I'm on 1st page after I cycled 3 times to the right, I can make only 3 cycles backwards). I think I am very close to finding the solution. Any ideas?
MainActivity.java
public class MainActivity extends FragmentActivity {
private static final int NUM_PAGES = 6;
private ViewPager pager;
private PagerAdapter pagerAdapter;
private List<ScreenSlidePageFragment> slideList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
slideList = new ArrayList<ScreenSlidePageFragment>();
for (int i = 0; i<NUM_PAGES; i++){
ScreenSlidePageFragment slide = new ScreenSlidePageFragment();
slide.setIndex(i+1);
slideList.add(slide);
}
pager = (ViewPager) findViewById(R.id.pager);
pagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
pager.setAdapter(pagerAdapter);
}
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
int _pos = position % NUM_PAGES;
return slideList.get(_pos);
}
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
}
}
ScreenSlidePageFragment.java
public class ScreenSlidePageFragment extends Fragment {
private int index;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup)inflater.inflate(R.layout.slide, container, false);
TextView tw = (TextView) root.findViewById(R.id.textView);
tw.setText(Integer.toString(index));
return root;
}
public void setIndex(int index){
this.index = index;
}
}
One possible workaround is not using viewpager, but creating your own custom Slider, I ended up with this solution, since ViewPager is very unflexible, like for example if you try to do something like Facebook Page app kind of thing.
Cons: you have to manage all the touches and lifecycle.
Another simpler solution for this is basically use the following API if I understand the question correctly it should be enough.
ViewPager.setCurrentItem
This is what I would do.
Since you have a finite amount of screens, I would make an array to save each instance of the ScreenSlidePageFragment you make under a corresponding index. So you can get an instance from that array by using "position". This will allow you to leverage getItem function to do something like
...
private ScreenSlidePageFragment[] pages = new ScreenSlidePageFragment[NUM_PAGES];
...
#Override
public Fragment getItem(int position) {
boolean moveRight = mPager.getCurrentItem() < position;
switch(position){
case 1:
if(moveRight)
return getPageByPosition(3);
return getPageByPosition(2);
case 2:
if(moveRight)
//case when you are in a position 3 and swipe right - return first screen
return getPageByPosition(3);
//if you're swiping left from position 1, return the next screen
return getPageByPosition(1);
case 3:
//write out the rest of your logic
...
}
}
private Fragment getPageByPosition(int position){
int index = position - 1;
if(index < 0 || index > NUM_PAGES-1)
throw new InvalidParameterException("requested position is invalid")
if(pages[index] == null)
pages[index] = new ScreenSlidePageFragment();
return pages[index];
}
Note: code not tested
Basically, you will need to write out the logic to return an existing instance of a fragment based on where you are currently and which position is requested.
You can try ViewPager.setCurrentItem
If you want to navigate to page
viewPager.setCurrentItem(PAGE_POS);
If you want to navigate to page without smooth scroll
viewPager.setCurrentItem(PAGE_POS,false);
I think it's possible to do.
1. Set ViewPager's item count to Integer.MAX_VALUE and current item to Integer.MAX_VALUE/2. It gives you ability to create fake infinite scroll.
2. Realize your logic depending on fragment on current position. It is easy if you store them in your adapter:
#Override
public Fragment getItem(int position) {
ItemFragmentRoot fragment = new ItemFragmentRoot();
mFragmentMap.put(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
mFragmentMap.remove(position);
}
public ItemFragmentRoot getItem(int position){
return mFragmentMap.get(position);
}
And the most difficult part, I think. Since ViewPager cache at least 1 view each side and you don't know which way user going to scroll the only one way is to manualy initialize and reinitialize appearing fragment according to scroll side. Use nested fragment inside each ItemFragmentRoot I mentioned before. All depends on how heavy your view hierarchy.
I can write more code later, if you need.

Viewpager not saving Fragment states

First of all I would like to thank the stackoverflow community. Thanks to you I solved so much problems! Usually I bang my head over the wall for hours and I find the solution here (98% percent of the time). This time I would like to share my problem.
This is the first app I'm developing. I have one activity (is this bad?). My app structure is the following: Nav drawer which contains four items - two viewpagers and two static fragments. The viewpagers contains lists with data and if you click on one of the items from the list you are present with a detail page which again is a viewpager. When you are on the detail page you can click and advance to other fragment. The problem is that when I advance to the other fragment onSavedInstance is not called for the viewpagerdetails and I can't retain the fragment state. onSavedInstance will be called if I pass to the FragmentPagerAdapter getFragmentManager(), instead of getChildFragmentManager(), but if I do this when I navigate back, the viewpager items are blank. I have tried using FragmentStatePagerAdapter, but when I click back from the 'other fragment' to return to viewpager details the app crashes with the following error: Fragment no longer exists for key f0: index 0. I have looked over the internet, but I didn't found a working solution. Some say that the problem is from the adapter, but I tried this solution and it didn't work. Also I want to manually retain my fragment state, please don't offer me the fix with the configchanges in the app manifest. I have tried to call onSavedInstanceState from the onStop method, but when I return to the fragment the saved state is empty. Should I implement this with a static variable in my mainActivity?
FragmentPagerAdapter:
public class DetailsSlideAdapter extends FragmentPagerAdapter {
private String[] navMenuTitles;
private FragmentManager manager;
private FragmentTransaction mCurTransaction = null;
private Resources res;
public DetailsSlideAdapter(FragmentManager fm, Resources res) {
super(fm);
this.manager = fm;
navMenuTitles = res.getStringArray(R.array.detailTabs);
this.res = res;
}
#Override
public int getCount() {
return 3;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
DetailsInfo info = new DetailsInfo();
return info;
case 1:
DetailsCast cast = new DetailsCast();
return cast;
case 2:
DetailsOverview overview = new DetailsOverview();
return overview;
default:
return null;
}
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return navMenuTitles[0];
case 1:
return navMenuTitles[1];
case 2:
return navMenuTitles[2];
default:
return navMenuTitles[1];
}
}
Viewpager details:
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Get the ViewPager and set it's PagerAdapter so that it can display items
DetailsSlideAdapter = new DetailsSlideAdapter(getChildFragmentManager(), getResources());
mViewPager = (ViewPager) rootView.findViewById(R.id.DetailsPager);
mViewPager.setOffscreenPageLimit(3);
mViewPager.setAdapter(DetailsSlideAdapter);
// Give the SlidingTabLayout the ViewPager, this must be done AFTER the ViewPager has had
// it's PagerAdapter set.
mSlidingTabLayout = (DetailsSlidingTabLayout) rootView.findViewById(R.id.sliding_tabs);
mSlidingTabLayout.setViewPager(mViewPager);
mSlidingTabLayout.setSelectedIndicatorColors(getResources().getColor(R.color.tabSelected));
}
If other code is needed I will post it. Thanks.
EDIT using FragmentStateAdapter:
App crashes: Fragment no longer exists for key f0: index 0. On the internet people say that the problem is from the adapter, but I tried this solution and it didn't work. Code:
public class DetailsSlideAdapter extends FragmentStatePagerAdapter {
private String[] navMenuTitles;
private FragmentManager manager;
private FragmentTransaction mCurTransaction = null;
private Resources res;
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
public DetailsSlideAdapter(FragmentManager fm, Resources res) {
super(fm);
this.manager = fm;
navMenuTitles = res.getStringArray(R.array.detailTabs);
this.res = res;
}
#Override
public int getCount() {
return 3;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
DetailsInfo info = new DetailsInfo();
return info;
case 1:
DetailsCast cast = new DetailsCast();
return cast;
case 2:
DetailsOverview overview = new DetailsOverview();
return overview;
default:
return null;
}
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return navMenuTitles[0];
case 1:
return navMenuTitles[1];
case 2:
return navMenuTitles[2];
default:
return navMenuTitles[1];
}
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
Have you seen this? FragmentStatePagerAdapter
From the Docs:
Implementation of PagerAdapter that uses a Fragment to manage each
page. This class also handles saving and restoring of fragment's
state.

Unable to access variable inside listener

I have a listener excerpt below which contains the variable degree.
public class CustomOnItemSelectedListener implements AdapterView.OnItemSelectedListener {
//Define Components
public EditText text;
public Spinner spinner1;
//Define Variables
public String degree;
public void onItemSelected(AdapterView<?> parent, View view, int pos,long id) {
//Set the selected item on spinner1 to the variable tempValue
String tempValue = spinner1.getSelectedItem().toString();
degree = "jobby";
}
I am trying to access that variable within my ViewPager adapter like so:
public class MyPagerAdapter extends FragmentPagerAdapter {
public CustomOnItemSelectedListener selectedListener;
[...]
#Override
public Fragment getItem(int pos) {
switch(pos) {
case 0: return resultFragment.newInstance(selectedListener.degree);
case 1: return resultFragment.newInstance("resultFragment, Instance 2");
default: return resultFragment.newInstance("resultFragment, Default");
}
}
[...]
}
And in my MainActivity
protected void onCreate(Bundle savedInstanceState) {
[...]
myPager = new MyPagerAdapter(getSupportFragmentManager());
selectedListener = new CustomOnItemSelectedListener();
addListenerOnSpinnerItemSelection();
myPager.selectedListener = selectedListener;
}
The issue I am having is that I am unable to access the variable of degree. If I set the value of the variable at the top of the listener class like so
public String degree = "jobby";
Then I can access the variable.
I am trying to write an if statement within the listener, and depending on which item of the spinner is selected, the variable changes.
How can I access the variable within the listener class?
I think it's because you need initialize the variable, try this:
public String degree = null;
And make sure when you use the variable it's with the correct value, to try avoid NullPointerExceptions.
In CustomOnItemSelectedListener#onItemSelected the fragment needs to be replaced using the Activity's FragmentManager.
Refer to the Fragment example code (scroll down to the showDetails() method). However, instead of FragmentManager#findFragmentById you should use FragmentManager#findFragmentByTag.
Perhaps something like this:
FragmentManager fragManager = getFragmentManager();
FragmentTransaction xact = fragManager.beginTransaction();
ResultFragment resultFragment = (ResultFragment)fragManager.findFragmentByTag(selectedListener.degree);
if(resultFragment == null) {
mList = resultFragment.newInstance(selectedListener.degree);
xact.add(R.id.view_container, resultFragment, selectedListener.degree);
}
xact.commit();
Be careful, though, if degree varies wildly then this would result in many Fragments contained in the FragmentManger. In this case you should remove the unused Fragments as needed.

How to pass string from one Fragment to another? [duplicate]

This question already has answers here:
How to pass values between Fragments
(18 answers)
Closed 7 years ago.
I want to pass a string call custID from one fragement to anopther. but I don't know how to pass it. Below is my code:
class TabsAdapter extends FragmentPagerAdapter {
public TabsAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return 2;
}
#Override
public Fragment getItem(int i) {
switch(i) {
case 0: return MaterialUpConceptFakePage.newInstance();
case 1: return MaterialUpConceptFakePage.newInstance();
}
return null;
}
#Override
public CharSequence getPageTitle(int position) {
switch(position) {
case 0: return "Tab 1";
case 1: return "Tab 2";
}
return "";
}
}
Can someone help me on this?
Bundle bundle=new Bundle();
bundle.putString("ID", "value");
MyFragment obj=new MyFragment ();
obj.setArguments(bundle);
well you can use bundle as below -
Fragment fragment = new Fragment();
Bundle bundle = new Bundle();
bundle.putInt(key, value);
fragment.setArguments(bundle);
getFragmentManager()
.beginTransaction()
.replace(R.id.body, fragment, null)
.commit();
and then inside onCreateView() you can receive as below -
Bundle args = getArguments();
int myInt = bundle.getInt(key, defaultValue);
User Bundle to pass any value from one Fragment to another. This is how you can pass string from one Fragment to another.
Put the value
SecondFragment secondFragment = new SecondFragment ();
Bundle args = new Bundle();
args.putString("MyKey", "MyValue");
secondFragment.setArguments(args);
Load the second fragment
getFragmentManager().beginTransaction().add(R.id.container, secondFragment).commit();
In onCreateView of the second Fragment:
//Retrieve the value
String value = getArguments().getString("MyKey");
While a simple Bundle like other answers works, I can already see you use the newInstance so I will show you an example while still using newInstance to make the code easier to maintain in the future.
In your fragment where you want the String to be add this code
public static YourFragmentName newInstance(String str) {
YourFragmentName fragment = new YourFragmentName ();
Bundle args = new Bundle();
args.putSerializable("customer_id", str);
fragment.setArguments(args);
return fragment;
}
Then looking at your code instead of
MaterialUpConceptFakePage.newInstance()
you should have
MaterialUpConceptFakePage.newInstance(customerid)
Then inside your onCreateView
String customer_id = (String) getArguments().get("customer_id");
SharedPreference is a good way for passing data between activities or fragment. You can send any data int,boolean, string int etc.
SharedPreferences preference;
//Initialize preference
preference = getActivity().getSharedPreferences("STATE",MODE_PRIVATE);
// it uses key-value pairs
// put string data to preference
preference.edit().putString("Your_String_Data", "anything").commit();
//use this to get that value in any fragment.
preference = getActivity().getSharedPreferences("STATE",
Context.MODE_PRIVATE);
String data = preference.getString("Your_String_Data",null);

Categories