Android: Activity losing link to ViewPager Fragment - android

I am having trouble with an Activity that fires off a command to a fragment in a ViewPager using a FragmentNotification interface. Everything works well until either the app is in the background for a long period of time or the orientation changes. At that point the Activity seems to lose connection to the Fragment.
My Activity code:
public class MyActivity extends FragmentActivity implements MyFragment3.FragmentNotification {
SectionsPagerAdapter mSectionsPagerAdapter;
ViewPager mViewPager;
MyFragment1 fragOne = new MyFragment1();
MyFragment2 fragTwo = new MyFragment2();
MyFragment3 fragThree = new MyFragment3();
boolean toggle = false;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
setContentView(R.layout.activity_main);
// Create the adapter that will return a fragment for each of the three primary sections
// of the app.
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setOffscreenPageLimit(2);
mViewPager.setAdapter(mSectionsPagerAdapter);
mViewPager.setClickable(true);
mViewPager.setCurrentItem(0);
}
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK)) {
if (fragThree != null) {
fragThree.doSomething();
toggle = false;
return false;
} else {
}
}
return super.onKeyDown(keyCode, event);
}
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
Fragment fragment;
if(i==0){
fragment = fragOne;
}else if(i==1){
fragment = fragTwo;
}else{
fragment = fragThree;
}
return fragment;
}
#Override
public int getCount() {
return 3;
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0: return getString(R.string.title_section1).toUpperCase();
case 1: return getString(R.string.title_section2).toUpperCase();
case 2: return getString(R.string.title_section3).toUpperCase();
}
return null;
}
}
//Receive an event notification from a fragment
// #Override
public void fragmentAction(int actionType) {
if (actionType == MyFragment3.TOGGLE_ACT) {
toggle = true;
}
}
}
My Fragment Code:
public class MyFragment3 extends Fragment {
View mView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
mView = ....
return rootView;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (FragmentNotification) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
}
}
public void doSomething(){
mView.setVisibility(View.GONE);
...
}
public interface FragmentNotification {
public void fragmentAction(int actionType);
}
}
As mentioned, everything works well until some state change, and then it appears the activity loses reference to the fragment present in the viewpager, even though it is being displayed properly until the back button is pressed.
I believe I need to restore the connection by supplying a bundle from my Fragment's onSaveInstanceState, but have no idea how to get started.
Any help would be greatly appreciated.
Thanks,
Josh

You are blindly creating instances of your three fragments, in data member initializers (!), even if those fragments already exist. Bear in mind that Android recreates all of your existing fragments on a configuration change. Hence, on a configuration change, none of those newly-created fragments will get used, as the ViewPager will use the ones Android recreated for it. You can see this in the implementation of instantiateItem() in FragmentPagerAdapter (source code is in your SDK).
The concept that "when pressing BACK I want to do something special with my third fragment in the pager" is not something that ViewPager supports all that well. I would encourage you to find some other solution to whatever problem you are trying to solve with that logic.

Related

First fragment in view pager not showing because of rest api delay

