Nested fragments created twice on orientation change - android

I created this example to understand the lifecycle of android fragments at screen orientation change.
MainActivity is a container for DrawerLayout, DrawerLayout allows you to choose a fragment, which will fill the MainActivity screen.
public class MainActivity extends ActionBarActivity {
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("MainActivity", "onCreate savedInstanceState = "+(savedInstanceState == null ? "null" : "not null"));
...
mDrawerList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (position == 0) {
if (viewingPosition == position) {
mDrawerLayout.closeDrawer(mDrawerList);
return;
}
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.contentFrame, new ParentPagerFragment(),
ParentPagerFragment.TAG).commit();
viewingPosition = 0;
}
if (position == 1) {
if (viewingPosition == position) {
mDrawerLayout.closeDrawer(mDrawerList);
return;
}
getSupportFragmentManager().beginTransaction()
.replace(R.id.contentFrame, ChildTextViewFragment.newInstance("hello fragment"), ChildTextViewFragment.TAG)
.commit();
viewingPosition = 1;
}
}
...
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.contentFrame, new ParentPagerFragment(),
ParentPagerFragment.TAG).commit();
...
Then I have ParentPagerFragment, ParentPagerFragment contains only a ViewPager with 3 ChildTextViewFragments.
public class ParentPagerFragment extends Fragment {
...
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(TAG, "onCreateView savedInstanceState is "+(savedInstanceState == null ? "null" : "not null"));
View v = inflater.inflate(R.layout.fragment_pager, container, false);
List<ChildTextViewFragment> viewFragments = new ArrayList<>();
viewFragments.add(ChildTextViewFragment.newInstance("Fragment1"));
viewFragments.add(ChildTextViewFragment.newInstance("Fragment2"));
viewFragments.add(ChildTextViewFragment.newInstance("Fragment3"));
MyPagerAdapter mPagerAdapter = new MyPagerAdapter(getChildFragmentManager(), viewFragments);
ViewPager mViewPager = (ViewPager) v.findViewById(R.id.pager);
mViewPager.setAdapter(mPagerAdapter);
return v;
}
...
}
class MyPagerAdapter extends FragmentPagerAdapter {
List<ChildTextViewFragment> viewFragments;
public MyPagerAdapter(FragmentManager fm, List<ChildTextViewFragment> viewFragments) {
super(fm);
this.viewFragments = viewFragments;
}
#Override
public Fragment getItem(int index) {
return viewFragments.get(index);
}
#Override
public int getCount() {
return viewFragments.size();
}
#Override
public CharSequence getPageTitle(int position){
if(position == 0) {
return "Fragment1";
} else if (position == 1) {
return "Fragment2";
} else {
return "Fragment3";
}
}
}
ChildTextViewFragment is used only to display some text
public class ChildTextViewFragment extends Fragment {
...
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_text, container, false);
TextView mTextView = (TextView) view.findViewById(R.id.textView1);
String text = getArguments().getString(TEXT_KEY);
mTextView.setText(text);
Log.d(TAG, text+" :: onCreateView savedInstanceState is " + (savedInstanceState == null ? "null" : "not null"));
return view;
}
After I run this example for the first time I got these logs messages as expected:
06-01 10:34:24.154: D/MainActivity(8426): onCreate savedInstanceState = null
06-01 10:34:24.272: D/ParentPagerFragment(8426): onCreateView savedInstanceState is null
06-01 10:34:24.389: D/ChildTextViewFragment(8426): Fragment1 :: onCreateView savedInstanceState is null
06-01 10:34:24.390: D/ChildTextViewFragment(8426): Fragment2 :: onCreateView savedInstanceState is null
The surprise appeared, when I rotated the display:
06-01 10:36:15.697: D/MainActivity(8426): onCreate savedInstanceState = not null
06-01 10:36:15.713: D/ParentPagerFragment(8426): onCreateView savedInstanceState is not null
06-01 10:36:15.716: D/ChildTextViewFragment(8426): Fragment1 :: onCreateView savedInstanceState is not null
06-01 10:36:15.717: D/ChildTextViewFragment(8426): Fragment2 :: onCreateView savedInstanceState is not null
06-01 10:36:15.718: D/ParentPagerFragment(8426): onCreateView savedInstanceState is null
06-01 10:36:15.739: D/ChildTextViewFragment(8426): Fragment1 :: onCreateView savedInstanceState is null
06-01 10:36:15.740: D/ChildTextViewFragment(8426): Fragment2 :: onCreateView savedInstanceState is null
I was expecting all fragments will be restored and displayed again (like the first 4 line of this logs shows), but I don't understand, why are all the fragments created again with savedInstanceState = null ? Is this a common behavior, or am I doing something wrong ?

