ViewPager - Events in all page occurs only in last page - android

I have a viewPager with say 4 pages. All 4 pages uses same Xml. When i do an event in 1st page somehow it always triggers in the last page.
Here is my PagerAdapter
#Override
public Object instantiateItem(ViewGroup container, int pos) {
View desktopView;
OnTouchListener tl = null;
desktopView = act.getLayoutInflater().inflate(
act.getViewPagerLayout(groupName), null);
RelativeLayout rr_appContainer, rr_dialogContainer;
ImageView rr_home_container = (ImageView) desktopView
.findViewById(R.id.imageView_forClick);
Button buttonChange = (Button)desktopView.findViewById(R.id.B1);
Button buttonDelete = (Button)desktopView.findViewById(R.id.B2);
rr_appContainer = (RelativeLayout) desktopView
.findViewById(R.id.rr_home_container);
rr_dialogContainer = (RelativeLayout) desktopView
.findViewById(R.id.rr_dialogView);
..........
buttonDelete.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
deletestuff();
}
buttonChange.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
changeColorOfStuff();
}
.....
return desktopView;
}
What is happening is, When i click on buttonChange from 1st page it supposed to change the color of text on 1st page, but actually it is changing color of the last page. Similarly buttonDelete is deleting color from last page.
Regardless of what page i am in, its reflecting those changes on last page.
Any help would be appreciated.