I have a navigation drawer activity, with one fragment having a view pager and tabs. All 4 fragments are fetching data from a server. My problem is that the view pager is loading the first 2 fragments therefore my first fragment doesn't show a content at first because of the delay of the rest api. So the second fragment is being created and shown before the data in the first fragment is parsed and shown. How can I solve this?
This is my fragment container
public class FragmentMoviesContainer extends KFragment {
private MainActivity activity;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_movies_container, container, false);
activity = (MainActivity) getActivity();
assert activity != null;
activity.setVisibleFragment(this);
SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(getChildFragmentManager());
// Set up the ViewPager with the sections adapter.
ViewPager mViewPager = rootView.findViewById(R.id.container);
TabLayout tabLayout = rootView.findViewById(R.id.tabs);
mViewPager.setAdapter(mSectionsPagerAdapter);
tabLayout.setupWithViewPager(mViewPager);
return rootView;
}
#Override
public void onResume() {
super.onResume();
ActionBar actionBar = activity.getSupportActionBar();
if (actionBar != null)
actionBar.setTitle(R.string.movies);
activity.getNavigationView().setCheckedItem(R.id.nav_movies);
activity.setElevation(true);
}
public class SectionsPagerAdapter extends FragmentPagerAdapter {
SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return replaceFragmentMovies(Constants.STRINGS.UPCOMING);
case 1:
return replaceFragmentMovies(Constants.STRINGS.NOW_PLAYING);
case 2:
return replaceFragmentMovies(Constants.STRINGS.POPULAR);
case 3:
return replaceFragmentMovies(Constants.STRINGS.TOP_RATED);
default:
return null;
}
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return getString(R.string.coming_soon);
case 1:
return getString(R.string.now_playing);
case 2:
return getString(R.string.popular);
case 3:
return getString(R.string.top_rated);
default:
return "";
}
}
#Override
public int getCount() {
return 4;
}
private FragmentMovies replaceFragmentMovies(String type) {
FragmentMovies fragmentMovies = new FragmentMovies();
fragmentMovies.setType(type);
return fragmentMovies;
}
}
#Override
public void serviceResponse(int responseID, List<KObject> objects) {
}
#Override
public void update(ModelService service, boolean reload) {
}
}
Here's my fragment showed in the tabs
public class FragmentMovies extends KFragment implements MoviesAdapter.OnLoadMoreListener {
private MainActivity activity;
private ModelService service;
private RecyclerView moviesRv;
private String type;
public void setType(String type) {
this.type = type;
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_movies, container, false);
activity = (MainActivity) getActivity();
if (activity != null) {
service = activity.getService();
activity.setVisibleFragment(this);
}
moviesRv = rootView.findViewById(R.id.movies_list);
moviesRv.setLayoutManager(new LinearLayoutManager(getContext()));
this.update(service, false);
return rootView;
}
#Override
public void serviceResponse(int responseID, List<KObject> objects) {
if ((objects != null && !objects.isEmpty()) && (responseID == Constants.UPCOMING || responseID == Constants.NOW_PLAYING || responseID == Constants.POPULAR
|| responseID == Constants.TOP_RATED)) {
Section section = (Section) objects.get(0);
MovieListAdapter adapter = new MovieListAdapter(getContext(), section.getMovieList());
moviesRv.setAdapter(adapter);
}
}
#Override
public void update(final ModelService service, final boolean reload) {
boolean hasConnection = Connection.isNetworkAvailable(getContext());
if (hasConnection && service != null) {
final int responseId = getResponseID();
service.getMovies(type, "", false, responseId, reload);
} else {
// progressBar.setVisibility(View.GONE);
DialogHelper.noConnectionDialog(getContext());
}
}
private int getResponseID() {
switch (type) {
case Constants.STRINGS.UPCOMING:
return Constants.UPCOMING;
case Constants.STRINGS.NOW_PLAYING:
return Constants.NOW_PLAYING;
case Constants.STRINGS.POPULAR:
return Constants.POPULAR;
case Constants.STRINGS.TOP_RATED:
return Constants.TOP_RATED;
default:
return 0;
}
}
#Override
public void onLoadMore(MoviesAdapter adapter) {
}
#Override
public void onResume() {
super.onResume();
if (activity.getSupportActionBar() != null)
activity.getSupportActionBar().setTitle("Movies");
activity.getNavigationView().setCheckedItem(R.id.nav_movies);
activity.setElevation(true);
activity.getAddFab().hide();
}
#Override
public void onDestroy() {
super.onDestroy();
}
}
The method update calls the rest api url and fetches the data. This is a framework I created based on AsyncTask. The list of objects then is returned to the fragment parsed in the method onServiceResponse where I create the adapter and show the data. The problem is that the second fragment is being created before the method onServiceResponse of the first fragment.
You should make api call from the first fragment and after getting the result you should make the rest of the calls. Let me know if you need any help with the code. I think this should be straight forward.
After Looking your code, there are Two things to inflate Fragments on to tabs.
Use single Fragment for all tabs.
Use individual fragment for every tab.
in the First case, if you are calling APIs form fragment that kind of problem occurs(As yours).
in the Second case APIs, the call will be in individual fragment and there will not be such kind of problem.
So the first solution to your problem is to use individual fragment for every tab.
And if really want to use single fragment for every tab then maintain the sequence of API calling for every instance of the fragment for every tab.
As you are doing in fragment like:
if (activity != null) {
service = activity.getService();
activity.setVisibleFragment(this);
}
moviesRv = rootView.findViewById(R.id.movies_list);
moviesRv.setLayoutManager(new LinearLayoutManager(getContext()));
this.update(service, false);
in this case you are calling service and and then you are reading setting your view.
The scenario is that here API call will be in the background but the code below API call will execute. Due to that if the response of API any fragment comes then that fragment view will be populated. So Solution of that scenario is that put your API call method in fragment and then call APIs and maintain calls.
if any help just comments. thanks.
I think the accepted answer is not very explanatory, so for anyone coming across this in future, this is what I did. I am calling my REST API from the on create method of the activity hosting the fragments and viewpager and using a single fragment class for 6 tabs by creating 6 instances of the fragment class. But the catch here is, dont set up the viewpager in onCreate, rather set it after the API call receives a successful response, after the data has been saved inside some object. So now the data is ready to be displayed within the fragment when it is first presented.
You should add this code on your one of your fragment.
Handler().postDelayed({
//api call
}, 3000)
So that two fragment can not do api call at same time when you use viewpager.

