I'm trying to understand the behavior of fragments when a configuration change occurs.
I have created a project where an activity will host a fragment in onCreate():
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(savedInstanceState==null) {
mFragment1 = new Fragment1();
mManager = getFragmentManager();
mManager.beginTransaction().add(R.id.container_1, mFragment1, null)
.add(R.id.container_1, mFragment1, null)
.commit();
}
}
If the transaction happens only if savedInstanceState is null, and activity and fragments are destroyed and recreated,why does my activity host the fragment again when the device is rotated?
My point is: if the savedinstancestate is not null after rotation , why does the fragment manager add the fragments again?
Thank you.
I belive what you are looking for is here
https://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
and
if you want to know the shorcut just put
setretaininstance(true);
inside of Fragments.
Fragments has it’s own life cycle but it always be embedded with an activity so that the fragments life cycle is directly affected by the host activity’s life cycle.
I think this will help: When you rotate your device and the screen changes orientation, Android usually destroys your application's existing Activities and Fragments and recreates them. Android does this so that your application can reload resources based on the new configuration.
if you use SaveInstanceState like below, then its hold the value, and will not be null and you can handle.
#Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
outState.putString(savedName, name);
}
Related
So, what my problem is that in one fragment(w/i a viewpager, I'll call this Fragment A) I click on this dynamically created button that adds a new fragment(I'll call this Fragment B) in a framelayout which allows me to use PayPal service. On PayPal Activity result, Fragment B communicates with the main Activity via a communicator(an interface class) to call Fragment A to change that text. But I'm getting a null pointer exeception crash.
To be specific:
what I did was that I made a global TextView variable that is initialized on click. I did this b/c I have a list of other things that are dynamically inflated and to avoid the TextView from being initialized with wrong layout I initialized it on click.
bidChange.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
eventListChangeKey = keyVal;
eventListChangeIdx = eventListIdx;
eventBiddingChangeIdx = finalI;
priceToChage = (TextView) biddersLayout.findViewById(R.id.single_list_bidder_bid_price);
Bundle bundle = new Bundle();
bundle.putInt("auctionID", auctionId);
bundle.putInt("dateID", dateId);
bundle.putInt("FromWhere", 2);
Fragment fragment = new Fragment_Home_ItemInfo_Bid();
fragment.setArguments(bundle);
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
fragmentManager.beginTransaction()
.add(R.id.container_mainScreen, fragment, "itemInfo_bid")
.addToBackStack(null)
.setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit();
}
});
In the main activity
public void changeBidderPrice(String s) {
Fragment fragment = viewPagerAdapter.getItem(1);
((Fragment_List) fragment).changePrice(s);
}
is what I do
back in Fragment A
public void changePrice(String val) {
priceToChage.setText(val);
dataMap.get(eventListChangeKey).get(eventListChangeIdx).getBidList().get(eventBiddingChangeIdx).setPrice(val);
}
I've thought this over an over but I just can't figure this out. I've searched for similar cases in StackOverflow but I wasn't able to get a help.
Would the problem be the way I initialize that TextView? or is it the way I'm calling Fragment A from the main activity?
for fragments onViewCreated() is called after onCreateView() and ensures that the fragment's root view is non-null. Any view setup should happen here. E.g., view lookups, attaching listeners.
source : codepath
for activities onCreate()
I'm having trouble with restoring state of a View inside a ViewPager. The content of the ViewPager is a view extending FrameLayout.
The problem is the FrameLayout.onRestoreInstanceState() is not being called if added programmatically into the ViewPager
Here's the code of my Activity.java
private ViewPager vPager;
private MainPagerAdapter mAdapter;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
setContentView(R.layout.activity_layout);
// all the findViewById stuff
CustomView cv1 = new CustomView(this);
CustomView cv2 = new CustomView(this);
cv1.setId(R.id.custom_view_id_1);
cv2.setId(R.id.custom_view_id_2);
mAdapter = MainPagerAdapter();
mAdapter.addView(cv1);
mAdapter.addView(cv2);
vPager.setAdapter(mAdapter);
}
MainPagerAdapter is a class from the accepted answer of this question
Source code for CustomView.java
#Override
protected Parcelable onSaveInstanceState() {
Log.d(TAG, "onSaveInstanceState() called");
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
Log.d(TAG, "onRestoreInstanceState() called");
}
Here's my findings so far:
onSaveInstanceState() will be called but onRestoreInstanceState() is not
When I tried to add the View directly to the root of the Activity, it calls both functions.
I found out that Activity's onRestoreState function will be called before the ViewPager calls the adapter's instantiateItem() function. So when the activity restore its state, the ViewPager doesn't have any children yet, thus the savedState doesn't belong to anyone
So I figure out that I need to make one of two things to work:
Make sure the ViewPager instantiate the item before trying to restore the state, or
Calls the CustomView's onRestoreInstanceState() manually.
I somehow managed to make option number 2, but is there any way to do option number 1?
If I understood your question, you can save your ViewPager items state using mPage.setOffscreenPageLimit(4); 4 is the number of my Fragments inside ViewPager.
I have attached a fragment at runtime in Activity's onCreate() method. I am trying to understand the sequence of lifecycle method calls on the fragment. However the behavior seems to be inconsistent from what is expected. I am doing a screen rotation to understand this. The below is the log after screen is rotated. Have put the marker in the log at the point of doubt.
Can someone explain what is happening here?
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(tag, "OnCreate()");
if (savedInstanceState != null) {
Log.d(tag, "SaveInstanceState is NOT NULL");
}
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
mainFragment = new MainFragment();
fm.beginTransaction().add(R.id.fragment_container, mainFragment, "MainFragment").commit();
}
MainFragment﹕ OnDestroy
MainFragment﹕ OnDetach
MainActivity﹕ OnDestroy
MainFragment﹕ onAttach() <<< Getting called before Activity's onCreate()
MainActivity﹕ OnCreate()
MainActivity﹕ SaveInstanceState is NOT NULL
MainFragment﹕ onCreateView()
MainFragment﹕ SaveInstanceState is NOT NULL
MainFragment﹕ onAttach() <<<<<< onAttach() on Fragment called again
MainFragment﹕ onCreateView() <<<<<<< onCreateView() on Fragment again.
Below is the Fragment lifecycle:
As per your question, when the screen is rotated, it means that the fragment and activities need to be reloaded or "recreated". If you follow the lifecycle image, you will see that Fragment is first destroyed (because it was rotated), then detached. The activity gets detached next.
Then the fragment is attached (the fragment is the object that is called before the Activity). Thereafter the activity is created.
Read up on the Android docs for a better understanding, but above answers your doubts.
I am new to android dev, I created a new project with NavigationDrawer activity, I use Android Studio. The problem is when I add a button and create OnClickListener, the app crushes, but without it, it launches fine. Please look at my code below.
I tried adding setContentView(View) but doesn't help
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); //comes by default
setContentView(R.layout.fragment_main); //added by me, but doesnt help
//referencing my button
btnTest = (Button)findViewById(R.id.btnTest);
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));
//my event listeners
//when i highlight the below code everythin works..these block cause the crash
btnTest.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
}
});
}
I know others have had these issue and solved it, but I'm not able to solve mine, please help, Thank You
setContentView(R.layout.activity_main); //comes by default
setContentView(R.layout.fragment_main); //added by me, but doesnt help
Don't call setContentView twice. The one that "comes by default" is provided for your convenience when the IDE is generating the Activity class, if you don't need it, delete it.
Secondly, you are setting a fragment layout as View for your Activity. So unless R.id.btnTest is included in that layout, btnTest will be null, hence causing a NullPointerException when calling setOnClickListener.
delete this :
setContentView(R.layout.fragment_main);
and here
setContentView(R.layout.activity_main);
make sure you have a layout file for your Activity that is named activity_main.xml or replace that reference with the name of your layout file.
EDIT : I'm guessing you chose the option in AS to generate a placeholder in your Activity and a Fragment to be added to it. You need to handle the button in the Fragment class, and more importantly, you need to add the Fragment to your Activity.
In your Activity (in OnCreate, after the setup of the nav drawer) :
Fragment newFragment = new ExampleFragment(); // replace ExampleFragment by your Fragment's class name
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(CONTENT_VIEW_ID, newFragment).commit(); // CONTENT_VIEW_ID is the id of the View in your Activity that should contain the Fragment.
And then in your Fragment, move this to onActivityCreated :
btnTest.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
}
});
You will need to inflate R.layout.fragment_main in onCreateView in your Fragment, and get a reference to btnTest from the View you inflate.
Without Logcat I would say that your problem is one of the following:
btnTest is not defined in your ActivityLayout (activity_main). If this is the case check your XML.
Is your btnTest inside your Fragment? If so you should put the OnClickListener in your Fragment class instead of your Activity class.
BTW, the second setContentView doesnt make any sense you should use only one.
Hope it helps
I read the many posts that have already dealt with similar issues but haven't been able to find something that answers my question completely.
I have an Android app that uses nested fragments (from the v4 support library). I have a main FragmentActivity that contains a fragment, and that fragment contains a ViewPager which enables swiping between 3 internal fragments.
I'd like to be able to save the state of each of the 3 internal nested fragments, and for that I overrode the onSaveInstanceState() method for each of the 3 internal fragments and attempted to restore the state in onActivityCreated(), like so:
InternalFragment1.java:
public class InternalFragment1 extends Fragment {
#Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
// Commands to attach to main UI components...
if(savedInstanceState != null) {
// Commands to restore the saved state...
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
// Commands to save the state into outState...
super.onSaveInstanceState(outState);
}
}
However when onActivityCreated() is called, savedInstanceState is always null, regardless of whether a saved state exists or not.
I should also point out that calling this.setRetainInstance() throws an exception stating: "Can't retain fragments that are nested in other fragments".
How can I properly save and restore the nested fragments' state?
I had a similar issue and was looking for hints on solving it. Eventually, I realized that my parent fragment's onCreateView included:
mChildFragment = ChildFragment.newInstance(mId);
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, mChildFragment).commit();
This, of course, creates a new instance of the child fragment, which has a null bundle for the savedInstanceState. Surrounding the above block with a conditional:
if(savedInstanceState == null) {
mChildFragment = ChildFragment.newInstance(mId);
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, mChildFragment).commit();
}
seems to make it work, in that now the onCreate in the child fragment sees the non-null savedInstanceState I created for it in onSaveInstanceState, and is restored to how I want it.
if you use setRetainInstance(true) then of course the bundle is null.
The fragment is not destroyed but only detached from the current activity and attached to the new activity. Only when the fragment is destroyed, you get a bundle with the values you saved in onSaveInstanceState.
Just try to remove setRetainInstance(true).
That's a problem you may encounter when the parent fragment is retaining.
You can try this:
http://ideaventure.blogspot.lu/2014/10/nested-retained-fragment-lost-state.html
But I would better recommend removing the setRetaining() on the parentFragment.
There doesn't seem to be a simple way for a nested fragment to retain information. My solution was to have the parent fragment hold onto a map of Bundles and the nested fragments get their own during onCreate. The biggest issue with this is you can't have more than one instance of each nested fragment.
Ex (sorry this is in Kotlin, but it's the same thing in Java)
class ParentFragment : Fragment(), ParentFragmentListener {
val bundles = SparseArray<Bundle>()
fun getChildBundle(fragmentId : Int) : Bundle {
if (bundles.get(fragmentId) == null) {
val bundle = Bundle()
bundles.put(fragmentId,bundle)
return bundle
}
return bundles.get(fragmentId)
}
}
interface ParentFragmentListener {
fun getChildBundle(fragmentId : Int) : Bundle
}
class ChildFragment : Fragment() {
lateinit var childBundle : Bundle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val listener = parentFragment as? ParentFragmentListener
val childBundle = listener?.getFragmentsSavedBundle(UNIQUE_FRAGMENT_ID)
if (childBundle != null) this.childBundle = childBundle else childBundle = Bundle()
}
}