Showing a fragment with different parameters in tabs of a view pager - android

I want to show a same fragment for example fragment having activities of a day of week in a viewpager for all days with different data. I will be giving the dayNumber parameter to each fragment being instantiated and showing related activities. The problem is I see same fragment in each tab no matter what parameter I passed. I think the last fragment added or instantiated by a pager overrides all the other tab fragments instance. Because when I open a list item in expendableList View it is opened in all fragments of the pager.
This is how I am using the pager and fragment.
Pager
mPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
// When swiping between pages, select the
// corresponding tab.
if (bar.getNavigationMode() == ActionBar.NAVIGATION_MODE_TABS)
if (bar.getSelectedNavigationIndex() != position)
bar.setSelectedNavigationItem(position);
// should be changed when some solution comes.
if (tab == 0) {
Fragment ev;
if ((ev = (Fragment) mPagerAdapter.instantiateItem(mPager,
0)) instanceof frTimetable)
((frTimetable) ev).refresh(day.Monday);
} else if (tab == 1) {
Fragment ac;
if ((ac = (Fragment) mPagerAdapter.instantiateItem(mPager,
1)) instanceof frTimetable)
((frTimetable) ac).refresh(day.Tuesday);
}
tab = position;
}
Fragement
{
//class other methods
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fr_timetable, container, false);
ExpandList = (ExpandableListView) view.findViewById(R.id.expActivityView);
//I will change the list items in refresh method of the fragment for a day type
ExpListItems = new ArrayList<Items>(Timetable_Provider.getAllActivites());
ExpAdapter = new ExpandListAdapter(getActivity(), ExpListItems);
ExpandList.setAdapter(ExpAdapter);
return view;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}

instantiateItem() is certainly not what you want. That is called by ViewPager, not by a consumer of a ViewPager.
Normally, you would provide the data to the ViewPager as part of setting up the pages, inside of your PagerAdapter. For example, this sample app uses the arguments Bundle to pass the page number of the page to the Fragment that is the implementation of the page.
If the data inside a page needs to be updated, ideally the page itself determines on its own that this is needed and handles it. Or, use an event bus (e.g., LocalBroadcastManager, greenrobot's EventBus, Square's Otto) to publish information that relevant pages can pick up. There is no great way to get at an existing page from outside of the page itself using FragmentPagerAdapter or FragmentStatePagerAdapter (e.g., to have an activity push data into a page), which is one of the reasons I wrote ArrayPagerAdapter.

Related

How to make button visible only after viewpager page loaded completly?

I am new in view pager.i have 2 pages in view pager..each contain button..i want show button after page fully loaded.Now button is showing half scrolling it self..
class MyPageAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
public MyPageAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
#Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
#Override
public int getCount() {
return this.fragments.size();
}
public int getItemPosition(Object object) {
return POSITION_NONE;
}
adding page
pageAdapter = new MyPageAdapter(activity.getSupportFragmentManager(), fragments);
viewpager.setId(position);
viewpager.setAdapter(pageAdapter);
fragment:
public class secondclass extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_secondclass, container, false);
return rootView;
}
please help me..
Just use OnPageChangeListener.
Actually you need to do some tedious tasks for this to be done.
First your activity containing the viewpager should have a way to communicate with the fragments of the viewpager. once you are done with this.
Then set OnPageChangeListener on the viewpager
and one of the overridden callback methods would be the following one
#Override
public void onPageSelected(int position) {
// you know the position now. send the message to the fragment at this position to show the button or do any particular task
}
you are all set to go
In viewpager nearby fragments are loaded even before they are actually visible to the user, this is done so as to provide a smooth scrolling effect.
So onCreateView and onResume methods of the second fragment is already called...even before the user scrolls to the second page.
So to do something exactly when the user loads a page completely we need to user the onPageSelected method and with the help of the position argument we notify the fragment at that position to do the tasks.
In your case show the button...so you need to set visibility of the button INVISIBLE in the layout and make it visible when the page is selected.
For the above thing to work your activity needs to have a reference to the fragment...or you may request the viewpageradapter to provide you the reference by doing a bit caching of the current fragment...apply any logic of yours.
Assume all your fragments are of type MyFragment and have a method doVisibleTask to make the button visible AND getItem of your view pager adapter returns the same instance everytime for a particular position.
you could do the following
#Override
public void onPageSelected(int position) {
// get the reference to the fragment
MyFragment mf = (MyFragment)viewPagerAdapter.getItem(position);
mf.m=doVisibleTask();
// you know the position now. send the message to the fragment at this position to show the button or do any particular task
}