fragment reference null on resume

Im having an issue that only appears after several hours of inactivity, I researched it ive tried various ways of fixing it to no avail. The issue is after my app has been dormant for several hours the references for my fragments are null, however; they still exist in the frag manager. I use the references to pull the tag, or id by findfragmentby...() so I can call specific methods within them for updating themselves and what not. The fragments are dynamic and have a UI. I have several activities and a service that are called on by the main activity. I can close the app and resume, call activities, pull info from the service, close, use the back button, all without an issue. To give you an idea of how the app is structured...
public class appClass extends Application {
public Fragment fragmentA;
public Fragment fragmentB;
public Fragment fragmentC;
#Override
public void onCreate() {
super.onCreate();
new fragmentTemplate();
fragemntA = fragmentTemplate.newInstance(getDbName(), usefuldata, "A List");
new fragmentTemplate();
fragemntB = fragmentTemplate.newInstance(getDbName(), usefuldata, "B list");
new fragmentTemplate();
fragemntC = fragmentTemplate.newInstance(getDbName(), usefuldata, "C list");
}
}
Moving on to activity where fragments are used in a viewager...
public class mainActivity extends AppCompatActivity implements ...listeners{
appClass myAppClass;
FragmentManager FragMgr;
ViewPager viewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myAppClass = (appClass) getApplication();
setTheme(myAppClass.getAppTheme());
setContentView(R.layout.activity_my_layout);
//toolbar actionbar stuff
FragMgr = getSupportFragmentManager();
viewPager = (ViewPager) findViewById(R.id.viewpager);
viewPager.setAdapter(new ViewPagerAdapter(FragMgr));
//tab setup
}
//inner class pager adapter is here
}
This is my pager adapter
class ViewPagerAdapter extends FragmentPagerAdapter implements ViewPager.OnPageChangeListener{
Fragment fragment;
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
if (myAppClass.fragmentA != null) {
fragment = myAppClass.fragemntA ;
}
break;
case 1:
if (myAppClass.fragmentB != null) {
fragment = myAppClass.fragmentB ;
}
break;
case 2:
if (myAppClass.fragmentC != null) {
fragment = myAppClass.fragmentC ;
}
break;
}
return fragment;
}
#Override
public int getCount() {
return 3;
}
}
I have a FAB and its listener looks like this
#Override
public void onClick(View v) {
Fraggment fragment;
int i = viewPager.getCurrentItem();
if (v.getId() == floatingActionButton.getId()) {
switch (i) {
case 0:
fragment= (Fragment) FragMgr.findFragmentByTag(myAppClass.fragmentA.getTag());
fragment.addItem(fragment.getSomeString());
break;
case 1:
fragment= (Fragment) FragMgr.findFragmentByTag(myAppClass.fragmentB.getTag());
fragment.addItem(fragment.getSomeString());
break;
case 2:
fragment= (Fragment) FragMgr.findFragmentByTag(myAppClass.fragmentC.getTag());
fragment.addItem(fragment.getSomeString());
break;
}
}
}
code for a fragment
public class fragmentTemplate extends Fragment implements RecyclerAdapter.aListener {
private appClass myAppclassReference;
private RecyclerView recyclerView;
private View view;
private FragmentTitle;
public static fragmentTemplate newInstance(String a, String b, String c) {
Bundle args = new Bundle();
args.putString(KEY_A, a);
args.putString(KEY_B, b);
args.putString(KEY_C, c);
fragmentTemplate fragment = new fragmentTemplate();
fragment.setArguments(args);
return fragment;
}
public String getFragmentTitle() {
return fragmentTitle;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, final Bundle savedInstanceState) {
view = inflater.inflate(R.layout.list, container, false);
myAppclassReference= ((appClass) getActivity().getApplication());
recyclerView = (RecyclerView) view.findViewById(R.id.listView);
//get list is a local function that loads a list from a db source
RecyclerAdapter recycler = new RecyclerAdapter(getActivity(), getList());
recycler.setListener(this);
recyclerView.setAdapter(recycler);
recyclerView.setLayoutManager(newLearLayoutManager(getActivity()));
recyclerView.addOnItemTouchListener(new RecyclerTouchListener(getActivity(), recyclerView, new ClickListener() {}};
return view;
}
}
When things go wonky the app does not crash right away, the tabs still scroll, the viewpager still scrolls, but it is empty, its not until I hit the FAB do I get a nullpointerexception, trying to invoke a method on a nullpointer reference within the onClick Listener does it actually crash.
This is happening because you are messing up with the way that the Android Framework handles Fragments for you. When the ViewPagerAdapter gets Fragments from you in getItem(int), it's using the FragmentManager that you gave it to attach the Fragments. Once the Activity is killed because of low memory, the FragmentManager will automatically create new instances of your Fragments. At this point there are two copies of the fragments, the ones the FragmentManager created and the ones you recreated in your appClass.
You should never keep references to your Fragments. The FragmentManager is free to destroy them and create new ones. If you need to communicate between the Activity and the Fragments in the ViewPager, you can either make the Fragment ask its Activity for commands, use an Event Bus, or explore the sketchy solutions here.

Go from second fragment inside fragment activity to second fragment inside another activty

The scenerio of my some part of my app is as below:
The problem I'm stuck at is, I'm in the EnterChildExpFragment. I want to go to ChildDetailsFragment (which is in ChildExpensesActivity).
I can't share the whole code here. but I can tell some part of what I've been doing until now for going to ChildDetailsFragment from EnterChildExpFragment.
First I tried: ((MainActivityExpenses)getActivity()).setCurrentItem(1, true); But this shows up an error : cannot cast fragmentactivty to activity.
Then I tried :
ChildDetailsFragment childDetailsFragment = new ChildDetailsFragment();
FragmentTransaction fragTransaction=getFragmentManager().beginTransaction();
fragTransaction.replace(R.layout.child_details_fragment_layout,childDetailsFragment);
fragTransaction.addToBackStack(null);
fragTransaction.commit();
But this also shows error: No view found for id 0x7f...
Note: Pl don't suggest me to use change the design or to merge EnterChi... fragments in ChildExpenseActivity.
I've kind of made nested fragments without using parent-child concept for fragments.
More info: In ChildExpenseActivity, I've used Viewpager and loaded the fragments in getItem method of FragmentStatePagerAdapter.
AddChildFragmentActivity is a FragmentActivity and has tabs in ActionBar.
TIA!
After cracking my head and doing some "jugaad" , finally I got want I wanted.
In EnterChildExpFragment I wrote this code snippet:
MainActivityExpenses.myBoolean=true;
Intent myIntent = new Intent(viewEnterChildExp.getContext(), MainActivityExpenses.class);
myIntent.putExtra("fromEnterChildExpToMainActivityExpenses", "true");
startActivity(myIntent);
getActivity().finish();
And in ChildExpenseActivity :
public static Boolean myBoolean=false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_expenses);
viewPagerMainActivityExpenses = (ViewPager) findViewById(R.id.viewPagerExpenses);
viewPagerMainActivityExpenses.setAdapter(new MyPagerAdapter(getSupportFragmentManager()));
// try {
if (myBoolean) {
// myBoolean = getIntent().getExtras().getBoolean("fromEnterChildExpToMainActivityExpenses");
myBoolean=false;
viewPagerMainActivityExpenses.setCurrentItem(1,true);
}
/*} catch (Exception e) {
Log.e("intent error","is : "+e.toString());
}*/
}
private class MyPagerAdapter extends FragmentStatePagerAdapter {
public MyPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0: {
return ChildFragment.newInstance();
}
case 1: {
return ChildDetailsFragment.newInstance();
}
case 2: {
return SomeFragment.newInstance();
}
default: {
return SomeDefaultFragment.newInstance();
}
}
}
#Override
public int getCount() {
return 3;
}
}
public void setCurrentItem(int item, boolean smoothScroll) {
viewPagerMainActivityExpenses.setCurrentItem(item, smoothScroll);
}