Try adding null check for savedInstanceState in your MainActivity
onCreate(Bundle savedInstanceState) method like this
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.contentFrame, new ParentPagerFragment(),
ParentPagerFragment.TAG).commit();
}

You are using FragmentPagerAdapter . So, By default it will try to load two fragments of two indices at a time in your viewpager.
So there you need to mention the pager.offScreenPageLimit(0) to load it once.

Related

Saving and restoring state using fragments

I'm trying to understand the process of saving and restoring state using fragments. I've created sliding navigation menu using it.
In one of the fragments there is this code:
public class FifthFragment extends Fragment {
CheckBox cb;
View view;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fifth_layout, container, false);
cb = (CheckBox) view.findViewById(R.id.checkBox);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
// Restore save state
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// save state
}
}
For example I want to save the state of the CheckBox before user exits the fragment and restore it when the fragment is created again. How to achieve this?
EDIT:
According to raxellson's answer I've changed my fragment to this:
public class FifthFragment extends Fragment {
private static final String CHECK_BOX_STATE = "string";
CheckBox cb;
View view;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fifth_layout, container, false);
cb = (CheckBox) view.findViewById(R.id.checkBox);
if (savedInstanceState == null) {
Log.i("statenull", "null");
}
if (savedInstanceState != null) {
// Restore last state for checked position.
boolean checked = savedInstanceState.getBoolean(CHECK_BOX_STATE, false);
cb.setChecked(checked);
}
return view;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(CHECK_BOX_STATE, cb.isChecked());
}
}
I got logged I/statenull: null so savedInstanceState was not saved. What am I doing wrong?
You want to save the value of your current checked state in onSaveInstanceState.
Something like this:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(CHECK_BOX_STATE, cb.getChecked());
}
and then when your view is created you want to get the value if it's present. And set your CheckBox state with it.
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fifth_layout, container, false);
cb = (CheckBox) view.findViewById(R.id.checkBox);
if (savedInstanceState != null) {
// Restore last state for checked position.
boolean checked = savedInstanceState.getBoolean(CHECK_BOX_STATE, false);
cb.setChecked(checked);
}
return view;
}
EDIT:
When you add the fragment, make sure to add it with a tag or id so that you can retrieve the same instance.
You could do a helper method to retrieve fragment and set the fragment.
private void setFragment(String tag, Fragment newFragment) {
FragmentManager fm = getSupportFragmentManager();
Fragment savedFragment = fm.getFragmentByTag(tag);
fm.replace(R.id.container, savedFragment != null ? savedFragment : newFragment, tag);
fm.commit();
}
so you your switch you can call the helper method instead.
switch (position) {
case 0:
setFragment("A", new FragmentA());
break;
....
}
Note: This is just an example not best practice since you are creating new fragments every time in your switch case now anyways. But it might point you in the right direction.
After see all the example. Here is the solution for save fragment state:
Two steps for this:
1.
String saveValue;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
saveValue = "";
} else {
saveValue = savedInstanceState.getString("saveInstance");
}
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
//save the values of fragment if destroy on second to back
if (!saveValue.isEmpty())
savedInstanceState.putString("saveInstance", saveValue);
}
In onSaveInstanceState you can save your values. And after destroy fragment you can receive your values through onCreate.

Fragment - wait for onCreateView() to complete before running methods

