Android ViewPager Exception after pressing back button - android

I have a ViewPager which contains 3 fragments which is working fine. I start an activity from my any of my fragments inside the viewpager and the activity is displayed. After that when i press the back button, my application crashes with the following excepion:
FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to resume activity {AppName.Activities/AppName.Activities.ViewPagerActivity}: java.lang.IndexOutOfBoundsException: Invalid index 2, size is 0
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2120)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2135)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:957)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:3683)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.IndexOutOfBoundsException: Invalid index 2, size is 0
at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:257)
at java.util.ArrayList.set(ArrayList.java:484)
at android.support.v4.app.FragmentStatePagerAdapter.destroyItem(FragmentStatePagerAdapter.java:97)
at android.support.v4.view.ViewPager.populate(ViewPager.java:415)
at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:271)
at android.support.v4.view.ViewPager.setCurrentItem(ViewPager.java:244)
at AppName.Activities.ViewPagerActivity.setUpView(ViewPagerActivity.java:36)
at AppName.Activities.ViewPagerActivity.onStart(ViewPagerActivity.java:28)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1129)
at android.app.Activity.performStart(Activity.java:3791)
at android.app.Activity.performRestart(Activity.java:3821)
at android.app.Activity.performResume(Activity.java:3826)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2110)
The following is my code for ViewPagerActivity that extends FragmentActivity::
public class ViewPagerActivity extends FragmentActivity
{
private ViewPager mViewPager;
private ViewPagerAdapter adapter;
boolean flag = false;
#Override
protected void onCreate(Bundle arg0)
{
super.onCreate(arg0);
setContentView(R.layout.view_pager);
}
#Override
protected void onStart()
{
super.onStart();
setUpView();
}
private void setUpView()
{
mViewPager = (ViewPager) findViewById(R.id.viewPager);
adapter = new ViewPagerAdapter(getApplicationContext(),getSupportFragmentManager());
mViewPager.setAdapter(adapter);
mViewPager.setCurrentItem(0);
}
#Override
public boolean onCreateOptionsMenu(Menu menu)
{
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case R.id.settings:
startActivity(new Intent(this, AppSettingsActivity.class));
return true;
case R.id.addSituationMenu:
Intent i = new Intent(this, MainLayout.class);
i.putExtra("parentActivity", "SplashScreenLayout");
startActivity(i);
return true;
case R.id.historyActivity:
startActivity(new Intent(this, HistoryActivity.class));
return true;
case R.id.chartActivity:
startActivity(new Intent(this, ViewPagerActivity.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
The line mViewPager.setCurrentItem(0); causes the crash.
This is the ViewPagerAdapter code:
public class ViewPagerAdapter extends FragmentStatePagerAdapter
{
private final Context context;
ArrayList<ArrayList<Object>> data;
int totalMoodEntries = 0;
static Fragment f = null;
public ViewPagerAdapter(Context mcontext, FragmentManager fm)
{
super(fm);
context = mcontext;
}
#Override
public Fragment getItem(int position)
{
switch (position)
{
case 0:
{
f = new ChartFragment(context, totalMoodEntries, data);
break;
}
case 1:
{
f = new ViewRecordsFragment(context, data);
break;
}
case 2:
{
f = new LearnMoreFragment(context);
break;
}
}
return f;
}
#Override
public int getCount()
{
return 3;
}
}

Can you try by moving following line in the onCreate() method:
mViewPager = (ViewPager) findViewById(R.id.viewPager);
adapter = new ViewPagerAdapter(getApplicationContext(),getSupportFragmentManager());
mViewPager.setAdapter(adapter);

The application is crashing because when you call the setCurrentItem(0) method of the ViewPager it tries to destroy the non-visible fragments to release memory. But at that point of the activity lifecycle, they aren't still there (hence the Invalid index 2, size is 0).
The number of fragments that it will keep alive is specified through the method setOffscreenPageLimit(), 1 by default, which means that at most 3 fragments will be retained in memory (the one it´s being shown, the previous one and the next one).
The process of creation and destruction of fragments is managed automatically by the ViewPager, and you should let it do its stuff. Besides, you are using a FragmentStatePagerAdapter, which already saves the states of the different fragments when they are destroyed, but it also makes it a little bit more complex passing information between fragments, because they don´t necessarily exist.
If you really need to programatically set the first fragment as the visible fragment, simply try calling the setCurrentItem() in onResume() instead of onStart(), and see if the fragments are already there.

The problem is that you are setting current item 0, after creating a new ViewPageAdapter and setting it to the ViewPager. Your fragments are still not created so it return a out of bounds exception.
Put a log on the getItem method, and a log before setCurrentItem, and check that your getItem is not called before the setCurrent (Your getItem is creating the fragments).
I'm not sure, but in my opinion it's not necesary to force a setCurrentItem, and the another thing is that you are creating a new fragment in each getItem call. Save them on a collection to avoid memory leaks

In my case the problem was a bit different I think. I had the same type of exception thrown by the instantiateItem() method of my PagerAdapter whenever I replaced the current Fragment I was in and then pressed the Back button to go back to the original one with the ViewPager. The exact line throwing the exception was:
((ViewPager) viewPagerContainer).addView(page, position);
in:
private class SmartViewPagerAdapter extends PagerAdapter {
// [...]
#Override
public int getCount() {
return ABOUT_VIEW_PAGER_IMAGES.length;
}
#Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == (View) arg1;
}
#Override
public Object instantiateItem(ViewGroup viewPagerContainer, int position) {
Drawable pageImage = getResources().getDrawable(ABOUT_VIEW_PAGER_IMAGES[position]);
Drawable pageTitle = getResources().getDrawable(ABOUT_VIEW_PAGER_TITLES[position]);
String pageDescription = getString(ABOUT_VIEW_PAGER_DESCRIPTIONS[position]);
SmartViewPagerPage page =
new SmartViewPagerPage(getActivity(), pageImage, pageTitle, pageDescription);
((ViewPager) viewPagerContainer).addView(page, position);
return page;
}
}
As it turned out, when you add a View to your ViewPager, you should not use the int argument of the instantiateItem() method but just use 0. Thus, the line should look like this:
((ViewPager) viewPagerContainer).addView(page, 0);
I hope this helps someone :)

Related

Interaction between fragments slow refresh

On my screen I have 2 containers - left for 3 fragments in view pager, right for single fragment. From this on the right I increment some temp variable and through inteface I pass this variable to fragments. I switch fragments in view pager and everything seems to act all right. But, there are two problems.
First is that when I start my app and try to actualize temp variable in third fragment in view pager there is NPE. And of course it should be, because at this point third fragment is not initialized (view pager only initialize actual fragment and the ones on his sides, to be able to slide between fragments). How can I initialize third fragment at the begining? I thought I can store data temporarily in second fragment and when second is initialized I can pass the data to third, but it seems not good solution.
Second question is when I initialize all fragments by sliding on it and I come back to first fragment view of third is gone (but it's initialized). When I actualize temp variable and slide to third fragment I can see this variable but after +- one second. Can someone explain me why? Without this knowledge I can't solve this problem :/
Here you have some code of mine:
public class ThirdFragment extends Fragment {
View thisView;
TextView tempTextView;
String iterator;
public AchievFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
iterator = "Start";
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
thisView = inflater.inflate(R.layout.fragment_third, container, false);
tempTextView= (TextView) thisView.findViewById(R.id.tempTextView);
tempTextView.setText(iterator);
return thisView;
}
public void getVariable(String text) {
iterator = text;
tempTextView.setText(iterator);
}
So it's third fragment class. At the beginning I initialize iterator (it's this temp variable) as a global variable to avoid npe after initializing fragments. And in onCreateView i set the view of TextView to value of this variable. Should work fine, but this +-1 sec offset makes me sad.
I think this sample of code should be enough, the rest is standard I suppose, but if anyone will be really interested I'll past the rest.
I really hope you will understand what I just wrote.
Here is the main (hosting) activity code:
public class MainActivity extends AppCompatActivity implements OnFragmentInteractionListener {
ViewPager mViewPager;
FragmentPageAdapter ft;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewPager = (ViewPager) findViewById(R.id.pager_container);
ft = new FragmentPageAdapter(getSupportFragmentManager());
mViewPager.setAdapter(ft);
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
RightFragment gameFragment = new RightFragment();
ft.replace(R.id.rightFragment_container, rightFragment,"RightFragment");
ft.commit();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}
#Override
public void OnUpdateFirst(String text) {
FirstFragment firstFragment = (FirstFragment) ft.instantiateItem(mViewPager,0);
firstFragment.getVariable(text);
}
#Override
public void OnUpdateSecond(String text) {
SecondFragment secondFragment = (SecondFragment) ft.instantiateItem(mViewPager,1);
secondFragment.getVariable(text);
}
#Override
public void OnUpdateThird(String text) {
ThirdFragment thirdFragment = (ThirdFragment) ft.instantiateItem(mViewPager,2);
thirdFragment.getVariable(text);
}
}
Fragment Page Adapter code:
public class FragmentPageAdapter extends FragmentPagerAdapter {
public FragmentPageAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int arg0) {
switch (arg0)
{
case 0:
return new FirstFragment();
case 1:
return new SecondFragment();
case 2:
return new ThirdFragment();
default:
break;
}
return null;
}
#Override
public int getCount() {
return 3;
}
Expection when I try to actualize variable on third fragment before I initialize it (in example right after I start app)
06-14 16:47:25.621 27246-27246/com.package.app E/AndroidRuntime﹕ FATAL EXCEPTION: main
java.lang.NullPointerException
at com.package.app.ThirdFragment.getVariable(ThirdFragment.java:37)
at com.package.app.MainActivity.OnUpdateThird(MainActivity.java:59)
at com.package.app.RightFragment$3.onClick(RightFragment.java:57)
at android.view.View.performClick(View.java:4101)
at android.view.View$PerformClick.run(View.java:17082)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4940)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:798)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:565)
at dalvik.system.NativeStart.main(Native Method)
As to your first question you can call setOffscreenPageLimit(2) method on your ViewPager right after initializing it. By default it is set to 1 that means that adapter creates one page which is on the screen right now and one that is next.

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);
}