How to display images in Android and change it by swipe right/left?

I want to add an introduction to my Android application, to inform the user about how the app works. This intro will be displayed only, if the preferred settings intro will be false. So in this intro, there will be 3 images and at the end, there will be a page, with some text and two buttons, to enable the user to access the application, by making a login. The change between each image, will be made with a swipe movement, (so right to left +, left to right -). How Can I do ?
This can be done via the use of Fragments and ViewPager and FragmentPagerAdapter. Look at this documentation:
FragmentPagerAdapter: http://developer.android.com/reference/android/support/v4/app/FragmentPagerAdapter.html
ViewPager:
http://developer.android.com/reference/android/support/v4/view/ViewPager.html
You can have one fragment that is instantiated based on the id in the ViewPager, and that id will indicate which image to show in your image fragment. So for three images, you instantiate a new fragment that sets the image in the fragment based on the current page in the FragmentPagerAdapter. The second fragment can be one for the login buttons and text you want at the end.
Ex for adapter defined in your FragmentActivity (or AppCompatActivity)
public class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
if(position < 3)
return ImageFragment.newInstance(position);
else
return new LoginFragment();
}
}
Ex for the image fragment for the various images in your introduction:
public static class ImageFragment extends Fragment{
private int mPosition;
public ImageFragment(){
}
public static ImageFragment newInstance(int pos){
ImageFragment frag = new ImageFragment();
Bundle args = new Bundle();
args.putInt("pos", pos);
frag.setArguments(args);
return frag;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPosition = getArguments().getInt("pos");
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_image, container, false);
ImageView backgroundView = (ImageView) v.findViewById(R.id.background_image);
switch(mPosition){
case 0:
//set background view image 1
case 1:
//set background view image 2
default:
//set background view image 3
}
return v;
}
}
I would recommend using a ViewPager. Check out this tutorial from the Developer Guide
http://developer.android.com/training/animation/screen-slide.html
If you want to add functionality to each of these pages instead of having just images then perhaps you can implement a fragmentStatePagerAdapter and then put all the functionality in each fragment. Here is a tutorial to implement one.
http://www.truiton.com/2013/05/android-fragmentstatepageradapter-example/
I think we can do it by using recycler view itself.
Using PagerSnapHelper layout manager in recycler view, we can implement swipe to change images.
recyclerView.setLayoutManager(new LinearLayoutManager(this,
LinearLayoutManager.HORIZONTAL, false));
// add pager behavior
PagerSnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);

Fragment view in ViewPager is not restored when resuming