I'm struggling with a puzzling sequence of events relating to a Fragment. I'm trying to add a fragment to an Activity, and then call a method inside the fragment to update some text. However, what I am finding is that the method is being processed in the fragment before onCreateView() finishes, which leaves me with a null View object, and my method fails. Here is the Activity code:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_log_entry_details);
fragmentManager = getSupportFragmentManager();
titleBarFragment = new TitleBarVerticalFragment();
fragmentTransaction = fragmentManager.beginTransaction ();
fragmentTransaction.add(R.id.log_entry_title_frame, titleBarFragment);
fragmentTransaction.commit();
titleBarFragment.updateTitleBar("Edit Log Entry", 20, false);
}
Seems simple enough. Here is the TitleBarVerticalFragment class:
public class TitleBarVerticalFragment extends TitleBarFragment {
#Inject SharedVisualElements sharedVisualElements;
View view;
TextView titleLabel;
public TitleBarVerticalFragment() {
// add this line for any class that want to use any of the singleton objects
Injector.INSTANCE.getAppComponent().inject(this);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
Log.d(Constants.TAG, "title fragment onCreateView()");
view = inflater.inflate(R.layout.fragment_title_bar_vertical, container, false);
ImageView logoImage = (ImageView) view.findViewById(R.id.logo_vertical);
titleLabel = (TextView) view.findViewById(R.id.verticalTitleLabel);
titleLabel.setTextColor(sharedVisualElements.secondaryFontColor());
titleLabel.setTypeface(sharedVisualElements.font());
titleLabel.setTextSize(20);
logoImage.setImageDrawable(sharedVisualElements.logoImage());
logoImage.setBackgroundColor(Color.TRANSPARENT);
return view;
}
public void updateTitleBar(String text, int textSize, boolean titleLabelIsHidden) {
Log.d(Constants.TAG, "about to update title bar text");
if (view == null) {
Log.d(Constants.TAG, "vertical title fragment is null");
return;
}
if (titleLabel == null)
titleLabel = (TextView) view.findViewById(R.id.verticalTitleLabel);
if (titleLabel == null) {
Log.d(Constants.TAG, "vertical title label is null");
return;
}
Log.d(Constants.TAG, "updating title text: " + text);
titleLabel.setText(text);
titleLabel.setTextSize(textSize);
}
Note the order of this logcat output. Notice how onCreateView() seems to run after the updateTitleBar() method? How can that be?
about to update title bar text vertical title fragment is null
title fragment onCreateView()
How can I ensure that onCreateView() runs before I call any of the fragment's other methods? Thank you.
Try running fragmentManager.executePendingTransactions() after fragmentTransaction.commit(); and before titleBarFragment.updateTitleBar("Edit Log Entry", 20, false);
Just use onStart() on your activity
Define a listener interface and implement it in your Activity.
interface LyfecycleListener {
void onCreatedView();
}
in your Activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
...
this.titleBarFragment = new TitleBarVerticalFragment();
this.titleBarFragment.setListener(this)
...
}
#Override
public void onCreatedView() {
titleBarFragment.updateTitleBar("Edit Log Entry", 20, false);
}
in your Fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
this.listener.onCreatedView();
}

Android childfragments from viewpager getActivity returns null