From the context given here, the deleteStuff() and changeColorOfStuff() can only be members of the Fragment/Activity that owns the adapter, or the adapter itself. So these methods can only act on members of those classes. ViewPager asks the adapter for the fragments it is going to display. However, the text in the fragment being shown by the ViewPager belong to the that fragment. To act on that text, you need a method that's a member of that fragment. The usual way to do this is to use a custom fragment. For example:
Custom Fragment (inner class):
public static class CustomFragment extends Fragment {
//members of the fragment
TextView yourTextView;
...
public static CustomFragment newInstance(int pos) {
CustomFragment fragment = new CustomFragment();
//get whatever info you need for this page
Bundle args = getInfoSomehow(pos);
fragment.setArguments(args)
return fragment;
}
#Override
public View onCreateView(Layout inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(....
yourTextView = root.findViewById(...) //this is the text view you want to change things in
//all the stuff you're currently doing in instantiateItem()
return root;
}
private void deleteStuff() {
//whatever you need to do. But notice that here it's acting on the TextView that belongs to this particular fragment
}
private void changeColorOfStuff() {...}
...
}
Then in your instantiateItem(...)
#Override
public Object instantiateItem(ViewGroup container, int pos) {
return CustomFragment.newInstance(pos);
}

Related

textview.settext in Fragment Doesn't Work After First Call

I'm trying to update a textview in my fragment based on what a user clicks. When I first load the activity it sets the textview. But later, even though the textview.settext does get called, nothing seems to change.
In my activity I send the new variable like this:
getSupportFragmentManager().beginTransaction().add(R.id.pager1, SecondFragment.newInstance(2, "Page # 2", nonStaticRandomInfoSt), "tag").commit();
My Fragment:
public static SecondFragment newInstance(int page, String title, String randomInfo) {
SecondFragment fragmentSecond = new SecondFragment();
Bundle args = new Bundle();
args.putInt("someInt", page);
args.putString("someTitle", title);
args.putString("randomInfo", randomInfo);
fragmentSecond.setArguments(args);
return fragmentSecond;
}
// Store instance variables based on arguments passed
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
randomInfo = this.getArguments().getString("randomInfo");
}
// Inflate the view for the fragment based on layout XML
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_second, container, false);
TextView randomInfoTV = (TextView) view.findViewById(R.id.randomInfo);
randomInfoTV.setText(randomInfo); //this is getting called, but it's not changing the textview
randomInfoTV.setMovementMethod(new ScrollingMovementMethod());
return view;
}
Edit:
I have a fragment in a viewpager. When the activity is first entered, a default value is set to the textview in the fragment. From a listview in my activity layout, the user can make a selection. When an item is selected, I call the fragment manager and pass the new randomInfo variable. I want to update the textview in the fragment. I know the correct value is being passed by debugging, and I know that the textview.settext is being called as well. But for some reason, the textview remains unchanged. Do you have any suggestions as to why this is happening? I don't even know where to start in fixing it.
How I create the fragments:
public static class MyPagerAdapter1 extends FragmentPagerAdapter {
private static int NUM_ITEMS = 2;
public MyPagerAdapter1(FragmentManager fragmentManager) {
super(fragmentManager);
}
// Returns total number of pages
#Override
public int getCount() {
return NUM_ITEMS;
}
// Returns the fragment to display for that page
#Override
public Fragment getItem(int position) {
switch (position) {
case 0: // Fragment # 0 - This will show FirstFragment
return FirstFragment.newInstance(0, "Page # 1", imageURL);
case 1: // Fragment # 0 - This will show FirstFragment different title
return SecondFragment.newInstance(2, "Page # 2", randomInfoSt);
default:
return null;
}
}
Click event code;
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int position,
long id) {
// TODO Auto-generated method stub
if (!fromservice) {
Log.i(TAG, "about to start service");
//starting service code...
initFragmentVars(position); //this runs the fragmentmanager transaction
}
Set the text in public void onViewCreated(final View view, final Bundle savedInstanceState); instead of onCreateView().
The later is ok for what you're doing (findViewById()) but the binding of the data + the view, should be done after the views have been created, especially inside a Fragment.
I know this is confusing, but Android Lifecycle is a mess created by dozens of different software engineers and here we areā€¦

Android memory leak issue when using ViewPagerAdapter with nested fragments

I have a fragment, fragment A, which holds a ViewPager. The ViewPager loads different fragments which the user can swipe through "indefinitely" (I use a really high number of pages/loops to emulate this). When a user clicks on the current ViewPager fragment, then fragment A with the ViewPager is replaced by fragment B in the fragment manager. When the user returns from fragment B, the backstack is popped using popBackStackImmediate(). If the user repeats this action several times, the heap begins to fill up by about 100kb at a time until the app starts to become sloppy and malfunction as the memory fills up. I'm unsure what exactly is causing this, can anyone help?
My fragment A with the ViewPager:
public class MainFragment extends Fragment {
private MainWearActivity mMainWearActivity;
View view;
private int currentPage;
private ViewPager pager;
private ViewPagerAdapter adapter;
private LinearLayout helpIcons;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMainWearActivity = (MainWearActivity) getActivity();
adapter = new ViewPagerAdapter(this.getChildFragmentManager());
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_main, container, false);
// Scrolling menu
pager = (ViewPager) view.findViewById(R.id.watchNavPager);
pager.setAdapter(adapter);
pager.addOnPageChangeListener(adapter);
// Set current item to the middle page
pager.setCurrentItem(Consts.FIRST_PAGE);
currentPage = Consts.FIRST_PAGE;
// Set number of pages
pager.setOffscreenPageLimit(4);
// Set no margin so other pages are hidden
pager.setPageMargin(0);
return view;
}
#Override
public void onDestroyView() {
pager = null;
super.onDestroyView();
}
}
My adapter class:
public class ViewPagerAdapter extends FragmentPagerAdapter implements
ViewPager.OnPageChangeListener {
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position)
{
position = position % Consts.PAGES;
switch(position){
case Consts.AUDIO_POS:
return new AdapterAudioFragment();
case Consts.VOICE_POS:
return new AdapterVoiceFragment();
case Consts.MAIL_POS:
return new AdapterMailFragment();
case Consts.INFO_POS:
return new AdapterInfoFragment();
default:
return null;
}
}
#Override
public int getCount()
{
return Consts.PAGES * Consts.LOOPS; // (4 * 1000)
}
#Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {}
#Override
public void onPageSelected(int position) {}
#Override
public void onPageScrollStateChanged(int state) {}
}
One of my fragments that the adapter loads (they are all pretty much the same):
public class AdapterAudioFragment extends Fragment {
private ImageView menuImg;
private TextView menuText;
private LinearLayout rootView;
private MainWearActivity mMainWearActivity;
private View.OnClickListener imgClickListener;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMainWearActivity = (MainWearActivity) getActivity();
imgClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
mMainWearActivity.replaceFragment(mMainWearActivity.getFragment(Consts.FRAG_AUDIO), Consts.FRAG_AUDIO);
}
};
}
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
// Get root view of the fragment layout
rootView = (LinearLayout) inflater.inflate(
R.layout.fragment_nav_object, container, false);
// Set the current menu image and text
menuImg = (ImageView) rootView.findViewById(R.id.fragment_image);
menuImg.setImageResource(R.mipmap.ic_audio);
menuImg.setOnClickListener(imgClickListener);
menuText = (TextView) rootView.findViewById(R.id.menuTxt);
menuText.setText(Consts.MENU_HEADER_AUDIO);
// Set the current menu selection
mMainWearActivity.setCurrentSelection(Consts.AUDIO_POS);
return rootView;
}
}
I have a feeling that the adapter's fragments are all being created but never destroyed and piling up in the heap but I can't figure out how to resolve this. Do I need to call destroyItem in the adapter and manually destroy them? Any help would be most appreciated, thanks.
Adding this to Fragment stopped leaks for me:
#Override
public void onDestroyView() {
super.onDestroyView();
viewPager.setAdapter(null);
}
Looking at the source code, the problem seems to be that when calling ViewPager#setAdapter the view will register itself as observer for the adapter. So each time onViewCreated is called your pager adapter instance will have reference of the newly created view.
There is a specific PagerAdapter for your needs - FragmentStatePagerAdapter
This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.