I have ActionBar Tabs setup. It consists of 4 tabs. Everything is fine until I navigate away from TabbedFragment and returning back.
I create tabs like this:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActionBar actionBar = getActionBar();
tabs = Lists.newArrayList();
tabs.add(new TabDefinition<>("Tab 1"));
tabs.add(new TabDefinition<>("Tab 2"));
tabs.add(new TabDefinition<>("Tab 3"));
tabs.add(new TabDefinition<>("Tab 4"));
for (TabDefinition tab : tabs) {
actionBar.addTab(actionBar.newTab()
.setText(tab.text)
.setTag(tab.tag)
.setTabListener(this));
}
}
And initialize adapter like this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.paging_tab_container, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewPager = (ViewPager) view.findViewById(R.id.pager);
viewPager.setAdapter(new FragmentStatePagerAdapter(getFragmentManager()) {
#Override
public Fragment getItem(int position) {
return tabs.get(position).fragment;
}
#Override
public int getCount() {
return tabs.size();
}
});
viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
getActionBar().setSelectedNavigationItem(position);
}
});
viewPager.setCurrentItem(getActionBar().getSelectedNavigationIndex(), true);
}
When returning back to TabbedFragment selected tab and 1 next to it would not have any content. Just empty view. But if I select current + 2 fragment content is loaded. And then returning to that first fragment content is reloaded.
For example I have A, B, C, D tabs. Before leaving TabbedFragment I had selected tab A.
When returning to TabbedFragment I still am at tab A, but it's empty. So is tab B.
But when selecting tab C it is created and loaded. Returning to tab A it is recreated.
What could be the problem here?
After a while ran into the same problem again, so updating this question.
If you're using FragmentStatePagerAdapter you should provide FragmentManager via getChildFragmentManager() instead of getFragmentManager(). See Issue 55068: ViewPager doesn't refresh child fragments when back navigation via backstack
Okay so When using a FragmentStatePagerAdapter your fragments will be destroyed when you navigate anymore than one fragment Away since by default offScreenPageLimit is set to 1 by default just as mentioned above.
Typically this Class is used for an activity that has a very large set of Fragments, i.e have to scroll through a large amount of views. If your application does not need more than say 3-4 tabs I would suggest using FragmentPagerAdapter instead, and then specifying your offScreenPageLimit to something like 3, so if you get to the 4th Tab, all 3 tabs before will still be in memory.
Here is some Sample Code for a project on github that i created illustrating how to dynamically load the fragments if you don't want to add this offScreenPageLimit.
https://github.com/lt-tibs1984/InterfaceDemo/blob/master/src/com/divshark/interfacedemo/InterfaceDemoMain.java
Walk through all this code in this Class, and you will see how I'm dynamically loading the fragments, each time my ViewPager is slid over. Most notably at the bottom.
You can download this code, and use it as a test base for what you want to do.
Try adding the setOffScreenPageLimit(2) in the onCreate() method for the viewPager and notice the different behavior. To check the behavior, edit the text in fragment 1. Navigate Away and navigate back, with this set or not. You will see when it is set, the fragment's text remains what you change it to, since the fragment is never recreated.
Please provide additional questions if you have them.
GoodLuck
UPDATE
private static final String [] fragmentClasses = {"com.example.project.YourFragment1","com.example.project.YourFragment2","com.example.project.YourFragment3"};
viewPager.setAdapter(new FragmentStatePagerAdapter(getFragmentManager()) {
#Override
public Fragment getItem(int position) {
Fragment fragmentAtPosition = null;
// $$$$ This is the Important Part $$$$$
// Check to make sure that your array is not null, size is greater than 0 , current position is greater than equal to 0, and position is less than length
if((fragmentClasses != null) && (fragmentClasses.length > 0)&&(position >= 0)&& (position < fragmentClasses.length))
{
// Instantiate the Fragment at the current position of the Adapter
fragmentAtPosition = Fragment.instantiate(getBaseContext(), fragmentClasses[position]);
fragmentAtPosition.setRetainInstance(true);
}
return fragmentAtPosition;
}
#Override
public int getCount() {
return fragmentClasses.length;
}
});
The problem exists in the Fragments you use as tabs, I think. They seem to not show anything when they are resumed (see Fragment lifecycle). The "weird" issue that only the currently selected +/-1 tab is empty, is because the offScreenPageLimit of your ViewPager is 1 by default. All tabs above this threshold are re-created.
Therefore, increasing the value will -- in your case -- cause all your tabs to appear empty after resuming. Check in your Fragment code which lifecycle methods you use to inflate your layout, set adapters and so forth, because that's what's causing your trouble.
I guess this happens because while loading fragment android loads current and current+1, if you debug you would not see onPause getting called for the immediate next fragment.
You can reload content programmatically in onTabChanged() method of TabHost.OnTabChangeListener.
After doing much research, this worked for me.
I have a complex layout with 3 tabs in a fragment, that gets switched out for other fragments. I realized that the ViewpagerAdapter will retain state, even if you press the home button. My problem was switching back and forth would null out the child fragment UI view elements and crash. The key is to not new out your ViewPagerAdapter. Adding the null check for the Adapter worked for me. Also, be sure to allocate setOffscreenPageLimit() for your needs. Also, from what I understand setRetainInstance(true); should not be used for fragments that have UI, it is designed for headless fragments.
In the fragment that holds your Tabs:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_tab, container, false);
tabLayout = (TabLayout) view.findViewById(R.id.tablayout);
viewPager = (ViewPager) view.findViewById(R.id.viewPager);
//Important!!! Do not fire the existing adapter!!
if (viewPagerAdapter == null) {
viewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager());
viewPagerAdapter.addFragments(new AFragment(), "A");
viewPagerAdapter.addFragments(new BFragment(), "B");
viewPagerAdapter.addFragments(new CFragment(), "C");
}
//Allocate retention buffers for three tabs, mandatory
viewPager.setOffscreenPageLimit(3);
tabLayout.setupWithViewPager(viewPager);
viewPager.setAdapter(viewPagerAdapter);
return view;
}
Or more simply when navigating back to tabbedfragment (assuming you use an intent and the fragment is within an activity) use:
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
This keeps the original activity and moves it to the top of the stack rather than recreating it, thus you never need to recreate the viewPager.

Cannot refresh/update listview in a fragment from another fragment in ViewPager