I have an activity and with a button I am switching between the two fragments (MAIN & SETTINGS). In the MAIN fragment I have a ViewPager with 4 child fragments.
At first run everything works fine, but if I rotate the screen, the getActivity() for fragments within the ViewPager is returning null.
ActivityMain:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Add or show the fragments
showHideScreenFragment(FRAGMENT_MAIN);
}
private void showHideScreenFragment(String tag) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
// Get the fragment from the backstack if it is existing
BaseFragment oldFragment = getFragmentFromBackstack(tag);
// Get the current fragment from the layout
BaseFragment currentFragment = getCurrentFragment();
if (oldFragment == null) {
if (currentFragment != null) {
ft.hide(currentFragment);
}
ft.add(getMainContainerId(), getFragmentInstance(tag), tag);
}
else {
if (currentFragment != null) {
if (isSameFragment(oldFragment, currentFragment))
return;
ft.hide(currentFragment);
}
if (oldFragment.isHidden())
ft.show(oldFragment);
}
ft.commit();
fm.executePendingTransactions();
}
private BaseFragment getFragmentInstance(String tag) {
if (tag.equals(FRAGMENT_MAIN)) return getFragmentMain();
if (tag.equals(FRAGMENT_SETTINGS)) return getFragmentSettings();
throw new RuntimeException("Fragment not found !");
}
private FragmentMain getFragmentMain() {
return new FragmentMain();
}
private FragmentSettings getFragmentSettings() {
return new FragmentSettings();
}
private BaseFragment getFragmentFromBackstack(String tag) {
if (tag.equals(FRAGMENT_MAIN)) return getFragmentMainFromBackstack();
if (tag.equals(FRAGMENT_SETTINGS)) return getFragmentSettingsFromBackstack();
throw new RuntimeException("Fragment not found !");
}
private FragmentMain getFragmentMainFromBackstack() {
return (FragmentMain) getSupportFragmentManager().findFragmentByTag(FRAGMENT_MAIN);
}
private FragmentSettings getFragmentSettingsFromBackstack() {
return (FragmentSettings) getSupportFragmentManager().findFragmentByTag(FRAGMENT_SETTINGS);
}
private boolean isSameFragment(Fragment f1, Fragment f2) {
return f1.getTag().equals(f2.getTag());
}
FragmentMain:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
viewPager = (ViewPager) view.findViewById(R.id.viewPager);
// Add the 4 child fragments to the viewpager
populateViewPager();
// Debugging
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
_printFragmentStates();
}
}, 2500);
return view;
}
private void populateViewPager() {
ArrayList<BaseMainFragment> fragments = new ArrayList<BaseMainFragment>();
fragments.add(new FragmentSearch());
fragments.add(new FragmentFavorites());
fragments.add(new FragmentHouse());
fragments.add(new FragmentRoom());
adapterMain = new AdapterMain(getChildFragmentManager(), fragments);
viewPager.setOffscreenPageLimit(4);
viewPager.setAdapter(adapterMain);
}
// DEBUGGING
private void _printFragmentStates() {
Activity actSearch = null;
Activity actFav = null;
Activity actHouse = null;
Activity actRoom = null;
actSearch = getFragmentSearch().getActivity();
actFav = getFragmentFavorites().getActivity();
actHouse = getFragmentHouse().getActivity();
actRoom = getFragmentRoom().getActivity();
Functions.logd("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
Functions.logd("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
Functions.logd("Main fragment act, is null: " + (getActivity() == null));
Functions.logd("Search act, is null: " + (actSearch == null));
Functions.logd("Favorite act, is null: " + (actFav == null));
Functions.logd("House act, is null: " + (actHouse == null));
Functions.logd("Room act, is null: " + (actRoom == null));
Functions.logd("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
Functions.logd("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
}
private FragmentSearch getFragmentSearch() {
return (FragmentSearch) adapterMain.getItem(0);
}
private FragmentFavorite getFragmentFavorite() {
return (FragmentFavorite) adapterMain.getItem(1);
}
private FragmentHouse getFragmentHouse() {
return (FragmentHouse) adapterMain.getItem(2);
}
private FragmentRoom getFragmentHouse() {
return (FragmentRoom) adapterMain.getItem(3);
}
As I said, at first run everything works fine, but after I rotate the screen, I am getting null for getActivity(); in the 4 child fragments: Search, Favorite, House and Room.
Logcat debug
1 run:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Main fragment act, is null: false
Search act, is null: false
Favorite act, is null: false
House act, is null: false
Room act, is null: false
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
After screen orientation changed:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Main fragment act, is null: false
Search act, is null: true
Favorite act, is null: true
House act, is null: true
Room act, is null: true
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
What am I doing wrong?
After hours of debugging, I figured out that if you're having only 1 fragment (without child or nested fragments) attached to your activity, then you don't need to re-add your fragment.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Add or show the fragments if the savedInstance is null, otherwise let the system reattach your fragment.
if (savedIstance == null)
showHideScreenFragment(FRAGMENT_MAIN);
}
You don't need to reattach the fragment, the android system will do this for you.
And the solution for getting NPE at getActivity(); in child fragments is:
Use FragmentStatePagerAdapter for your ViewPager's adapter.
and override the saved state method:
#Override
public Parcelable saveState() {
return null;
}
I don't know why, but setRetainInstance(false); does not helped me, and I think this will remain a mystery for me.
It's a good practice to follow a pattern like this when working with fragments :
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view = view.findViewById(R.id.view);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// do your thing here
populateViewPager();
}
1) Use OnCreateView to set your fragment's view,
2) OnViewCreated to initialize view and
3) OnActivityCreated to setup things.
Using getActivity() inside OnActivityCreated ensures that getActivity() does not return null. This method gets called only after the activity is fully initialized. OnAttach, OnCreate, OnCreateView may get even before the activity is created.