Android Set Text of TextView in Fragment that is in FragmentPagerAdapter

This one is driving me nuts. Basically, I want to create a ViewPager and add a few Fragments to it. Then, all I want to do, it set a value in one of the Fragment's TextViews. I can add the Fragments fine, and they attach, but when I go to findViewById() for one of the TextViews in the first Fragment it throws a NullPointerException. I, for the life of me, can't figure out why.
Here's my code so far, let me know if more is needed please.
public class SheetActivity extends FragmentActivity {
// /////////////////////////////////////////////////////////////////////////
// Variable Declaration
// /////////////////////////////////////////////////////////////////////////
private ViewPager viewPager;
private PagerTitleStrip titleStrip;
private String type;
private FragmentPagerAdapter fragmentPager; //UPDATE
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sheet);
viewPager = (ViewPager) findViewById(R.id.viewPager);
titleStrip = (PagerTitleStrip) findViewById(R.id.viewPagerTitleStrip);
// Determine which type of sheet to create
Intent intent = getIntent();
this.type = intent.getStringExtra("type");
FragmentManager manager = getSupportFragmentManager();
switch (type) {
case "1":
viewPager.setAdapter(new InstallAdapter(manager));
break;
case "2":
viewPager.setAdapter(new InstallAdapter(manager));
break;
}
fragmentPager = (FragmentPagerAdapter) viewPager.getAdapter(); //UPDATE
}
#Override
public void onResume() {
super.onResume();
fragmentPager.getItem(0).setText("something"); //UPDATE
}
class MyAdapter extends FragmentPagerAdapter {
private final String[] TITLES = { "Title1", "Title2" };
private final int PAGE_COUNT = TITLES.length;
private ArrayList<Fragment> FRAGMENTS = null;
public MyAdapter(FragmentManager fm) {
super(fm);
FRAGMENTS = new ArrayList<Fragment>();
FRAGMENTS.add(new FragmentA());
FRAGMENTS.add(new FragmentB());
}
#Override
public Fragment getItem(int pos) {
return FRAGMENTS.get(pos);
}
#Override
public int getCount() {
return PAGE_COUNT;
}
#Override
public CharSequence getPageTitle(int pos) {
return TITLES[pos];
}
}
}
All of Fragments I created only have the onCreateView() method overridden so I can display the proper XML layout. Other than that they are 'stock'. Why can't I interact with elements in any of the Fragments?
UPDATE:
So do something like this?
public class FragmentA extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle inState) {
return inflater.inflate(R.layout.fragment_a, container, false);
}
public void setText(String text) {
TextView t = (TextView) getView().findViewById(R.id.someTextView); //UPDATE
t.setText(text);
}
}
XML LAYOUT FOR FRAGMENT A
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="#+id/someTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="22sp" />
</LinearLayout>
The TextView is located in the fragments layout, not in the ViewPagers or the PagerAdapter, that is causing the NPE. Now, you have 2 options.
The first is the easiest, you should simple move your code for changing the text into the corresponding fragment's class, FragmentA in this case.
Secondly, you could make the TextView into FragmentA static, so it can be accessed by other classes. So your code would look something like this:
....
TextView myText;
#Override
public View onCreateView(....) {
myLayout = ....;
myText = myLayout.findViewById(yourID);
....
}
And then you would change the text from somewhere else (if it's really necessary):
FragmentA.myText.setText("new text");
Explaining method 2
Use the following in your Fragment.
public static void setText(String text) {
TextView t = (TextView) getView().findViewById(R.id.someTextView);
t.setText(text);
}
Then change the text like:
FragmentA.setText("Lulz");
Unless you are planning to change the value at runtime, you can pass the value into the fragment as a parameter. It is done my using a Bundle and passing it as args into a Fragment, which then retrieves it from it's args. More info here. If you implement this, your instantiation of new Fragments might look something like this:
public InstallAdapter(FragmentManager fm) {
super(fm);
FRAGMENTS = new ArrayList<Fragment>();
FRAGMENTS.add(FragmentA.newInstance("<text to set to the TextView>"));
FRAGMENTS.add(FragmentB.newInstance("<text to set to the TextView>"));
}
If, however, you are planning to update the value at runtime (it will change as user is running the app), then you want to use an Interface to channell communication between your fragment and your activity. Info here. This is what it might look like:
//Declare your values for activity;
ISetTextInFragment setText;
ISetTextInFragment setText2;
...
//Add interface
public interface ISetTextInFragment{
public abstract void showText(String testToShow);
}
...
//your new InstallAdapter
public InstallAdapter(FragmentManager fm) {
super(fm);
FRAGMENTS = new ArrayList<Fragment>();
Fragment fragA = new FragmentA();
setText= (ISetTextInFragment)fragA;
FRAGMENTS.add(fragA);
Fragment fragB = new FragmentB();
setText2= (ISetTextInFragment)fragB;
FRAGMENTS.add(fragB);
}
//then, you can do this from your activity:
...
setText.showText("text to show");
...
and it will update your text view in the fragment.
While it can be done "more easily", these methods are recomended because they reduce chances of bugs and make code a lot more readable and maintainable.
EDIT: this is what your Fragment should look like (modified your code):
public class FragmentA extends Fragment implements ISetTextInFragment {
TextView myTextView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle inState) {
View v = inflater.inflate(R.layout.fragment_a, container, false);
myTextView = (TextView)v.findViewbyId(R.id.someTextView)
return v;
}
#Override
public void showText(String text) {
myTextView.setText(text);
}
}
If after that you are still getting a null pointer exception, your TextView is NOT located where it needs to me, namely in the R.layout.fragment_a filem, and it needs to be located there. Unless you are calling the interface method BEFORE the fragment finished loading, of course.
This line:
TextView t = (TextView) findViewById(R.id.someTextViewInFragmentA);
is looking for the view in your ParentActivity. Of course it wont find it and that's when you get your NPE.
Try something like this:
Add a "tag" to your fragments when you add them
Use someFragment = getSupportFragmentManager().findFragmentByTag("your_fragment_tag")
Get the view of the fragment
fragmentView = someFragment.getView();
And finally find your TextView and set the text
TextView t = (TextView) fragmentView.findViewById(R.id.someTextViewInFragmentA);
t.setText("some text");
How about to change this line
TextView t = (TextView) getView().findViewById(R.id.someTextView); //UPDATE
to
TextView t = (TextView) getActivity().findViewById(R.id.someTextView); //UPDATE
then you can try to update "t" with .setText("some_string") inside "SheetActivity".

