I am using PagerSlidingTabs. I have two fragment and each fragment has one listview. I added TabListener, because i want to go first item in listview when user reselect the same tab. This is working only on the second fragment(tab). Problem is second listview's smoothScrollToPosition(0) method working but first listview's smoothScrollToPosition(0) method(all methods) not working. First listview is showing properly on screen but it's id seems to second listview's id.
PagerAdapter:
public class PagerAdapter extends FragmentPagerAdapter {
private Context mContext;
private int mPageCount;
public PagerAdapter(Context context, FragmentManager fm, int pageCount) {
super(fm);
this.mContext = context;
this.mPageCount = pageCount;
}
#Override
public int getCount() {
return mPageCount;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return Fragment1.newInstance(position);
case 1:
return Fragment2.newInstance(position);
default:
return null;
}
}}
Fragment1:
public class Fragment1 extends Fragment {
private static final String ARG_POSITION = "position";
private ListView mListView1;
private ListAdapter mAdapter;
public static Fragment1 newInstance(int position) {
Fragment1 f = new Fragment1();
Bundle b = new Bundle();
b.putInt(ARG_POSITION, position);
f.setArguments(b);
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (mRootView == null) {
mRootView = inflater.inflate(R.layout.fragment1, container, false);
mListView1 = (ListView) mRootView.findViewById(R.id.listview1);
.
.
if (mAdapter == null) {
mAdapter = new ListAdapter(getActivity(), mListView1, R.layout.list_item, mDatas);
mListView1.setAdapter(mAdapter);
} else {
mAdapter.notifyDataSetChanged();
}
.
.
((HomeActivity) getActivity()).getPagerSlidingTabStrip().setOnTabListener(new TabListener() {
#Override
public void onTabReselected(View tab, int postion) {
mListView1.smoothScrollToPosition(0);
//mListView1's id seems to mListView2's id
//when reselect first tab, application acting like i reselect second tab.
}
});
} else {
((ViewGroup) mRootView.getParent()).removeView(mRootView);
}
return mRootView;
}}
Fragment2:
public class Fragment2 extends Fragment {
private static final String ARG_POSITION = "position";
private ListView mListView2;
private ListAdapter mAdapter;
public static Fragment2 newInstance(int position) {
Fragment2 f = new Fragment2();
Bundle b = new Bundle();
b.putInt(ARG_POSITION, position);
f.setArguments(b);
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (mRootView == null) {
mRootView = inflater.inflate(R.layout.fragment2, container, false);
mListView2 = (ListView) mRootView.findViewById(R.id.listview2);
.
.
if (mAdapter == null) {
mAdapter = new ListAdapter(getActivity(), mListView2, R.layout.list_item, mDatas);
mListView2.setAdapter(mAdapter);
} else {
mAdapter.notifyDataSetChanged();
}
.
.
((HomeActivity) getActivity()).getPagerSlidingTabStrip().setOnTabListener(new TabListener() {
#Override
public void onTabReselected(View tab, int postion) {
mListView2.smoothScrollToPosition(0); //this line is working fine
}
});
} else {
((ViewGroup) mRootView.getParent()).removeView(mRootView);
}
return mRootView;
}}
Fragment1 and Fragment2 have only listview(id:listview1, id:listview2).
HomeActivity have PagerSlidingTabs view and viewpager.
How can i fix this problem?
When you set the listener from the second fragment, that replaces the listener from the first. That's why all the calls seem to be going to the second tab: its listener was the last one added, so it's receiving all the callbacks. One potential solution is to declare an interface, let's call it OnTabReselctedListener, that has a method onTabReselected(into position). Have your activity maintain a list of those interfaces, have your fragments implement that interface, and register those fragments as listeners in the activity. When the activity receives a reselection event, it can pass that to its children fragments; those fragments check if the position matches their own position and scroll to the top if they do. I hope that made sense; I'm writing this from my phone so I can't easily wrote sample code. If you want me to write some, let me know!
Related
I am trying to refresh the current fragment in the tabbed layout of my viewpager. I have one fragment (UserProgressFragment) that creates the ViewPager and sets the adapter (UserProgressAdapter) along with creating tabbed layout.
In my UserProgressAdapter, in the getItem() method I am returning two other fragments, (UserWeightTrackerFragment and UserCalorieCounterFragment) based on which tab i am on.
My issue is how do i refresh the fragment and update its content/view from the UserCalorieCounterFragment on a button click, because access to the viewpager and adapter are set in the UserProgressFragment? I have considered notifyDataChange for the adapter but i dont know how to call this from this class as it is set up on the UserProgressFragment class. The purpose of wanting to refresh this page is to update a specific view which is a chart, if context is needed.
I have attached the code below:
UserProgressFragment
public class UserProgressFragment extends Fragment{
public static UserProgressFragment newInstance() {
return new UserProgressFragment();
}
public UserProgressFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_user_progress, container, false);
ViewPager viewPager = view.findViewById(R.id.progress_viewpager);
viewPager.setAdapter(new UserProgressTabsAdapter(getChildFragmentManager()));
TabLayout tabLayout = view.findViewById(R.id.progress_sliding_tabs);
tabLayout.setupWithViewPager(viewPager, true);
return view;
}
}
UserProgressTabsAdapter
public class UserProgressTabsAdapter extends FragmentStatePagerAdapter {
private static final int PAGE_COUNT = 2;
private String tabTitles[] = new String[]{"Weight Tracker", "Calorie Counter"};
public UserProgressTabsAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return UserWeightTrackerFragment.newInstance(position);
case 1:
return UserCalorieCounterFragment.newInstance(position + 1);
}
return null;
}
#Override
public int getCount() {
return PAGE_COUNT;
}
#Override
public CharSequence getPageTitle(int position) {
// Generate title based on item position
return tabTitles[position];
}
UserCalorieCounterFragment (need to refresh this one)
public class UserCalorieCounterFragment extends Fragment implements View.OnClickListener, AdapterView.OnItemSelectedListener {
public static final String ARG_PAGE = "ARG_PAGE";
public static UserCalorieCounterFragment newInstance(int page) {
Bundle args = new Bundle();
args.putInt(ARG_PAGE, page);
UserCalorieCounterFragment fragment = new UserCalorieCounterFragment();
fragment.setArguments(args);
return fragment;
}
public UserCalorieCounterFragment() {
// Required empty public constructor
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int mPage = getArguments().getInt(ARG_PAGE);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_user_calorie_counter, container, false);
Button mAddCalories = view.findViewById(R.id.btn_add_calorie);
mAddCalories.setOnClickListener(this);
return view;
}
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_add_calorie:
//REFRESH/UPDATE HERE
break;
}
}
You can create a public method in your fragment which contains the view pager which will contain the notifyDataSetChanged() function. And in the view pager fragment/item, you can call getParentFragment() and type cast it to your parent/first fragment and access that public method to notifyDataSetChanged().
I have an activity that show data stored in database in a view pager organized in fragments.
When the adapter init the View Pager it call a method in each fragment to pass the id of the database entry to retrive and show:
public Fragment getItem(int position) {
switch (position) {
case 0: // Fragment # 0 - This will show FirstFragment different title
firstFragment = Fragment.newInstance(1, "Page # 1");
firstFragment.setID(ctx, id);
return firstFragment;
case 1: // Fragment # 0 - This will show FirstFragment different title
secondFragment = Fragment.newInstance(1, "Page # 2");
secondFragment.setID(ctx, id);
return secondFragment;
case 2: // Fragment # 0 - This will show FirstFragment
thirdtFragment = Fragment.newInstance(1, "Page # 3");
thirdtFragment.setID(context, id);
return thirdtFragment;
default:
return null;
}
}
when the method onCreateView of the fragment get called it retrive the data from the database and init all the view displaying the data to the user:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
DataManager dataManager = new DataManager(mContext);
DataModel data = dataManager.getData(mID);
.....
All seems to work fine except when the user restart the app that was for example paused when inside this activity: when the activity got restarted I get a fatal error accessing the data, as if the private variable (of the fragment) containing the ID of the data got deleted from the garbage collector.
Is my approach correct? How can I prevent the error generated when the activity get restarted?
During onPause and onResume Fragment and Activity is managed by android so you may not retrieve the member variables so to overcome this problem on your Fragment's newInstance method use something like
public static Fragment newInstance(int id, String page){
Bundle bundle = new Bundle();
bundle.putString("page", page);
bundle.putInt("ID", page);
setArguments(bundle);
}
and on your onCreateView callback use something like
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
int mID = getArguments().getInt("ID");
DataManager dataManager = new DataManager(mContext);
DataModel data = dataManager.getData(mID);
...
So, I approached my problem by doing the following:
Create a view pager that has a list of Fragments and their respective titles. This view pager adapter should have a method to add fragments to the list. Here is my code:
public class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
private int mItemId;
public ViewPagerAdapter(FragmentManager manager, int id) {
super(manager);
mItemId = id;
}
#Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
#Override
public int getCount() {
return mFragmentList.size();
}
public void addFrag(Fragment fragment, String title) {
Bundle bundle = new Bundle();
bundle.putInt("itemId", mItemId);
fragment.setArguments(bundle);
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}}
Now, inside your Fragment, you can extract the bundle extra which can help you when reading some data from your database for example if it is an id for table column. This could be your fragment class:
public class InfoFragment extends Fragment {
private Activity mActivity;
private CustomerDbObject customer;
private int customerId;
public InfoFragment() {
// Required empty public constructor
}
#Override
public void onAttach(Context ctx){
super.onAttach(ctx);
if (ctx instanceof Activity){
this.mActivity = (Activity) ctx;
}
customerId = getArguments().getInt("itemId");
Realm realm = Realm.getInstance(ctx);
RealmQuery<Customer> query = realm.where(Customer.class);
query = query.equalTo("account_id", customerId);
customer = query.findFirst();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.customer_fragment_info, container, false);
ButterKnife.bind(this, view);
return view;
}}
With these two pieces in place, what we need now is our activity that will display the viewpager with tabs! Remember, it is inside this same activity that we provide the extra (itemId) to the viewpager instance then the viewpager will pass it to the fragment in that order.
public class CustomerActivity extends AppCompatActivity {
private int mSelectedCustomer = 5; //this value could be extracted for an intent that is passed from a Recyclerviewer etc
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_customer_details);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
final ViewPager mViewPager = (ViewPager) findViewById(R.id.viewPager);
setupViewPager(mViewPager);
TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
tabLayout.setupWithViewPager(mViewPager);
tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
mViewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
private void setupViewPager(ViewPager viewPager) {
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager(), mSelectedCustomer);
adapter.addFrag(new InfoFragment(), getString(R.string.info_tab));
}
}
Now that we have all the pieces together, the important extra (data) being passed around should always be available to the Fragment through the viewpager adapter which gets it subsequently from the activity.
When I tested this, it worked and I hope it works for you as well. Please let me know how it goes!!
I'm having an issue with the support library ViewPager. That ViewPager lives inside a fragment and it's composed of 3 tabs: one that will show some information, the second and third show a list of elements (so they both are beeing generated from the same fragment). When I scroll from the first to the second one everything works fine but if I try to scroll from the second one to the third something happens to the fragments and they don't show up again even the PagerTabStrip disappears when this happens. Also I tryed using the same type of fragment for the 3 tabs (the one that disappeared with the list)and everything seems to work fine, so I'm quite bugged about this. Also, the only log on the console related to the issue is this one:
W/FragmentManager: moveToState: Fragment state for StoreListFragment{3fb980e7 #2 id=0x7f0d0080 android:switcher:2131558528:2} not updated inline; expected state 3 found 2
This is the code for my parent Fragment:
public class StoreFragment extends Fragment {
#Bind(R.id.pager) ViewPager mPager;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_store, container, false);
ButterKnife.bind(this, v);
setupViewPager();
return v;
}
private void setupViewPager() {
String[] titles = {getString(R.string.store_information),
getString(R.string.store_offers),
getString(R.string.store_products)
};
mPager.setAdapter(new StoresPagerAdapter(getChildFragmentManager(), titles));
}
}
This is the code for the PagerAdapter:
public class StoresPagerAdapter extends FragmentPagerAdapter {
private String[] mPageTitles;
public StoresPagerAdapter(FragmentManager fm, String[] titles) {
super(fm);
mPageTitles = titles;
}
#Override
public Fragment getItem(int position) {
switch (position){
case 0:
return StoreInfoFragment.newInstance();
//return StoreListFragment.newInstance(position);
case 1:
return StoreListFragment.newInstance(position);
case 2:
return StoreListFragment.newInstance(position);
default:
return null;
}
}
#Override
public int getCount() {
return mPageTitles != null ? mPageTitles.length : 0;
}
#Override
public CharSequence getPageTitle(int position) {
return mPageTitles[position];
}
}
And the one for the Fragment:
public class StoreListFragment extends Fragment implements MyListListener {
#Bind(R.id.store_list) RecyclerView mStoreContentView;
private ArrayList<StoreModel> mStoreContents = new ArrayList<>();
public static StoreListFragment newInstance(int page) {
return new StoreListFragment();
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mStoreContents.add(new Schedule());
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_store_list, container, false);
ButterKnife.bind(this, v);
return v;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setupList();
}
protected void setupList() {
LinearLayoutManager layoutManager = new LinearLayoutManager(this.getActivity());
mStoreContentView.setHasFixedSize(true);
mStoreContentView.setLayoutManager(layoutManager);
mStoreContentView.setAdapter(new ArrayAdapter(this, mStoreContents, R.layout.list_elem_locations));
}
#Override
public void onClickElement(int elementId, String elementName) {
Intent i = new Intent(this.getActivity(), DetailActivity.class);
startActivity(i);
}
}
I think your problem is here:
public static StoreListFragment newInstance(int page) {
return new StoreListFragment();
}
You're not using this page anywhere and you're getting two completely same StoreListFragments. You should add:
Bundle args = new Bundle();
args.putInt("key", page);
fragment.setArguments(args);
And then use this differentiation somewhere.
I'm building an Android application using a FragmentStatePagerAdapter for tabbed navigation and dynamic content in each tab. Each tab has Fragment with content which is to be replaced upon user input (for example, the first tab has a Fragment containing a list of books, and upon clicking, you can access detailed information of the book, which is displayed using another Fragment
Problem: I haven't find a way of correctly handling the onBack events nor the BackStack, so when I'm reviewing any book's details, I can easily go back pressing the back button - I mean, popping the last state from the BackStack.
What I suspect: The way I'm switching Fragment objects may not the the best one, but except for the back button issue, it is working just as I want. I suspect some problem between the FragmentStatePagerAdapter's adapter, and the FragmentManager's own collection of Fragments; probably this is something with an easy solution I didn't see.
Unaswered question (not very detailed though): Adding Fragment to BackStack using FragmentStatePagerAdapter
The code:
// MAIN ACTIVITY, Just this simple.
public class MainActivity extends FragmentActivity {
public static final String TAG = "MainActivity";
// Whether the Log Fragment is currently shown
private boolean mLogShown;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
MainTabSliderFragment fragment = new MainTabSliderFragment();
transaction.replace(R.id.sample_content_fragment, fragment);
transaction.commit();
}
}
}
.
// THE SLIDE TAB FRAGMENT, which becomes the parent view of the tabs.
public class MainTabSliderFragment extends Fragment {
static final String LOG_TAG = MainTabSliderFragment.class.getSimpleName();
private SlidingTabLayout mSlidingTabLayout;
private ViewPager mViewPager;
private CustomFragmentStatePageAdapter cfspAdapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_sample, container, false);
return root;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
mViewPager = (ViewPager) view.findViewById(R.id.viewpager);
cfspAdapter = new CustomFragmentStatePageAdapter(getFragmentManager());
List<String> pageTitles = new ArrayList<>();
pageTitles.add(getString(R.string.page_one));
pageTitles.add(getString(R.string.page_two));
pageTitles.add(getString(R.string.page_three));
List<Fragment> pageFragments = new ArrayList<>();
final BookListPageFragment pageOne = BookListPageFragment.newInstance(new CustomFragmentStatePageAdapter.SwitchFragmentListener() {
#Override
public void onSwitchFragments(Class<? extends Fragment> clazz, Map<String, String> ... args) {
cfspAdapter.switchFragment(CustomFragmentStatePageAdapter.PagePosition.POSITION_PAGE_ONE, clazz, this, args);
}
});
CustomerPageFragment pageTwo = CustomerPageFragment.newInstance(...);
ForumPageFragment pageThree = ForumPageFragment.newInstance(...);
pageFragments.add(pageOne);
pageFragments.add(pageTwo);
pageFragments.add(pageThree);
cfspAdapter.addFragments(pageFragments, pageTitles);
mViewPager.setAdapter(cfspAdapter);
mSlidingTabLayout = (SlidingTabLayout) view.findViewById(R.id.sliding_tabs);
mSlidingTabLayout.setViewPager(mViewPager);
}
}
.
// THE FIRST TAB, In its initial state (the initial fragment).
public class BookListPageFragment extends Fragment {
private static final String TAG = BookListPageFragment.class.getSimpleName();
private BookListAdapter bAdapter;
private static CustomFragmentStatePageAdapter.SwitchFragmentListener switchFragmentListener;
public static BookListPageFragment newInstance(CustomFragmentStatePageAdapter.SwitchFragmentListener _switchFragmentListener) {
switchFragmentListener = _switchFragmentListener;
BookListPageFragment f = new BookListPageFragment();
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.page_one_booklist, container, false);
final ListView lv = (ListView) v.findViewById(R.id.list);
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
BookRowData bRow = (BookRowData) lv.getItemAtPosition(position);
Log.i(TAG, "Clicked on book " + bRow.getBookId());
Map<String, String> param = new HashMap<>();
param.put("book_id", Long.toString(bRow.getBookId()));
switchFragmentListener.onSwitchFragments(ReviewBookPageFragment.class, new Map[]{param});
}
});
initializeTestList(v, lv); // Just add some books to the list.
return v;
}
.
// THE PAGE ADAPTER, used for handling tab's Fragment switching.
public class CustomFragmentStatePageAdapter extends FragmentStatePagerAdapter {
private final static String TAG = FragmentStatePagerAdapter.class.getSimpleName();
private FragmentManager fragmentManager;
private List<Fragment> fragmentList = new ArrayList<>();
private List<String> tabTitleList = new ArrayList<>();
public CustomFragmentStatePageAdapter(FragmentManager fm) {
super(fm);
fragmentManager = fm;
}
public void addFragments(List<Fragment> fragments, List<String> titles) {
fragmentList.clear();
tabTitleList.clear();
fragmentList.addAll(fragments);
tabTitleList.addAll(titles);
notifyDataSetChanged();
}
#Override
public int getItemPosition(Object object) {
if (fragmentList.contains(object)) {
return POSITION_UNCHANGED;
}
return POSITION_NONE;
}
#Override
public Fragment getItem(int item) {
if (item >= fragmentList.size()) {
return null;
}
return fragmentList.get(item);
}
#Override
public int getCount() {
return fragmentList.size();
}
#Override
public CharSequence getPageTitle(int position) {
return tabTitleList.get(position);
}
/**
* Switching pages
*
* #param newFragment
*/
public void switchFragment(final PagePosition position, Class<? extends Fragment> newFragment, SwitchFragmentListener sfListener, Map<String, String> ... args) {
final Fragment old = fragmentList.get(position.getPagePosition());
fragmentManager.beginTransaction().remove(old).commit(); //FIRST VERSION: IF HITTING BACK, IT EXITS APP AT ONCE.
//fragmentManager.beginTransaction().addToBackStack("page_one").remove(old).commit(); //SECOND VERSION: NOW I NEED TO HIT BACK TWICE TO EXIT, BUT THE VIEW DOESN'T CHANGE AFTER HITTING THE FIRST TIME.
try {
Fragment f = (Fragment) newFragment.asSubclass(Fragment.class).getMethod("newInstance", SwitchFragmentListener.class, Map[].class).invoke(newFragment, new Object[]{sfListener, args});
fragmentList.set(position.getPagePosition(), f);
} catch (IllegalAccessException iae) {
Log.e(TAG, "Fragment class access exception");
} catch (NoSuchMethodException e) {
Log.e(TAG, "Fragment instantiation exception (reflection)");
} catch (InvocationTargetException e) {
Log.e(TAG, "Fragment instantiation exception (reflection: no public constructor)");
}
notifyDataSetChanged();
}
public interface SwitchFragmentListener {
void onSwitchFragments(Class<? extends Fragment> clazz, Map<String, String> ... args);
}
public enum PagePosition {
POSITION_PAGE_ONE (0),
POSITION_PAGE_TWO (1),
POSITION_PAGE_THREE (2);
private final int position;
PagePosition(int position) {
this.position = position;
}
public int getPagePosition() {
return this.position;
}
}
}
.
// AND FINALLY THE FRAGMENT I WANT TO GO BACK FROM; this is the book review Fragment, which is displayed also in the first tab when clicking on a book from the list. Second and third tabs are ommitted.
public class ReviewBookPageFragment extends Fragment {
private static final String TAG = ReviewBookPageFragment.class.getSimpleName();
private CommentsListAdapter cAdapter;
private Long bookId;
private static CustomFragmentStatePageAdapter.SwitchFragmentListener switchFragmentListener;
public static ReviewBookPageFragment newInstance() {
ReviewBookPageFragment f = new ReviewBookPageFragment();
return f;
}
public static ReviewBookPageFragment newInstance(CustomFragmentStatePageAdapter.SwitchFragmentListener _sfListener, Map<String, String> ... args) {
switchFragmentListener = _sfListener;
Bundle b = BundlePacker.packMaps(args); // Custom util class for packing the params into a bundle.
ReviewBookPageFragment f = new ReviewBookPageFragment();
f.setArguments(b);
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.page_review_book, container, false);
Bundle bookIdBundle = this.getArguments();
Long bId = Long.parseLong(bookIdBundle.getString("book_id"));
Log.i(TAG, "Book ID: " + bId);
initializeTestList(v); // Just fill the book's reviews with test data.
return v;
}
}
So, that's the bunch of code. The idea, as a summary, is to switch from the books list view (shown on tab one), to the book's reviews when tapping on any book from the list; the reviews are also shown on the first tab, and I want to go back to the books list when pressing back. Currently, it closes the application hitting back ONCE, and if I add the transaction to the backstack (see my CustomFragmentStatePageAdapter), TWICE (but the view doesn't change after hitting back the first time.
Any help with the issue will be greatly appreciated.
For fixing the popback issue you can use this code in your activity class,
#Override
public void onBackPressed() {
// if there is a fragment and the back stack of this fragment is not empty,
// then emulate 'onBackPressed' behaviour, because in default, it is not working
FragmentManager fm = getSupportFragmentManager();
for (Fragment frag : fm.getFragments()) {
if (frag.isVisible()) {
FragmentManager childFm = frag.getChildFragmentManager();
if (childFm.getBackStackEntryCount() > 0) {
childFm.popBackStack();
return;
}
}
}
super.onBackPressed();
}
I did somethink like this:
private View _view;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(_view==null){
_view = inflater.inflate(R.layout.page_review_book, container, false);
// your_code
}
return _view;
}
I created a ViewPager that uses individual Fragments. There are 3, here is an example of one of them:
public class PainFragment extends Fragment {
private TextView mTxtScale;
private Button mBtnMinus;
private Button mBtnPlus;
private int mScale;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_pain, container, false);
mTxtScale = (TextView)v.findViewById(R.id.scale);
mBtnMinus = (Button)v.findViewById(R.id.minus);
mBtnPlus = (Button)v.findViewById(R.id.plus);
mScale = Integer.valueOf(mTxtScale.getText().toString());
mBtnMinus.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mScale--;
if(mScale == -1) {
mScale = 9;
}
mTxtScale.setText(String.valueOf(mScale));
}
});
mBtnPlus.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mScale++;
if(mScale == 10) {
mScale = 0;
}
mTxtScale.setText(String.valueOf(mScale));
}
});
return v;
}
public static PainFragment newInstance(String text) {
PainFragment f = new PainFragment();
Bundle b = new Bundle();
//b.putString("msg", text);
f.setArguments(b);
return f;
}
public int getScale() {
int scale = Integer.valueOf(mTxtScale.getText().toString());
return scale;
}
And I instantiated the ViewPager in my MainFragment:
public class MainFragment extends Fragment {
Entry mEntry = new Entry();
ViewPager mPager;
JournalPagerAdapter mAdapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main_screen, container, false);
mPager = (ViewPager)rootView.findViewById(R.id.pager);
mPager.setOffscreenPageLimit(2); // So all 3 pages are loaded at once.
mAdapter = new JournalPagerAdapter(getActivity().getSupportFragmentManager());
mPager.setAdapter(mAdapter);
...
I have button click listeners in the ViewPager Fragments. I would like to know the best way to set up a listener so that my main fragment can detect when a button is pressed on one of the ViewPager fragments.
/** Update - Here is my adapter class **/
public class JournalPagerAdapter extends FragmentPagerAdapter {
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
public JournalPagerAdapter(FragmentManager mgr) {
super(mgr);
}
#Override
public Fragment getItem(int pos) {
switch(pos) {
case 0: return PainFragment.newInstance("PainFragment");
case 1: return StressFragment.newInstance("StressFragment");
case 2: return SleepFragment.newInstance("SleepFragment");
default: return PainFragment.newInstance("PainFragment");
}
}
#Override
public int getCount() {
return 3;
}
/* Thanks to Streets of Boston (http://stackoverflow.com/questions/8785221/retrieve-a-fragment-from-a-viewpager)
* for the next 3 methods, should include in all PagerAdapters. Let's you get the fragment instances by position */
#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);
}
}
Second answer with pseudo example of using a ClickListener instead of the Callback. This should let you keep all logic out of the Activity.
Implement OnClickListener interface in MainFragment. Add a OnClickListener to your JournalPagerAdapter constructor. Presumably the Adapter is creating the PainFragments. Add OnClickListener to PainFragment newInstance and have the Adapter provide it when it creates each PainFragment.
public class PainFragment extends Fragment {
private TextView mTxtScale;
private Button mBtnMinus;
private Button mBtnPlus;
private int mScale;
protected OnClickListener mainClickListener;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_pain, container, false);
mTxtScale = (TextView)v.findViewById(R.id.scale);
mBtnMinus = (Button)v.findViewById(R.id.minus);
mBtnPlus = (Button)v.findViewById(R.id.plus);
mScale = Integer.valueOf(mTxtScale.getText().toString());
mBtnMinus.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mScale--;
if(mScale == -1) {
mScale = 9;
}
mTxtScale.setText(String.valueOf(mScale));
mainClickListener.onClick(view);
}
});
mBtnPlus.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mScale++;
if(mScale == 10) {
mScale = 0;
}
mTxtScale.setText(String.valueOf(mScale));
mainClickListener.onClick(view);
}
});
return v;
}
public static PainFragment newInstance(String text, OnClickListener onClickListener) {
PainFragment f = new PainFragment();
f.mainClickListener = onClickListener;
Bundle b = new Bundle();
//b.putString("msg", text);
f.setArguments(b);
return f;
}
public int getScale() {
public class MainFragment extends Fragment implements OnClickListener {
Entry mEntry = new Entry();
ViewPager mPager;
JournalPagerAdapter mAdapter;
#Override
public void onClick(View v)
{
// TODO Auto-generated method stub
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main_screen, container, false);
mPager = (ViewPager)rootView.findViewById(R.id.pager);
mPager.setOffscreenPageLimit(2); // So all 3 pages are loaded at once.
mAdapter = new JournalPagerAdapter(getActivity().getSupportFragmentManager(), this);
mPager.setAdapter(mAdapter);
...
public class JournalPagerAdapter extends FragmentPagerAdapter {
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
private OnClickListener mOnClickListener;
public JournalPagerAdapter(FragmentManager mgr, OnClickListener onClickListener) {
super(mgr);
mOnClickListener = onClickListener;
}
#Override
public Fragment getItem(int pos) {
switch(pos) {
case 0: return PainFragment.newInstance("PainFragment", mOnClickListener);
case 1: return StressFragment.newInstance("StressFragment", mOnClickListener);
case 2: return SleepFragment.newInstance("SleepFragment", mOnClickListener);
default: return PainFragment.newInstance("PainFragment", mOnClickListener);
}
}
...
The proper way to achieve this is probably through the use of a callback. Your fragment would utilize a normal click listener which would then use a callback to communicate back to the hosting Activity.
See the Android docs regarding communicating from a Fragment back to the Activity; then the Activity can communicate it to other Fragments.
http://developer.android.com/guide/components/fragments.html#CommunicatingWithActivity
In some cases, you might need a fragment to share events with the activity. A good way to do that is to define a callback interface inside the fragment and require that the host activity implement it. When the activity receives a callback through the interface, it can share the information with other fragments in the layout as necessary.