How come position information isn't being passed to my fragments?

I have an Activity that is largely unmodified form the default Android Studio example for a tabbed activity. Its FragmentPagerAdapter is modified to display all 50 United States, with 50 corresponding tabs displaying their names. This works until a fragment is destroyed, but when it's re-created, it's not told which tab it's on. Why does this happen?
The following are all the methods that I think could be part of the problem:
public class MainQuizActivity extends Activity implements ActionBar.TabListener {
...
public class SectionsPagerAdapter extends FragmentPagerAdapter {
...
#Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
// Return a PlaceholderFragment (defined as a static inner class below).
return
questionFragments[position] == null
? questionFragments[position] = QuestionFragment.newInstance(position)
: questionFragments[position];
}
...
}
...
}
...
public class QuestionFragment extends Fragment {
...
public static QuestionFragment newInstance(int stateIndex) {
System.out.println("Creating new instance for state #" + stateIndex);
QuestionFragment fragment = new QuestionFragment();
Bundle args = new Bundle();
args.putInt(States.BUNDLE_KEY, stateIndex);
fragment.setArguments(args);
return fragment;
}
...
#Override
public View onCreateView(
LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
System.out.println("~onCreateView");
View rootView = inflater.inflate(
R.layout.fragment_main_quiz, container, false);
webView = (WebView)rootView.findViewById(R.id.webView);
initState(savedInstanceState);
return rootView;
}
private void initState(Bundle args) {
if (state == null) {
System.out.println("Bundle: " + args);
if (args == null)
args = getArguments();
System.out.println("Bundle is now: " + args);
int stateIndex = args.getInt(States.BUNDLE_KEY);
System.out.println("Gonna be state #" + stateIndex);
state = States.values()[stateIndex];
System.out.println("Gonna be " + state);
}
else
System.out.println("State already exists! (yay!)");
String path = state.getImageURL();
System.out.println("Opening image at " + path);
webView.loadUrl(path);
webView.setBackgroundColor(0x00000000);
}
#Override
public void onCreate(Bundle savedInstanceState) {
System.out.println("~onCreate");
super.onCreate(savedInstanceState);
if (webView == null)
webView = (WebView)(getActivity().findViewById(R.id.webView));
System.out.println("onCreate: webView == " + webView);
System.out.println("onCreate: bundle == " + savedInstanceState);
if (webView != null
&& savedInstanceState != null)
initState(savedInstanceState);
}
...
}
I saved this in the bundle with the key States.BUNDLE_KEY (which is "STATE"), but the bundle it's given does not have that key in it the second time. For debugging purposes, I overrode all on* methods that deal with loading and unloading with empty ones like:
#Override public void onResume(){
System.out.println("~onResume");
super.onResume();}
Of course, there are also more debugging outputs that I threw in.
I hope this video I recorded helps illustrate the issue: http://youtu.be/cmbR_2rvpX4
The console dump for the video is in this pastebin: http://pastebin.com/rxAP7qda
And, if all this still doesn't help, here's its git: https://github.com/Supuhstar/US-State-Quiz-App
Even after all this, I feel I'm not giving the right information. Please ask for less or more if you think this can be put better.
You're not initializing things properly in your QuestionFragment. First of all there's no need to call the initState(savedInstanceState); method in both the onCreate() and onCreateView() callbacks. Call it in onCreateView() and remove entirely the onCreate() method:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
System.out.println("~onCreateView");
View rootView = inflater.inflate(R.layout.fragment_main_quiz, container, false);
webView = (WebView)rootView.findViewById(R.id.webView);
guessSpinner = (Spinner)getActivity().findViewById(R.id.guess_spinner);
initState();
initGuesser();
return rootView;
}
Your initState(savedInstanceState) method is also a bit too complicated for what it should do:
private void initState() {
Bundle args = getArguments();
if (args == null) {
throw new IllegalArgumentException("The arguments should be valid!");
}
System.out.println("Bundle is now: " + args);
int stateIndex = args.getInt(States.BUNDLE_KEY);
System.out.println("Gonna be state #" + stateIndex);
state = States.values()[stateIndex];
System.out.println("Gonna be " + state);
String path = state.getImageURL();
System.out.println("Opening image at " + path);
webView.loadUrl(path);
webView.setBackgroundColor(getResources().getColor(R.color.transparent));
}