ViewPagerIndicator - How to avoid reload data in fragment each time returned

I am using Jake Wharton's ViewPagerIndicator to make a swipeable tab. Currently i can make it works in my app, with 4 tabs in it. But apparently each fragment is re-create when i change page/tab. For example, if I am in tab A, change to tab C, and back to tab A, the A tab's fragment is recreate again, not just bring the old one to front of the page.
I try to follow example of the library. Here is how my code looks like :
ProfileViewPagerActivity
private static final String[] TAB_TITLES = new String[] { "Info",
"Personal Event", "Favorite", "Attending" };
TestFragmentAdapter mAdapter;
ViewPager mPager;
PageIndicator mIndicator;
int action;
User user;
#Override
protected void onCreate(Bundle arg0) {
// TODO Auto-generated method stub
super.onCreate(arg0);
setContentView(R.layout.simple_tabs);
action = getIntent().getIntExtra(Constants.LOAD_WHAT, Constants.LOAD_MY_PROFILE);
user = (User) getIntent().getSerializableExtra(Constants.USER);
mAdapter = new TestFragmentAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mIndicator = (TabPageIndicator) findViewById(R.id.indicator);
mIndicator.setViewPager(mPager);
}
and here is the adapter as a class inside ProfileViewPagerActivity
TestFragmentAdapter
private int mCount = TAB_TITLES.length;
public TestFragmentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return UserInfoFragment.newInstance(action,user);
case 1:
return EventListFragment.newInstance(user.getUsername(), Constants.LIST_MINE, action);
case 2:
return EventListFragment.newInstance(user.getUsername(), Constants.LIST_FAVORITE, action);
case 3:
return EventListFragment.newInstance(user.getUsername(), Constants.LIST_ATTENDED, action);
}
return null;
}
#Override
public int getCount() {
return mCount;
}
#Override
public CharSequence getPageTitle(int position) {
return TAB_TITLES[position].toUpperCase(Locale.US);
}
Using codes above, the fragment re-create itself each time I change tab. I try to make a different approach, such as calling newInstance() method for each fragment inside TestFragmentAdapter constructor, and then just call the fragment object i already have in the getItem method, and it still not work.
Another solution i already tried is to saveInstanceState in each fragment, but not works too. Am i missing something? Thanks in advance.
EDIT
I'm sorry, my mistake, i guess it's not "reinstantiated fragment" related question. After doing more debugging, i found that only data being reloaded again if the fragment showed in FragmentAdapter. In my fragment, i load data from web server in the OnCreateView method. From the android docs, i guess i should load data that I need in OnCreate method to solve this problem, am i right? thanks.
From Android developers website on FragmentPagerAdapter:The fragment of each page the user visits will be kept in memory, though its view hierarchy may be destroyed when not visible.
I believe android has its own mechanism to preserve the memory. If you are not convinced, you can always declare your fragments first:
private int mCount = TAB_TITLES.length;
private UserInfoFragment fragment0; // first fragment
private EventListFragment [] fragmentAt; // fragment 2,3,4
#Override
protected void onCreate(Bundle arg0) {
fragmentAt = new EventListFragment[mCount-1];
...
}
Then in your TestFragmentAdapter:
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
if(fragment0 == null) {
fragment0 = UserInfoFragment.newInstance(action,user);
}
return fragment0;
case 1:
case 2:
case 3:
if(fragmentAt[position-1] == null) {
fragmentAt[position-1] = EventListFragment.newInstance(user.getUsername(), Constants.LIST_ATTENDED, action);
}
return fragmentAt[position-1];
}
}
This way, the fragments are guaranteed to be created only once.
You could try to make your TestFragmentAdapter extends FragmentPagerAdapter.

