I am not going to list every reference I've read through before posting this, but I have extensively read similar questions on stackoverflow and all of the android developer docs on activity/fragment lifecycles and maintaining state and have not found a solution.
Here is my scenario:
I have a main activity (ImpulseActivity) that uses a FragmentPagerAdapter. Each fragment displays a separate list of data that I am retrieving from the server. When pressing an action_item in ImpulseActivity's actionbar, you can filter data sent from the server. To accomplish this, ImpulseActivity launches a separate activity (FilterEventsActivity). FilterEventsActivity lists ImpulseActivity as it's parent activity. When pressing the back button on FilterEventsActivity, ImpulseActivity is recreated (OnCreate called) with a null (Bundle savedInstanceState). For testing purposes, I am overriding OnSaveInstanceState and placing fake data in outState. Note that this happens for every activity launched from ImpulseActivity.
My question is:
What is the proper design paradigm to prevent each fragment from needing to reload data in this particular situation? I would prefer not to use the Singleton pattern since my fragments are reused in other activities.
Relevant source code if needed:
public class ImpulseActivity extends FragmentActivity implements
ActionBar.TabListener {
private MapSearchFragment mSearchFragment;
private BulletinFragment mBulletinFragment;
SectionsPagerAdapter mSectionsPagerAdapter;
ViewPager mViewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.impulse_activity);
Log.v("ImpulseActivity", "onCreate " + savedInstanceState);
final ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
CategoryManager manager = CategoryManager.getManager();
manager.setListener(this);
manager.loadCategories();
mSectionsPagerAdapter = new SectionsPagerAdapter(
getSupportFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
mViewPager
.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
actionBar.setSelectedNavigationItem(position);
}
});
for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
actionBar.addTab(actionBar.newTab()
.setText(mSectionsPagerAdapter.getPageTitle(i))
.setTabListener(this));
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putDouble("Hello", 1.02);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.impulse_activity_actions, menu);
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_create) {
return true;
}
if (id == R.id.action_filter) {
Intent intent = new Intent(this, FilterEventsActivity.class);
startActivity(intent);
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
int position = tab.getPosition();
mViewPager.setCurrentItem(position, true);
}
#Override
public void onEventListClick(Event e) {
Intent mIntent = new Intent(this, EventActivity.class);
Bundle mBundle = new Bundle();
mBundle.putSerializable(EventDetailsFragment.Event_Key, e);
mIntent.putExtras(mBundle);
startActivity(mIntent);
}
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Fragment fragment;
switch(position) {
case 0:
if (mBulletinFragment != null) {
fragment = mBulletinFragment;
} else {
fragment = new BulletinFragment();
mBulletinFragment = (BulletinFragment) fragment;
}
break;
case 1:
fragment = new MapSearchFragment();
mSearchFragment = (MapSearchFragment) fragment;
break;
case 2:
fragment = new MyEventsFragment();
break;
default:
fragment = new BulletinFragment();
break;
}
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
// Show 3 total pages.
return 3;
}
#Override
public CharSequence getPageTitle(int position) {
Locale l = Locale.getDefault();
switch (position) {
case 0:
return "Bulletin";
case 1:
return "Map";
case 2:
return "My Events";
default:
return "Test";
}
}
}
}
AndroidManifest.xml
<activity
android:name=".Home.ImpulseActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".Event.EventActivity"
android:label="#string/title_activity_event"
android:parentActivityName=".Home.ImpulseActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".Home.ImpulseActivity" />
</activity>
Although the question mentions Fragments, the problem is actually with up navigation.
The default implementation for up navigation doesn't work exactly as one would expect for standard activties. In particular, when the parent activity has launchMode="standard" (the default), pressing the up button will create a new instance of it, not return to the previous one.
There are two alternatives for solving this problem:
Changing the launchMode of ImpulseActivity to singleTop in the Manifest.
Overriding the home button action to launch the intent with the FLAG_ACTIVITY_CLEAR_TOP flag. For example, in EventActivity.onOptionsItemSelected():
if (id == android.R.id.home)
{
Intent intent = NavUtils.getParentActivityIntent(this);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent);
return true;
}
Either of these will bring your old activity to the top of the stack.
if your fragments have layout just like activity example mainlayout.xml
you can use intent for open each fragment with same details and dont use onstop(); pack create intent and intent flags in the main fragment activity
Related
I have a MainActivity that controls four fragments, each of which is a tab. When my main activity starts, I have a line being printed to the log to show me what fragment is being instantiated. Here is my FragmentPagerAdapter:
public class TabsPagerAdapter extends FragmentPagerAdapter {
public TabsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
switch (index) {
case 0:
System.out.println("Returning new PrearrivalPlan()");
return new PrearrivalPlan();
case 1:
System.out.println("Returning new PrimarySurvey()");
return new PrimarySurvey();
case 2:
System.out.println("Returning new SecondarySurvey()");
return new SecondarySurvey();
case 3:
System.out.println("Returning new PrepareForTravel()");
return new PrepareForTravel();
}
return null;
}
#Override
public int getCount() {
// get item count - equal to number of tabs
return 4;
}
}
The tab bar has the following options in order:
Prearrival Plan | Primary Survey | Secondary Survey | Prepare for Travel
When my main activity starts, the following is printed to the screen:
Returning new PrearrivalPlan()
Returning new PrimarySurvey()
What it seems to be doing is loading one tab ahead of the one I have selected. Since PrearrivalPlan is the first tab, I would think it should just return a new PrearrivalPlan() except it returns both. Another example, when I click on Primary Survey tab (second tab), the following is printed to the screen:
Returning new SecondarySurvey() // <- This is the third tab!?
Because PrimarySurvey was already instantiated when the activity first started (see output above), it jumped ahead just like it did before and loaded the third tab even though I only clicked on the second.
Here is my MainActivity:
public class MainActivity extends FragmentActivity implements ActionBar.TabListener {
private CustomViewPager viewPager;
private TabsPagerAdapter mAdapter;
private ActionBar actionBar;
private String[] tabTitles = {"Pre-arrival Plan", "Primary Survey", "Secondary Survey", "Prepare for Travel"};
#TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager = (CustomViewPager) findViewById(R.id.pager);
actionBar = getActionBar();
mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapter);
viewPager.setPagingEnabled(false);
actionBar.setHomeButtonEnabled(true);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
viewPager.setOnPageChangeListener(new CustomViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
actionBar.setSelectedNavigationItem(position);
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
// Remove Android icon from Action Bar
getActionBar().setIcon(new ColorDrawable(getResources().getColor(android.R.color.transparent)));
getActionBar().setDisplayShowHomeEnabled(false);
// Add tabs to Action Bar
for (String tab_name : tabTitles) {
actionBar.addTab(actionBar.newTab().setText(tab_name).setTabListener(this));
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
public void updateTabTitles(int tabNumber, int checkBoxesRemaining) {
String text = tabTitles[tabNumber] + " \n (" + checkBoxesRemaining + ")";
actionBar.getTabAt(tabNumber).setText(text);
}
#Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
viewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
#Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Intent intent = new Intent(this, MainMenu.class);
startActivity(intent);
return true;
case R.id.complete:
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
switch (which){
case DialogInterface.BUTTON_POSITIVE:
goToReport();
break;
case DialogInterface.BUTTON_NEGATIVE:
//No button clicked
break;
}
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Are you sure you want to complete the checklist?").setPositiveButton("Yes", dialogClickListener).setNegativeButton("No", dialogClickListener).show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public void goToReport() {
Intent intent = new Intent(this, Report.class);
startActivity(intent);
}
It is the expected behaviour of ViewPager. ViewPager always keeps one tab ahead and behind in memory in order to show slider animation. If you don't you have the next or previous tab (or fragment) in memory, trying to initiate the fragment during the slider transition will cause a performance lag or at the worse you will have the sliding fragment to be empty and get loaded later.
ViewPager also allows you to set page offset limit through setOffscreenPageLimit() which will control amount fragment to keep them in memory. By default, this is 1 which means one fragment ahead and behind will always be there in memory
I have an app that I'm working on that uses the Twitter API. I have a list view of tweets inside of a Navigation Drawer activity that I added with Android Studio. I have another activity that I use for a settings activity, and on that activity the user can select the theme.
The problem I'm having is that when the user changes the setting and presses the back button, the theme of the container is changed, but not the fragment containing the list view. It's not until I restart the app that the proper theme is applied to the fragment.
However, if I use the back button on the action bar (up navigation) the theme is applied right away.
I'd like to come up with a way to apply the theme again right away when the user presses the back button (And the theme has actually been toggled), and so far this is what I've got:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch(requestCode) {
case RETURN_TO_TWEET_LIST:
if (resultCode == RESULT_OK) {
Bundle bundle = data.getExtras();
if (bundle != null) {
boolean themeChanged = bundle.getBoolean(AppConstants.Strings.THEME_CHANGED);
if (themeChanged) {
refreshFragment();
}
}
}
}
super.onActivityResult(requestCode, resultCode, data);
}
private void refreshFragment() {
//TODO: Figure out how to get the list view to refresh when pressing back
}
I feel like the problem is not my Fragment code, as it works the way I would expect in one case, but no the other.
Here is the Navigation Drawer class:
public class CategoryViewSelector extends BaseActivity
implements NavigationDrawerFragment.NavigationDrawerCallbacks {
/**
* Fragment managing the behaviors, interactions and presentation of the navigation drawer.
*/
private NavigationDrawerFragment mNavigationDrawerFragment;
private TwitterFeedFragment currentFragment;
private String fragmentName = "CUSTOM_FRAGMENT";
private CategoryManager categoryManager;
private final int RETURN_TO_TWEET_LIST = 2;
private SettingsManager settingsManager;
/**
* Used to store the last screen title. For use in {#link #restoreActionBar()}.
*/
private CharSequence mTitle;
private String[] drawerItems;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
categoryManager = new CategoryManager();
setContentView(R.layout.activity_category_view_selector);
mNavigationDrawerFragment = (NavigationDrawerFragment)
getSupportFragmentManager().findFragmentById(R.id.navigation_drawer);
mTitle = getTitle();
// Set up the drawer.
mNavigationDrawerFragment.setUp(
R.id.navigation_drawer,
(DrawerLayout) findViewById(R.id.drawer_layout));
}
#Override
protected void onResume() {
super.onResume();
}
#Override
public void onNavigationDrawerItemSelected(int position) {
//create a fragment object palceholder
TwitterFeedFragment fragment = null;
// update the main content by replacing fragments
//create a bundle object that will be used to pass arguments to the fragment
//such as a category id.
Bundle bundle = new Bundle();
switch(position) {
case 0:
//load all the tweets by default.
bundle.putLong("categoryId", position);
fragment = new TwitterFeedFragment();
fragment.setArguments(bundle);
setCurrentFragment(fragment);
break;
case 1:
Intent intent = new Intent(this, CategoryManagerView.class);
//may have to change this to be for result again, so that you can use the onActivityResult method
//which will prevent refreshing of the fragment.
startActivityForResult(intent, RETURN_TO_TWEET_LIST);
break;
case 2:
Toast.makeText(getApplicationContext(), "Help not yet available", Toast.LENGTH_LONG).show();
break;
default:
openFragmentForCategory(position);
break;
}
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.container, currentFragment, fragmentName)
.commitAllowingStateLoss();
}
private void openFragmentForCategory(int position) {
List<Category> categories = categoryManager.getAllCategories();
//there are three default items in the list right now.
//figure out a better way to handle this. Maybe an app constant.
int index = position - 3;
Category selectedCategory = categories.get(index);
long Id = selectedCategory.getId();
Bundle bundle = new Bundle();
TwitterFeedFragment fragment = null;
bundle.putLong("categoryId", Id);
bundle.putString("categoryName", selectedCategory.getCategoryName());
fragment = new TwitterFeedFragment();
fragment.setArguments(bundle);
setCurrentFragment(fragment);
}
/**
* This method attaches a title to the fragment window when an item is clicked. This will also
* require the use of the category names from the category manager.
* #param number
*/
public void onSectionAttached(int number) {
switch (number) {
case 1:
mTitle = getString(R.string.text_category_default_feed);
break;
case 2:
mTitle = "";
break;
case 3:
mTitle = getString(R.string.text_category_help);
break;
}
}
public void restoreActionBar() {
ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setTitle(mTitle);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
if (!mNavigationDrawerFragment.isDrawerOpen()) {
// Only show items in the action bar relevant to this screen
// if the drawer is not showing. Otherwise, let the drawer
// decide what to show in the action bar.
getMenuInflater().inflate(R.menu.menu_category_view_selector, menu);
restoreActionBar();
return true;
}
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch(id) {
case R.id.action_settings:
Intent intent = new Intent(this, SettingsActivity.class);
startActivityForResult(intent, RETURN_TO_TWEET_LIST);
break;
case R.id.action_refresh:
currentFragment.refreshFeed();
break;
case R.id.action_logout:
TwitterApplication.getRestClient().clearAccessToken();
onBackPressed();
break;
}
return super.onOptionsItemSelected(item);
}
/**
* This is required so that the refreshFeed method of any fragment can be called.
*
* In the future it might make more sense to make an interface.
* #param currentFragment
*/
public void setCurrentFragment(TwitterFeedFragment currentFragment) {
this.currentFragment = currentFragment;
}
#Override
public void onBackPressed() {
Intent intent = new Intent();
setResult(RESULT_OK, intent);
super.onBackPressed();
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch(requestCode) {
case RETURN_TO_TWEET_LIST:
if (resultCode == RESULT_OK) {
Bundle bundle = data.getExtras();
if (bundle != null) {
boolean themeChanged = bundle.getBoolean(AppConstants.Strings.THEME_CHANGED);
if (themeChanged) {
refreshFragment();
}
}
}
}
super.onActivityResult(requestCode, resultCode, data);
}
private void refreshFragment() {
//TODO: Figure out how to get the list view to refresh when pressing back
}
}
Does anyone have suggestions on what I should be doing differently that would allow the fragment to have the correct theme when the back button is pressed? Please let me know if you would like to see more code, or have any questions.
I have tried detaching and attaching the fragment using the fragment manager, but I guess the theme of the fragment is never changed that way.
I'm also open to other suggestions for code improvement when they are spotted.
Thank you very much for any help!
You have to set an Activity's theme in onCreate() before calling setContentView(). And the fragment's theme is set during onCreateView().
What do you mean when you say the container changes theme, but not the fragment? I'm guessing this is what happens ...
1) you go into the SettingsActivity to change theme
2) The host Activity goes through onStop/onCreate, meaning it gets re-themed
3) but you have setRetainInstance(true) on the Fragment, meaning it does not get recreated.
If so, you have two options: remove setRetainInstance, or manually create a completely new Fragment (not just detach and re-attach) when you see that the theme has changed.
A quick fix would be to detach then re-attach the fragment.
Otherwise you could call invalidate() on the specific views of your fragment.
Before I post, I tried to read many topics about restore app state and tried it but it does not solve my problem.
My Class is:
public class MainActivity extends ActionBarActivity
implements NavigationDrawerFragment.NavigationDrawerCallbacks {
private static boolean lv11 = true;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNavigationDrawerFragment = (NavigationDrawerFragment)
getSupportFragmentManager().findFragmentById(R.id.navigation_drawer);
mTitle = getTitle();
// Set up the drawer.
mNavigationDrawerFragment.setUp(
R.id.navigation_drawer,
(DrawerLayout) findViewById(R.id.drawer_layout));
}
#Override
public void onNavigationDrawerItemSelected(int position) {
// update the main content by replacing fragments
FragmentManager fragmentManager = getSupportFragmentManager();
if(position == 0)
{
fragmentManager.beginTransaction()
.replace(R.id.container, PlaceholderFragment.newInstance(position + 1))
.commit();
}
else if(position == 1)
{
fragmentManager.beginTransaction()
.replace(R.id.container, PlaceProfile.newInstance(position + 1))
.commit();
}
else if(position == 2)
{
fragmentManager.beginTransaction()
.replace(R.id.container, PlaceLogBook.newInstance(position + 1))
.commit();
}
else if(position == 3)
{
fragmentManager.beginTransaction()
.replace(R.id.container, PlaceBeverages.newInstance(position + 1))
.commit();
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(resultCode == RESULT_OK) {
boolean finInfo = data.getBooleanExtra("Done", false);
System.out.println("OnActivityResult = " + finInfo);
if (finInfo == true) {
readIt.setBackgroundColor(R.drawable.abc_ab_bottom_solid_light_holo);
if (readIt.getText() == info1.getText()) {
lv11 = false;
}
}
}
}
public void onSectionAttached(int number) {
switch (number) {
case 1:
mTitle = getString(R.string.title_section1);
break;
case 2:
mTitle = getString(R.string.title_section2);
break;
case 3:
mTitle = getString(R.string.title_section3);
break;
case 4:
mTitle = getString(R.string.title_section4);
break;
}
}
public void restoreActionBar() {
ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setTitle(mTitle);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
if (!mNavigationDrawerFragment.isDrawerOpen()) {
// Only show items in the action bar relevant to this screen
// if the drawer is not showing. Otherwise, let the drawer
// decide what to show in the action bar.
getMenuInflater().inflate(R.menu.main, menu);
restoreActionBar();
return true;
}
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
public static class PlaceholderFragment extends Fragment {
/**
* The fragment argument representing the section number for this
* fragment.
*/
private static final String ARG_SECTION_NUMBER = "section_number";
/**
* Returns a new instance of this fragment for the given section
* number.
*/
public static PlaceholderFragment newInstance(int sectionNumber) {
PlaceholderFragment fragment = new PlaceholderFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
return fragment;
}
public PlaceholderFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
InitializeItem();
if(savedInstanceState != null) {
Log.v("","on Creat View");
lv11 = savedInstanceState.getBoolean("Level1");
if (lv11 == false) {
info1.setBackgroundColor(R.drawable.abc_ab_bottom_solid_light_holo);
}
}
//where i create my view ...
return rootView;
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
Log.v("TAG", "In frag's on save instance state ");
savedInstanceState.putBoolean("Level1",false);
super.onSaveInstanceState(savedInstanceState);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
((MainActivity) activity).onSectionAttached(
getArguments().getInt(ARG_SECTION_NUMBER));
}
}
All are working fine but my problem is using onSaveInstantState() method.I used Log.v to check and it called when I press recent button on hardware then I kill it.After that when I run the app again my SaveInstantstate is null.In my case I want to change button color by check boolean that put parameter in save state and restore it back on onCreateView.
I want to know:
what is wrong in my implementation and why I can't call restore my state
How to implement it in the right way
*all code above are in the same class file
Thank you,and sorry for my grammar.
After you killed your application, all you Bundles are destroyed. The onSavedInstanceState() is used to protect state of app from being killed by the OS.
In your case you need to save the state permanently.
The "SharedPreferences" is ideal for that.
The application has a main activity (MainActivity.java) with three tabs (fragments). I can navigate between them using the swipe left (riht) or clicking on a specific tab.
Upon starting the application, the 1st fragment is shown.
If I go to the 2nd fragment from the 1st fragment and then back to the 1st fragment, nothing happens (onResume() of the 1rd fragment isn't called), so it doesn't refresh it's content.
If I go to the 3rd fragment from the 1st fragment and then directly back to the 1st fragment, the onCreateView() of fragment1 is created and it's onResume() is called, which is correct.
If I go from the 3rd fragment to the 2nd fragment, the onCreateView() and onResume() of fragment1 are called, but not the onCreateView of fragment2.
I guess the logic in MainActivity isn't right, so I would kindly ask someone to take a look and tell me what could be wrong.
MainActivity.java:
public class MainActivity extends FragmentActivity implements ActionBar.TabListener {
CollectionPagerAdapter mCollectionPagerAdapter;
public TTSocket socket;
DBHandler db;
public String logged_user;
private LogedinPerson person;
ViewPager mViewPager;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Bundle extras = getIntent().getExtras();
logged_user = extras.getString("logged_user");
socket = TTSocket.getInstance();
socket.currentRef = this;
db = new DBHandler(this);
person=db.getLogedInPerson();
socket.dbHandler=db;
socket.person=person;
if(!socket.isInit){
String typeInitStr = "{\"Type\":\"Init\", \"UserId\":\""+ person.getUserId() +"\"}";
socket.Send(typeInitStr);
}
mCollectionPagerAdapter = new CollectionPagerAdapter(getSupportFragmentManager());
// Set up action bar.
final ActionBar actionBar = getActionBar();
// Specify that we will be displaying tabs in the action bar.
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// Set up the ViewPager, attaching the adapter and setting up a listener
// for when the
// user swipes between sections.
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mCollectionPagerAdapter);
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
Log.d("TEST",position+"");
// the corresponding tab.
// We can also use ActionBar.Tab#select() to do this if
// we have a reference to the Tab
actionBar.setSelectedNavigationItem(position);
}
});
// For each of the sections in the app, add a tab to the action bar.
for (int i = 0; i < mCollectionPagerAdapter.getCount(); i++) {
// Create a tab with text corresponding to the page title defined by
// the adapter.
// Also specify this Activity object, which implements the
// TabListener interface, as the
// listener for when this tab is selected.
if(i == 0){
actionBar.addTab(actionBar.newTab()
.setIcon(R.drawable.messages)
.setTabListener(this));
}else if(i == 1){
actionBar.addTab(actionBar.newTab()
.setIcon(R.drawable.contacts)
.setTabListener(this));
}else{
actionBar.addTab(actionBar.newTab()
.setIcon(R.drawable.history)
.setTabListener(this));
}
}
}
#Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
socket.currentRef = this;
socket.dbHandler=db;
socket.person=person;
//mCollectionPagerAdapter.notifyDataSetChanged();
}
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
}
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
// When the given tab is selected, switch to the corresponding page in
// the ViewPager.
mViewPager.setCurrentItem(tab.getPosition());
}
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
}
/**
* A {#link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the primary sections of the app.
*/
public class CollectionPagerAdapter extends FragmentPagerAdapter {
final int NUM_ITEMS = 3; // number of tabs
public CollectionPagerAdapter(FragmentManager fm) {
super(fm);
}
/*
#Override
public Fragment getItem(int i) {
Fragment fragment = new TabFragment();
Bundle args = new Bundle();
args.putInt(TabFragment.ARG_OBJECT, i);
fragment.setArguments(args);
return fragment;
}
*/
#Override
public Fragment getItem(int position) {
Fragment fragment = new Fragment();
Bundle args = new Bundle();
args.putInt(TabFragment.ARG_OBJECT, position);
switch (position) {
case 0:
Log.i("Fragment", "0");
fragment = new Tab1Fragment();
fragment.setArguments(args);
return fragment;
case 1:
Log.i("Fragment", "1");
fragment = new Tab2Fragment();
fragment.setArguments(args);
return fragment;
case 2:
Log.i("Fragment", "2");
fragment = new Tab3Fragment();
fragment.setArguments(args);
return fragment;
default:
break;
}
return fragment;
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public CharSequence getPageTitle(int position) {
String tabLabel = null;
switch (position) {
case 0:
tabLabel = getString(R.string.label1);
break;
case 1:
tabLabel = getString(R.string.label2);
break;
case 2:
tabLabel = getString(R.string.label3);
break;
}
return tabLabel;
}
}
/**
* A fragment that launches other parts of the demo application.
*/
public static class TabFragment extends Fragment {
public static final String ARG_OBJECT = "object";
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Bundle args = getArguments();
int position = args.getInt(ARG_OBJECT);
int tabLayout = 0;
switch (position) {
case 0:
tabLayout = R.layout.tab1;
break;
case 1:
tabLayout = R.layout.tab2;
break;
case 2:
tabLayout = R.layout.tab3;
break;
}
View rootView = inflater.inflate(tabLayout, container, false);
return rootView;
}
}
}
Strange onPause(), onResume() behaviour() in fragments
It's not strange behaviour but native behaviour of ActionSherlock. This kind of behaviour is used for caching -> optimalisation for older devices with lower RAM this is reason why fragments are cached.
If you need to update content of fragment don't try to replace its layout or something similar. If you want to update fragment when scrolling between pages, you need to use method of FragmentPagerAdapter:
#Override
public int getItemPosition(Object object) {
// implementation
return super.getItemPosition(object);
}
This method is called when you will call
notifyDataSetChanged();
on your FragmentPagerAdapter. It's handy method for make updates of your fragments. There are more ways how to do it but here i'll show you how I'm doing it.
Let your fragments implement interface for example called Updateable:
interface Updateable {
public void update();
}
public class MyFragment extends SherlockFragment implements Updateable {
#Override
public void update() {
// perform Fragment updates
}
}
And in this method you will perform updates. Now back to getItemPosition() method. This method will be used for invoking update() method from Fragment i.e:
#Override
public int getItemPosition(Object object) {
Fragment f = (Fragment) object;
// determine which fragment
if (f instanceof MyFragment) {
((MyFragment) f).update(); // invokes update() method
}
return super.getItemPosition(object);
}
Now whenever you scroll page or tap on some tab (you need also call notifyDataSetChanged()) you are able to make Fragment updates. This way is more efficient against destroying and recreating fragment(s) each time you scrolling or clicking on tabs. But how i said this is not only solution there are more possible solutions.
Note: getItemPosition() can return two values: POSITION_NONE and UNCHANGED. Difference between both is that first indicates that Fragment will be always destroyed and recreated that is not very efficient and second indicates that Fragment won't be changed (is in on right place).
For more detailed explanation look here.
That is because ViewPager doesn't hide all fragments you switch.
You can control this behaviour by setOffscreenPageLimit
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.