Android Fragments in a ViewPager

I'm kinda confused about the whole Fragment-way-of-thinking. I've followed a tutorial on how to create a ViewPager with Fragments like the Google Play app.
I have TabFragment class like this one:
public class SwipeyTabFragment extends SherlockFragment {
#Override
public void onCreate(Bundle b) {
super.onCreate(b);
Log.e("FRAGMENT: ", "Hello World!");
}
public static Fragment newInstance(String title) {
SwipeyTabFragment f = new SwipeyTabFragment();
Bundle args = new Bundle();
args.putString("title", title);
f.setArguments(args);
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_swipeytab, null);
final String title = getArguments().getString("title");
((TextView) root.findViewById(R.id.text)).setText(title);
return root;
}
}
I know that the onCreateView method initialize the layout and the controlls like Button, ListView and so on.
Over to my FragmentAdapter
private class SwipeyTabsPagerAdapter extends FragmentPagerAdapter implements SwipeyTabsAdapter {
private final Context mContext;
public SwipeyTabsPagerAdapter(Context context, FragmentManager fm) {
super(fm);
this.mContext = context;
}
#Override
public Fragment getItem(int position) {
return SwipeyTabFragment.newInstance(TITLES[position]);
}
#Override
public int getCount() {
return TITLES.length;
}
public TextView getTab(final int position, SwipeyTabs root) {
TextView view = (TextView) LayoutInflater.from(mContext).inflate(R.layout.swipey_tab_indicator, root, false);
view.setText(TITLES[position]);
view.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mViewPager.setCurrentItem(position);
}
});
return view;
}
}
This will just construct a new Tab based on a String-Array, which will set the text and the header of the Fragment.
So this is where I get confused. Say for instance that I want several fragments with different layout, and different ways of interacting when the user presses on Button, Picture or whatever. How can I do so?
Thanks.
All the 'user presses button' stuff is handled in the fragments, you can call back to the Activity of course when you need to (see here).
You have to create different fragment classes for different layouts, logic. etc. and return them to the ViewPager in getItem. You could have a FirstPageFragment and a SecondPageFragment, then return them (depending on the index) in getView. This only makes sense if those fragments have different functionalities of course.
Hope it's clear what I mean ;)
EDIT: as to your comment:
I don't know what exactly you want to do, but you have your SwipeyTabFragment already defined in it's own file. Take this then, modify it, give it another layout and other functionality, then call it OtherFragment or whatever. Let's say you want to have 2 different 'pages' in your App - the getCount() method in your adapter defines the amount of 'pages' in your ViewPager, so let's let it return two.
In the getItem() method, if position is 0, let it return your SwipeyFragment, else (position is 1) let it return your new OtherFragment. Now you have a ViewPager with 2 different Fragments that can serve totally different purposes.