Crash after adding item to action bar's options menu from Fragment followed by orientation change

I'm using ActionBarSherlock and ViewPagerIndicator to display Fragments as tabs. One of those Fragments adds items to ActionBar:
private String[] mapNames;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
// init an String array `mapNames` which is used when populating submenu
// ...
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_maps, menu);
SubMenu mapNamesMenu = menu.findItem(R.id.map_names).getSubMenu();
mapNamesMenu.clear();
for (int i=0; i<mapNames.length; i++) {
mapNamesMenu.add(1, i, Menu.NONE, mapNames[i]);
}
super.onCreateOptionsMenu(menu, inflater);
}
and in res/menu/fragment_maps.xml I have
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/map_names"
android:title="Maps"
android:icon="#drawable/maps_32"
android:showAsAction="always|withText">
<menu>
<item android:id="#+id/placeholder_maps" />
</menu>
</item>
</menu>
Everything is working fine until I rotate my phone. After orientation change this menu becomes inaccessible (nothing happens when icon is clicked). Then if I rotate my phone again I get this error:
FATAL EXCEPTION: main
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
at android.view.ViewRoot.setView(ViewRoot.java:532)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:177)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)
at android.view.Window$LocalWindowManager.addView(Window.java:424)
at android.widget.PopupWindow.invokePopup(PopupWindow.java:912)
at android.widget.PopupWindow.showAsDropDown(PopupWindow.java:824)
at com.actionbarsherlock.internal.widget.IcsListPopupWindow.show(IcsListPopupWindow.java:226)
at com.actionbarsherlock.internal.view.menu.MenuPopupHelper.tryShow(MenuPopupHelper.java:129)
at com.actionbarsherlock.internal.view.menu.MenuPopupHelper.show(MenuPopupHelper.java:102)
at com.actionbarsherlock.internal.view.menu.ActionMenuPresenter.onSubMenuSelected(ActionMenuPresenter.java:273)
at com.actionbarsherlock.internal.view.menu.MenuBuilder.dispatchSubMenuSelected(MenuBuilder.java:263)
at com.actionbarsherlock.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:892)
at com.actionbarsherlock.internal.view.menu.ActionMenuView.invokeItem(ActionMenuView.java:510)
at com.actionbarsherlock.internal.view.menu.ActionMenuItemView.onClick(ActionMenuItemView.java:145)
at android.view.View.performClick(View.java:2494)
at android.view.View$PerformClick.run(View.java:9122)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:130)
at android.app.ActivityThread.main(ActivityThread.java:3806)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
at dalvik.system.NativeStart.main(Native Method)
Any ideas how to solve it? I'm using Android 2.3.6
Edit: see test repository
I think , This is a Context problem. That's why BadTokenException is occurring.
There are many possibilities behind this exception:
1) May be you are using "this" as a context-reference at some place where it actually needs YourActivity.this or the parent activity's context.
OR
2) From the log-cat i am guessing, you are trying to display a Pop-up window.
The problem may be, you are displaying Pop-up window too early (i.e. before the Activity life cycle completes.).
So wait till the activity life cycle completes.
To defer showing the popup, you can refer this link.
In-short this problem is due to the below use-case:
An activity's reference is passed to the some component (i.e. like
Toast, alert dialog, pop-up etc), and activity destroyed but
that component is still alive or trying to use destroyed activity's
context.
So make sure that there isn't any situation like this.
Hope this will you give you some hint about solving the problem.
This is your MainActivity:
public class BaseSampleActivity extends SherlockFragmentActivity {
TestFragmentAdapter mAdapter;
ViewPager mPager;
PageIndicator mIndicator;
protected ListFragment mFrag;
#Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.themed_titles);
//This adapter can load as many fragment as you want with different content, see later
mAdapter = new TestFragmentAdapter(getSupportFragmentManager());
mPager = (ViewPager)findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mPager.setCurrentItem(1);
mIndicator = (TitlePageIndicator)findViewById(R.id.indicator);
mIndicator.setViewPager(mPager);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
//This show how to set up a Searhbar
SearchView searchView = new SearchView(getSupportActionBar().getThemedContext());
searchView.setQueryHint("Procure pelo nome");
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
// TODO Auto-generated method stub
// Intent search = new Intent(getApplicationContext(), SearchableActivity.class);
// search.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
// search.putExtra("query", query);
// startActivity(search);
return true;
}
#Override
public boolean onQueryTextChange(String newText) {
// TODO Auto-generated method stub
return false;
}
});
menu.add("Search")
.setIcon(R.drawable.ic_search_inverse)
.setActionView(searchView)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
//This is how to set up a SubMenu
SubMenu subMenu1 = menu.addSubMenu("Action Item");
subMenu1.add(0, 1, 0, "Sample");
subMenu1.add(0, 2, 0, "Menu");
subMenu1.add(0, 3, 0, "Sair");
MenuItem subMenu1Item = subMenu1.getItem();
subMenu1Item.setIcon(R.drawable.ic_title_share_default);
subMenu1Item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
//This is a switch case to do when the SubMenu is clicked.
case 1:
Toast.makeText(BaseSampleActivity.this, "Now "+item.getItemId(), Toast.LENGTH_SHORT).show();
return true;
case 2:
Toast.makeText(BaseSampleActivity.this, "Now = "+item.getItemId(), Toast.LENGTH_SHORT).show();
return true;
case 3:
Toast.makeText(BaseSampleActivity.this, "Now = "+item.getItemId(), Toast.LENGTH_SHORT).show();
return true;
}
return super.onOptionsItemSelected(item);
}
}
This is your FragmentPagerAdapter:
class TestFragmentAdapter extends FragmentPagerAdapter implements IconPagerAdapter {
//Here you set up the title of each fragment, its in portuguese.
protected static final String[] CONTENT = new String[] { "CATEGORIAS", "PRINCIPAL", "AS MELHORES", };
protected static final int[] ICONS = new int[] {
R.drawable.perm_group_calendar,
R.drawable.perm_group_camera,
R.drawable.perm_group_device_alarms,
};
private int mCount = CONTENT.length;
public TestFragmentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {Fragment f = null;
switch(position){
case 0:
{
//Here you can set up a lot of diferent fragment content, here I just created a simple //arraylistfragment
f = new ArrayListFragment();
// set arguments here, if required
Bundle args = new Bundle();
args.putInt(ArrayListFragment.ARG_position, position);
f.setArguments(args);
break;
}
case 1:
{
f = new ArrayListFragment();
// set arguments here, if required
Bundle args = new Bundle();
f.setArguments(args);
break;
}
case 2:
{
f = new ArrayListFragment();
// set arguments here, if required
Bundle args = new Bundle();
f.setArguments(args);
break;
}
default:
throw new IllegalArgumentException("not this many fragments: " + position);
}
return f;
}
#Override
public int getCount() {
return mCount;
}
#Override
public CharSequence getPageTitle(int position) {
return TestFragmentAdapter.CONTENT[position % CONTENT.length];
}
#Override
public int getIconResId(int index) {
return ICONS[index % ICONS.length];
}
public void setCount(int count) {
if (count > 0 && count <= 10) {
mCount = count;
notifyDataSetChanged();
}
}
}
I just done an easy sample that you can easy understand how to implements ActionBarSherlock and ViewPagerIndicator.
I´d like to upload this to github, but it will take a while to understand how to do it, maybe you can teach me later.
Then I upload this in 4shared. http://www.4shared.com/rar/zOWrvmyu/ViewpagerandSherlock.html
If you have any question, ask me later.
This worked for me but I don't know if there are some side effects.
Add this to Fragment's onCreate
setRetainInstance(true);
and this to AndroidManifest.xml to Activity containing the Fragment
android:configChanges="orientation"

Categories

Resources