Android: Replacing a fragment in a view pager

I have two fragments SearchFragment and CreateFragment in a view pager inside a activity called TicketManagementActivity. Now when the user presses the search button in SearchFragment, I want SearchFragment to be replaced with SearchResultFragment. I should then be able to swipe between SeachResultFragment and CreateFragment in the ViewPager. Also when I press back from SearchResultFragment I should go back to SearchFragment.
Right now, when I press the button I get a blank screen instead of the layout of SearchResultFragment. When I press back I get to SearchFragment but now I have to click the button twice for the blank screen to come. Now after the blank screen comes after the double click, whenever I swipe to CreateFragment tab I get a blank screen instead of CreateFragment layout.
I looked at quite a number of questions on SO but none of them seem to be working for me. Most useful seems to be the first two answers in this question, but the first answer doesn't handle the back press, nor am I able to implement it. The second answer seems very implementable but I get errors which I have mentioned below.
My main TicketManagemementActivity:
public class TicketManagementActivity extends FragmentActivity implements
ActionBar.TabListener {
ViewPager viewPager;
TabsPagerAdapter adapter;
ActionBar actionBar;
String[] tabs={"Search", "Create"};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ticket_management);
viewPager=(ViewPager)findViewById(R.id.pager);
actionBar=getActionBar();
adapter=new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(adapter);
actionBar.setHomeButtonEnabled(false);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
for(String tab_name : tabs){
actionBar.addTab(actionBar.newTab().setText(tab_name).setTabListener(this));
}
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
// on changing the page
// make respected tab selected
actionBar.setSelectedNavigationItem(position);
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
#Override
public void onPageScrollStateChanged(int arg0) {
}
});
}
//removed methods for menu creation and filling and placeholder fragment for brevity on SO
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
viewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
}
My activity_ticket_management.xml which is layout set in onCreate of ticket management activity, just contains the viewpager
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v4.view.ViewPager>
My TabsPagerAdapter class extending FragmentPagerAdapter:
public class TabsPagerAdapter extends FragmentPagerAdapter {
public TabsPagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
switch (index) {
case 0:
// Top Rated fragment activity
return new SearchFragment();
case 1:
// Games fragment activity
return new CreateFragment();
}
return null;
}
#Override
public int getCount() {
// get item count - equal to number of tabs
return 2;
}
}
Relevant part of my SearchFragment:
public class SearchFragment extends Fragment implements View.OnClickListener {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_search, container, false);
.
.//some widget initializations
.
return rootView;
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.ticket_search_btn: searchSigmaTickets();
break;
}
}
public void searchSigmaTickets(){
.
.
.
.//some operations
.
new SearchAsyncTask().execute();
}
}
private class SearchAsyncTask extends AsyncTask<Void, Void, Void>{
protected Void doInBackground(Void... params){
.
.//some more operation
.
}
protected void onPostExecute(Void param){
Fragment newFragment = new SearchResultFragment();
//Here I use getFragmentManager and not getChildFragmentManager
FragmentTransaction transaction = getFragmentManager().beginTransaction();
//HERE I try to replace the fragment. I'm not sure what id to pass, I pass the id of the main veiwpager in ticketmanagement activity
transaction.replace(R.id.pager, newFragment);
transaction.addToBackStack(null);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.commit();
}
}
}
If I use getChildFragmentManager instead of getFragmentManager as mentioned in the second answer I get
06-25 06:55:32.045: E/AndroidRuntime(2797): java.lang.IllegalArgumentException: No view found for id 0x7f06003c (com.amberroad.sigmaticket:id/pager) for fragment SearchResultFragment{b2fed358 #0 id=0x7f06003c}
Sorry for the lengthy question, how should I solve this?
Kartik, get ready for a lengthy answer to your lenghty question. Replacing fragments in a viewpager is quite involved but is very possible and can look super slick. First, you need to let the viewpager itself handle the removing and adding of the fragments. What is happening is when you replace the fragment inside of SearchFragment, your viewpager retains its fragment views. So you end up with a blank page because the SearchFragment gets removed when you try to replace it.
The solution is to create a listener inside of your viewpager that will handle changes made outside of it so first add this code to the bottom of your adapter.
public interface nextFragmentListener {
public void fragment0Changed(String newFragmentIdentification);
}
Then you need to create a private class in your viewpager that becomes a listener for when you want to change your fragment. For example you could add something like this. Notice that it implements the interface that was just created. So whenever you call this method, it will run the code inside of the class below.
private final class fragmentChangeListener implements nextFragmentListener {
#Override
public void fragment0Changed(String fragment) {
//I will explain the purpose of fragment0 in a moment
fragment0 = fragment;
manager.beginTransaction().remove(fragAt0).commit();
switch (fragment){
case "searchFragment":
fragAt0 = SearchFragment.newInstance(listener);
break;
case "searchResultFragment":
fragAt0 = Fragment_Table.newInstance(listener);
break;
}
notifyDataSetChanged();
}
There are two main things to point out here: 1)fragAt0 is a "flexible" fragment. It can take on whatever fragment type you give it. This allows it to become your best friend in changing the fragment at position 0 to the fragment you desire. 2) Notice the listeners that are placed in the 'newInstance(listener)constructor. These are how you will callfragment0Changed(String newFragmentIdentification)`. The following code shows how you create the listener inside of your fragment.
static nextFragmentListener listenerSearch;
public static Fragment_Journals newInstance(nextFragmentListener listener){
listenerSearch = listener;
return new Fragment_Journals();
}
You could then call the change inside of your onPostExecute
private class SearchAsyncTask extends AsyncTask<Void, Void, Void>{
protected Void doInBackground(Void... params){
.
.//some more operation
.
}
protected void onPostExecute(Void param){
listenerSearch.fragment0Changed("searchResultFragment");
}
}
This would trigger the code inside of your viewpager to switch your fragment at position zero fragAt0 to become a new searchResultFragment. There are two more small pieces you would need to add to the viewpager before it became functional.
One would be in the getItem override method of the viewpager.
#Override
public Fragment getItem(int index) {
switch (index) {
case 0:
//this is where it will "remember" which fragment you have just selected. the key is to set a static String fragment at the top of your page that will hold the position that you had just selected.
if(fragAt0 == null){
switch(fragment0){
case "searchFragment":
fragAt0 = FragmentSearch.newInstance(listener);
break;
case "searchResultsFragment":
fragAt0 = FragmentSearchResults.newInstance(listener);
break;
}
}
return fragAt0;
case 1:
// Games fragment activity
return new CreateFragment();
}
Now without this final piece you would still get a blank page. Kind of lame, but it is an essential part of the viewPager. You must override the getItemPosition method of the viewpager. Ordinarily this method will return POSITION_UNCHANGED which tells the viewpager to keep everything the same and so getItem will never get called to place the new fragment on the page. Here's an example of something you could do
public int getItemPosition(Object object)
{
//object is the current fragment displayed at position 0.
if(object instanceof SearchFragment && fragAt0 instanceof SearchResultFragment){
return POSITION_NONE;
//this condition is for when you press back
}else if{(object instanceof SearchResultFragment && fragAt0 instanceof SearchFragment){
return POSITION_NONE;
}
return POSITION_UNCHANGED
}
Like I said, the code gets very involved, but you basically have to create a custom adapter for your situation. The things I mentioned will make it possible to change the fragment. It will likely take a long time to soak everything in so I would be patient, but it will all make sense. It is totally worth taking the time because it can make a really slick looking application.
Here's the nugget for handling the back button. You put this inside your MainActivity
public void onBackPressed() {
if(mViewPager.getCurrentItem() == 0) {
if(pagerAdapter.getItem(0) instanceof FragmentSearchResults){
((FragmentSearchResults) pagerAdapter.getItem(0)).backPressed();
}else if (pagerAdapter.getItem(0) instanceof FragmentSearch) {
finish();
}
}
}
You will need to create a method called backPressed() inside of FragmentSearchResults that calls fragment0changed. This in tandem with the code I showed before will handle pressing the back button. Good luck with your code to change the viewpager. It takes a lot of work, and as far as I have found, there aren't any quick adaptations. Like I said, you are basically creating a custom viewpager adapter, and letting it handle all of the necessary changes using listeners
Here is the code all together for the TabsPagerAdapter.
public class TabsPagerAdapter extends FragmentPagerAdapter{
Fragment fragAt0;
fragmentChangeListener listener = new fragmentChangeListener();
FragmentManager manager;
static String fragment0 = "SearchFragment";
//when you declare the viewpager in your adapter, pass it the fragment manager.
public viewPager(FragmentManager fm) {
super(fm);
manager = fm;
}
private final class fragmentChangeListener implements nextFragmentListener {
#Override
public void fragment0Changed(String fragment) {
//I will explain the purpose of fragment0 in a moment
fragment0 = fragment;
manager.beginTransaction().remove(fragAt0).commit();
switch (fragment){
case "searchFragment":
fragAt0 = SearchFragment.newInstance(listener);
break;
case "searchResultFragment":
fragAt0 = Fragment_Table.newInstance(listener);
break;
}
notifyDataSetChanged();
}
}
#Override
public Fragment getItem(int index) {
switch (index) {
case 0:
//this is where it will "remember" which fragment you have just selected. the key is to set a static String fragment at the top of your page that will hold the position that you had just selected.
if(fragAt0 == null){
switch(fragment0){
case "searchFragment":
fragAt0 = FragmentSearch.newInstance(listener);
break;
case "searchResultsFragment":
fragAt0 = FragmentSearchResults.newInstance(listener);
break;
}
}
return fragAt0;
case 1:
// Games fragment activity
return new CreateFragment();
}
#Override
public int getCount() {
// Show 3 total pages.
return 3;
}
#Override
public CharSequence getPageTitle(int position) {
Locale l = Locale.getDefault();
String[] tab = {"Journals", "Charts", "Website"};
switch (position) {
case 0:
return tab[0].toUpperCase(l);
case 1:
return tab[1].toUpperCase(l);
case 2:
return tab[2].toUpperCase(l);
}
return null;
}
public int getItemPosition(Object object)
{
//object is the current fragment displayed at position 0.
if(object instanceof SearchFragment && fragAt0 instanceof SearchResultFragment){
return POSITION_NONE;
//this condition is for when you press back
}else if{(object instanceof SearchResultFragment && fragAt0 instanceof SearchFragment){
return POSITION_NONE;
}
return POSITION_UNCHANGED
}
public interface nextFragmentListener {
public void fragment0Changed(String fragment);
}

Using backstack and back button in viewpager

I'm using a viewpager to swipe between fragments and would like the back button to navigate to the previously viewed fragment rather than ending the activity. Sorry if this is a duplicate of this question however I didn't find the answer very helpful. Obviously onBackPressed needs to be overridden, but I don't know how to get and display the correct fragment. I assume that I should use the fragmentmanager's backstack, but getSupportFragmentManager().getBackStackEntryCount() always returns 0. Do I need to manually add fragments to the backstack using FragmentTransaction.addToBackStack()? If so, where would I add this in my adapter?
Here is the code for my activity,
public class PagerActivity extends FragmentActivity {
ArrayList<Sale> sales;
MyAdapter mAdapter;
ViewPager mPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent it = getIntent();
this.sales = (ArrayList<Sale>) it.getExtras().get("sales");
int position = it.getExtras().getInt("position");
ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.fragment_pager);
mAdapter = new MyAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mPager.setCurrentItem(position);
}
public class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
#Override
public int getCount() {
return sales.size();
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
}
#Override
public Fragment getItem(int position) {
SalesThumbFragment frag = new SalesThumbFragment();
return frag.newInstance(sales.get(position));
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.sales_controller, menu);
return true;
}
#Override
public void onBackPressed() {
if(getSupportFragmentManager().getBackStackEntryCount() != 0) {
getSupportFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
}
In new design support library, i use this
I have same issue and i follow this step
In the main activity where there are 3 fragment in viewpager i create stack
and push and pop data.
//private Stack<Integer> stackkk; ==> As i get edit suggestion
private Stack<Integer> stackkk = new Stack<>(); // Edited
private ViewPager mPager;
private int tabPosition = 0;
mTabLayout.setupWithViewPager(mPager);
mPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout));
mTabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
tabPosition = tab.getPosition();
mPager.setCurrentItem(tab.getPosition());
if (stackkk.empty())
stackkk.push(0);
if (stackkk.contains(tabPosition)) {
stackkk.remove(stackkk.indexOf(tabPosition));
stackkk.push(tabPosition);
} else {
stackkk.push(tabPosition);
}
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
tabPositionUnselected = tab.getPosition();
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
and in the onBackPressed in activity,
#Override
public void onBackPressed() {
if (stackkk.size() > 1) {
stackkk.pop();
mPager.setCurrentItem(stackkk.lastElement());
} else {
}
}
Use this code in Fragment Activity class. Don't forget to add return true;
public boolean onKeyDown(int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
if ((keyCode == KeyEvent.KEYCODE_BACK)) {
mViewPager.setCurrentItem(viewPageSelected - 1);
return true;
}
return super.onKeyDown(keyCode, event);
}
I had a similar problem, this is how I solved it. I think you can adapt the code to your problem, if I understood what you problem is. I had a ViewPager with 6 fragments and wanted to keep track of the page history and to be able to use the back button to navigate backwards in the history. I create a java.util.Stack<Integer> object, add fragment numbers to it (except when you use the back button, see below), and override onBackPressed() to make it pop the last viewed fragment instead of using the back stack, when my history stack is not empty.
You want to avoid pushing elements on the Stack when you press the back button, otherwise you will get stuck between two fragments if you keep using the back button, instead of eventually exiting.
My code:
MyAdapter mAdapter;
ViewPager mPager;
Stack<Integer> pageHistory;
int currentPage;
boolean saveToHistory;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAdapter = new MyAdapter(getSupportFragmentManager());
mPager = (ViewPager)findViewById(R.id.container);
mPager.setAdapter(mAdapter);
mPager.setOffscreenPageLimit(5);
pageHistory = new Stack<Integer>();
mPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int arg0) {
if(saveToHistory)
pageHistory.push(Integer.valueOf(currentPage));
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
#Override
public void onPageScrollStateChanged(int arg0) {
}
});
saveToHistory = true;
}
#Override
public void onBackPressed() {
if(pageHistory.empty())
super.onBackPressed();
else {
saveToHistory = false;
mPager.setCurrentItem(pageHistory.pop().intValue());
saveToHistory = true;
}
};
If you use a field to keep track of the index of the previous page using mPager.getCurrentItem() after each time the user navigates to a new fragment, then in the onBackPressed() method, you should be able to call mPager.setCurrentItem(previousPage)
Or, if the user can only page in order, then you don't need a field at all, and you could just do mPager.setCurrentItem(mPager.getCurrentItem()-1)
I've made custom ViewPager and implement stack functionality in it.
public class CustomViewPager extends ViewPager {
private Stack<Integer> stack = new Stack<>();
#Override
public void setCurrentItem(int item, boolean smoothScroll) {
stack.push(getCurrentItem());
super.setCurrentItem(item, smoothScroll);
}
public int popFromBackStack(boolean smoothScroll) {
if (stack.size()>0) {
super.setCurrentItem(stack.pop(), smoothScroll);
return getCurrentItem();
} else return -1;
}

Categories

Resources