I am having a hard time figuring out the next thing.
What I have: I have a viewpager and several pages in it. In this question only two of them is important, lets call them Fragment1 and Fragment2 and they are next to each other. Fragment1 contains a listview which is filled with data from the internet (external database). Fragment2 contains a simple button.
My goal: If I click on the button in Fragment2, I add a new item to the external database. I would like to update/refresh the listview in the Fragment1 with this new item.
The notifyDataChanged() doesnt work in my case, however so far I was convinced that it reinstantiates every pages.. I am going to introduce my problem the clearest way I can, so lets see the code I have, this is my ViewPager adapter:
class MyPagerAdapter extends FragmentStatePagerAdapter {
public List<String> fragmentsA;
public MyPagerAdapter(FragmentManager fm) {
super(fm);
fragmentsA = fragments;
}
#Override
public Fragment getItem(int position) {
return Fragment.instantiate(context, fragmentsA.get(position));
}
#Override
public CharSequence getPageTitle(int position) {
return mEntries.get(position % CONTENT.length).toUpperCase();
}
#Override
public int getCount() {
return mEntries.size();
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
Fragment1 onCreateView() (shortly):
public View onCreateView(LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
getData();
View view = inflater.inflate(R.layout.latestapps_tab, container, false);
lw = (ListView) view.findViewById(R.id.lw);
context = getActivity().getApplicationContext();
act = this.getActivity();
m_adapter = new ItemAdapter();
lw.setAdapter(m_adapter);
return view;
}
I create the ViewPager and the adapter, I set the adapter for the ViewPager afterwards I fill the my viewpager with my fragments in my Main class. After this I am goint to have a fully functional ViewPager with 2 fragments.
pager = (ViewPager)findViewById( R.id.viewpager );
adapter = new MyPagerAdapter(getSupportFragmentManager());
indicator = (TabPageIndicator)findViewById( R.id.indicator );
pager.setAdapter( adapter );
indicator.setViewPager( pager );
pager.setCurrentItem(INITIAL_PAGE);
pager.setOffscreenPageLimit(3);
//adding fragments to the pager
fragments.add( Fragment1.class.getName());
fragments.add( Fragment2.class.getName());
In the Fragment1 I have a listview with some textviews in every list item. The loading works perfectly: I create the ArrayLists and I fill thoes lists with data from the external database. After loading is done, I fill the listviews with these tons of data.
In Fragment 2 I click on the button and I would like that listview to be updated so a new row should be created in the listview with some data from the external database. (of course writing into the database works)
My guess, that I might not refresh the ArrayLists or I dont reinstantiate the Fragment1, so the getDataFromSQL() method never turns only if I exit and launch the application again or I swipe so much in the ViewPager that the Fragment1 gets detached. So I cannot update or refresh the Fragment1. Could someone help in this questionL?
EDIT
I managed to make it happen with delivering a message to the fragment2 to update itself. But I am not sure if it is a good solution and there is not a better way, i.e. just refreshing somehow the whole fragment.
SOLUTION
Okay I think it must have been my laziness but I solved it now. For everyone who still wants to refresh a fragment from another one or just make conection between fragments, I tell you the appropriate approach:
You have to implement your own listener which helps you communicate between the fragments through the holder activity. This can be found here: http://developer.android.com/training/basics/fragments/communicating.html . Very simple and useful.
You have to retrieve the fragment, which is again simple: Retrieve a Fragment from a ViewPager These Q offers several acceptable way, I used the SpareArray solution.
Thank you for the help anyway!
you need be able to get your fragments from your activity, to do that you need to get the fragment from your adapter, you will need to add a couple methods to your page adapter
public Fragment getFragment(ViewPager container, int position, FragmentManager fm) {
String name = makeFragmentName(container.getId(), position);
return fm.findFragmentByTag(name);
}
private String makeFragmentName(int viewId, int index) {
return "android:switcher:" + viewId + ":" + index;
}
then from your activity make the following method
public Fragment getFragmentbyPosition(int position) {
return adapter.getFragment(pager, position, getSupportFragmentManager());
}
now on fragment2 call the following:
Fragment1 fragment1 = (Fragment1) ((MyActivity)getActivity()).getFragmentbyPosition(0);
now you will be able to call public methods on fragment1 from fragment 2, so just use that in your onClick and tell fragment1 to update it's listview.
now the reason makeFragmentName works is that is how the FragmentPagerAdapter creates the tag for the fragments it makes.

Android FragmentTab host and Fragments inside Fragments

I have an app with hierarchy like this:
FragmentTabHost (Main Activity)
- Fragment (tab 1 content - splitter view)
- Fragment (lhs, list)
- Framment (rhs, content view)
- Fragment (tab 2 content)
- Fragment (tab 2 content)
All fragment views are being inflated from resources.
When the app starts everything appears and looks fine. When I switch from the first tab to another tab and back again I get inflate exceptions trying to recreate tab 1's views.
Digging a little deeper, this is what's happening:
On the first load, inflating the splitter view causes its two child fragments to be added to the fragment manager.
On switching away from the first tab, it's view is destroyed but it's child fragments are left in the fragment manager
On switching back to the first tab, the view is re-inflated and since the old child fragments are still in the fragment manager an exception is thrown when the new child fragments are instantiated (by inflation)
I've worked around this by removing the child fragments from the fragment manager (I'm using Mono) and now I can switch tabs without the exception.
public override void OnDestroyView()
{
var ft = FragmentManager.BeginTransaction();
ft.Remove(FragmentManager.FindFragmentById(Resource.Id.ListFragment));
ft.Remove(FragmentManager.FindFragmentById(Resource.Id.ContentFragment));
ft.Commit();
base.OnDestroyView();
}
So I have a few questions:
Is the above the correct way to do this?
If not, how should I be doing it?
Either way, how does saving instance state tie into all of this so that I don't lose view state when switching tabs?
I'm not sure how to do this in Mono, but to add child fragments to another fragment, you can't use the FragmentManager of the Activity. Instead, you have to use the ChildFragmentManager of the hosting Fragment:
http://developer.android.com/reference/android/app/Fragment.html#getChildFragmentManager()
http://developer.android.com/reference/android/support/v4/app/Fragment.html#getChildFragmentManager()
The main FragmentManager of the Activity handles your tabs.
The ChildFragmentManager of tab1 handles the split views.
OK, I finally figured this out:
As suggested above, first I changed the fragment creation to be done programatically and had them added to the child fragment manager, like so:
public override View OnCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstance)
{
var view = inflater.Inflate(Resource.Layout.MyView, viewGroup, false);
// Add fragments to the child fragment manager
// DONT DO THIS, SEE BELOW
var tx = ChildFragmentManager.BeginTransaction();
tx.Add(Resource.Id.lhs_fragment_frame, new LhsFragment());
tx.Add(Resource.Id.rhs_fragment_frame, new RhsFragment());
tx.Commit();
return view;
}
As expected, each time I switch tabs, an extra instance of Lhs/RhsFragment would be created, but I noticed that the old Lhs/RhsFragment's OnCreateView would also get called. So after each tab switch, there would be one more call to OnCreateView. Switch tabs 10 times = 11 calls to OnCreateView. This is obviously wrong.
Looking at the source code for FragmentTabHost, I can see that it simply detaches and re-attaches the tab's content fragment when switching tabs. It seems the parent Fragment's ChildFragmentManager is keeping the child fragments around and automatically recreating their views when the parent fragment is re-attached.
So, I moved the creation of fragments to OnCreate, and only if we're not loading from saved state:
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
if (savedInstanceState == null)
{
var tx = ChildFragmentManager.BeginTransaction();
tx.Add(Resource.Id.lhs_fragment_frame, new LhsFragment());
tx.Add(Resource.Id.rhs_fragment_frame, new RhsFragment());
tx.Commit();
}
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstance)
{
// Don't instatiate child fragments here
return inflater.Inflate(Resource.Layout.MyView, viewGroup, false);
}
This fixed the creation of the additional views and switching tab's basically worked now.
The next question was saving and restoring view state. In the child fragments I need to save and restore the currently selected item. Originally I had something like this (this is the child fragment's OnCreateView)
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance)
{
var view = inflater.Inflate(Resource.Layout.CentresList, container, false);
// ... other code ommitted ...
// DONT DO THIS, SEE BELOW
if (savedInstance != null)
{
// Restore selection
_selection = savedInstance.GetString(KEY_SELECTION);
}
else
{
// Select first item
_selection =_items[0];
}
return view;
}
The problem with this is that the tab host doesn't call OnSaveInstanceState when switching tabs. Rather the child fragment is kept alive and it's _selection variable can be just left alone.
So I moved the code to manage selection to OnCreate:
public override void OnCreate(Bundle savedInstance)
{
base.OnCreate(savedInstance);
if (savedInstance != null)
{
// Restore Selection
_selection = savedInstance.GetString(BK_SELECTION);
}
else
{
// Select first item
_selection = _items[0];
}
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance)
{
// Don't restore/init _selection here
return inflater.Inflate(Resource.Layout.CentresList, container, false);
}
Now it all seems to be working perfectly, both when switching tabs and changing orientation.

Categories

Resources