How to refresh current view in ViewPager

I am using ViewPager with views V1, V2, V3 ..... I am trying to set visibility of a LinearLayout used in each view, by clicking on a button. Through this code it apply the change on the next view instead of the current view. e.g. I am on V5. When I click it hides/show the object on V6. If I am going backwards from V6 to V5, then it applies the change on V4.
Here is the code:
public class FragmentStatePagerSupport extends FragmentActivity {
static final int NUM_ITEMS = 10;
MyAdapter mAdapter;
ViewPager mPager;
static int mNum;
private Button btn_zoom;
static LinearLayout LL_Head;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_pager);
mAdapter = new MyAdapter(getSupportFragmentManager());
mPager = (ViewPager)findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mPager.setCurrentItem(5);
btn_zoom = (Button)findViewById(R.id.btn_zoom);
btn_zoom.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
if (LL_Head.getVisibility() == View.VISIBLE) {
LL_Head.setVisibility(View.GONE);
}else{
LL_Head.setVisibility(View.VISIBLE);
}
}
});
.
.
.
}
public static class MyAdapter extends FragmentStatePagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
return ArrayListFragment.newInstance(position);
}
}
public static class ArrayListFragment extends ListFragment {
static ArrayListFragment newInstance(int num) {
ArrayListFragment f = new ArrayListFragment();
Bundle args = new Bundle();
args.putInt("num", num);
f.setArguments(args);
return f;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNum = getArguments() != null ? getArguments().getInt("num") : 1;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.sura_vpager, container, false);
TextView tv1=(TextView) v.findViewById(R.id.txtHead);
tv1.setText("Fragment #" + mNum);
LL_Head = (LinearLayout)v.findViewById(R.id.LL_Head);
return v;
}
Please advise
Thanks
In order to make a fluent experience the ViewPager not only loads the view you are currently looking at, but also the adjacent views. That means, that if you are scrolling from position 0 to position 1, what actually happens is that position 2 is loaded, so it will be ready when you scroll on. This is why the change is applied to the "next" view, rather than the current one (if you scroll from view 2 to 1, then view 0 is created).
Since you are setting the static LinearLayout in OnCreate, then it's only the last view to be created that is changed - and this will only ever be the one you are looking at, if you have scrolled to the end of the pager. Instead you should keep track of which fragment the user is looking at (ViewPager.setOnPageChangeListener()) and cache the fragment in your adapter. You then know which fragment position you want, and when you ask for it, you will just return the one you previously created (don't create a new one, then it won't work :)).
Or, the tl;dr version:
LL_Head is almost always set to be the next fragment, not the current one. Don't set it statically. Cache it in your PagerAdapter and reget it when you need it.
Edit:
Alternatively you may want to have the fragments listen to an event of sorts, which tells them whether they should show or hide the layout in question. Otherwise it will only be the current fragment that is affected by this, rather than all fragments.
The numbering in Java starts from 0. Thus when you want to set the 5th item, you have to call mPager.setCurrentItem(4);
Hope this helps!

Categories

Resources