Why does the fragment's onCreateView, onCreate, onActivityCreated are called

I have an app, that deals with fragments and ViewPager. I have three fragments in a ViewPager. When you switch between them, it always causes the other two fragments to call their's onCreateView methods. How to do it only once, only when FragmentActivity is created???
I've read some questions and tried the solutions, but the fragments still have the same behavior.
ListFragment onCreate called twice
onCreate() and onCreateView() invokes a lot more than required (Fragments)
Here is some code, if it helps you, guys:
MainActivity:
public class StartingActivity extends FragmentActivity implements View.OnClickListener {
ViewPager viewPager;
CirclePageIndicator pageIndicator;
Button discount;
Button qrCode;
Button pay;
TabHost tabHost;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.starting_layout);
viewPager = (ViewPager) findViewById(R.id.pager);
if (savedInstanceState == null) {
Fragment firstPage = Fragment.instantiate(this, FindTovarFragment.class.getName());
Fragment secondPage = Fragment.instantiate(this, MainWindowActivity.class.getName());
Fragment thirdPage = Fragment.instantiate(this, MapActivity.class.getName());
if ((firstPage != null && !firstPage.isDetached())|| (secondPage != null && !secondPage.isDetached()) || (thirdPage != null && !thirdPage.isDetached())) {
List<Fragment> viewPagerFragments = new ArrayList<Fragment>();
viewPagerFragments.add(firstPage);
viewPagerFragments.add(secondPage);
viewPagerFragments.add(thirdPage);
PageAdapter pageAdapter = new PageAdapter(getSupportFragmentManager(), viewPagerFragments);
viewPager.setAdapter(pageAdapter);
pageIndicator = (CirclePageIndicator) findViewById(R.id.circle);
pageIndicator.setViewPager(viewPager);
pageIndicator.setCurrentItem(pageAdapter.getCount() - 2);
}
}
}
MapActivity:
public class MapActivity extends Fragment implements OnMyLocationListener {
//Тэг для логов
private static final String TAG = "MapActivity";
List<Address> addressList;
private static final String STRING_LOCATION = "";
ArrayList<TorgCentr> randomTorgCentr;
ArrayList<String> torgCentrNames;
Context context;
AutoCompleteTextView searchTorgCentr;
OverlayManager overlayManager;
MapController mapController;
TextView textView;
double longitude;
double latitude;
double itemLongitude;
double itemLatitude;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(TAG, "MapActivity onCreateView");
View view = (LinearLayout) inflater.inflate(R.layout.map_layout, container, false);
final MapView mapView = (MapView) view.findViewById(R.id.map);
textView = (TextView) view.findViewById(R.id.searchlocation);
searchTorgCentr = (AutoCompleteTextView) view.findViewById(R.id.autoCompleteTextView);
mapView.showBuiltInScreenButtons(true);
mapController = mapView.getMapController();
context = getActivity();
return view;
}
#Override
public void onResume() {
super.onResume();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "MapActivity onCreate");
}
public void onActivityCreated(Bundle savedInstanceState) {
Log.d(TAG, "MapActivity onActivityCreated");
context = getActivity();
SetRightMapDisplayAddress rightMapDisplayAddress = new SetRightMapDisplayAddress();
rightMapDisplayAddress.execute(STRING_LOCATION);
DownloadSuperMarketsArray superMarketsArray = new DownloadSuperMarketsArray();
superMarketsArray.execute();
overlayManager = mapController.getOverlayManager();
overlayManager.getMyLocation().setEnabled(false);
super.onActivityCreated(savedInstanceState);
}
Second Fragment:
public class MainWindowActivity extends Fragment {
private static final String TAG = "MainWindowActivity";
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(TAG, "MainWindowActivity onCreateView");
View view = (RelativeLayout) inflater.inflate(R.layout.main_window_layout, container, false);
if (container == null) {
return null;
}
return view;
}
}
And the third one:
public class FindTovarFragment extends Fragment {
private static final String TAG= "FindTovarFragment";
Context context;
ArrayList<Category> categories;
Spinner categoryContainer;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(TAG, "FindTovarFragment onCreateView");
View view = (LinearLayout) inflater.inflate(R.layout.find_tovar_main_layout, container, false);
categoryContainer = (Spinner) view.findViewById(R.id.category);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.d(TAG, "FindTovarFragment onActivityCreated");
DownloadCategory downloadCategory = new DownloadCategory();
downloadCategory.execute();
}
Logs for MapActivity:
06-20 11:06:37.709: DEBUG/MapActivity(1290): MapActivity onCreate
06-20 11:06:37.709: DEBUG/MapActivity(1290): MapActivity onCreateView
06-20 11:06:38.509: DEBUG/MapActivity(1290): MapActivity onActivityCreated
Then again and again:
06-20 11:07:53.239: DEBUG/MapActivity(1290): MapActivity onCreate
06-20 11:07:53.239: DEBUG/MapActivity(1290): MapActivity onCreateView
06-20 11:07:53.429: DEBUG/MapActivity(1290): MapActivity onActivityCreated
06-20 11:08:23.029: DEBUG/MapActivity(1290): MapActivity onCreate
06-20 11:08:23.039: DEBUG/MapActivity(1290): MapActivity onCreateView
06-20 11:08:23.269: DEBUG/MapActivity(1290): MapActivity onActivityCreated
Thank you very much in advance.
ViewPager retain in memory 1 page by default, to either side of the current page. So it would not re-create those pages when swiping 1 page left/right of the current page. But when swipe more than 1 pages to left/right, it would re-create those page again, hence called OnCreateView(), OnCreate().
If app uses few pages 3, you can increase the number of pages to retain by calling,
mViewPager.setOffscreenPageLimit(2);
Described here
I would change your architecture for this one on the android developer documentation:
http://developer.android.com/reference/android/support/v4/app/FragmentPagerAdapter.html
but I would change some things...
1-I would change this method:
/**
* The Fragment's UI is just a simple text view showing its
* instance number.
*/
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_pager_list, container, false);
View tv = v.findViewById(R.id.text);
((TextView)tv).setText("Fragment #" + mNum);
return v;
}
For something like this where we decide which fragment you populate depending the position of the viewPager:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
SupportFragmentManager ft = getChildFragmentManager().beginTransaction();
String tag = "";
Fragment fragment = null;
switch (mNum) {
case 0:
fragment = new MyFragmentZero();
tag = FragmentTags.TAG_0;
break;
case 1:
fragment = new MyFragmentOne();
tag = FragmentTags.TAG_3;
break;
case 2:
fragment = new MyFragmentTwo();
tag = FragmentTags.TAG_2;
break;
default:
break;
}
/*OPTIONAL We can pass arguments to the fragments
Bundle args = new Bundle();
args.putInt(Arguments.ARG_POSITION, mNum);
fragment.setArguments(args);*/
//Place the fragment in the container
ft.replace(R.id.fragment_container fragment, tag);
ft.commit();
//You need a base layout for all fragment and use nested fragments later or you can define the layout for each position(mNum) inside the switch.
return inflater.inflate(R.layout.fragment_layout_default_for_all_views, container,
false);
}
Like this you will have a good architecture and once it is working like this should be fine.
Anyway you must know how the viewPager works populating the fragment in the different positions.
When you start on the position 0, then the fragment on the position 0 and the one of the position 1 are created.
Then when you swipe to the position 1 the fragment on the 2 position is created, so you have now the three fragments created on the different positions (0,1,2..assuming you have only 3 pages on the viewPager).
We swipe to the position 2, the last one, and the fragment on the first position (0) get destroy, so we have now the fragments on the positions 2 and 3.
I hope it helped and let me know if you have any problem. Cheers
Finally I was able to figure it out. Just need to override the destroyItem method so that it won't destroy objects. Hope this is going to be useful for someone.
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
Log.d(TAG, "destroy!");
}

Categories

Resources