I have a SupportMapFragment added programmatically with getChildFragmentManager() in my onCreate() method.
When I reopen the app after the activity has been closed, the app seems to be rendering the old child SupportMapFragment without the markers. The old child fragment isn't interactable either.
How do I fix this lifecycle issue with SupportMapFragment? Do I need to call a specific detach method or something to that effect?
The issue was to do with the way I was handling my child fragments.
Every time the parent fragment would call onCreate the children fragments would get recreated.
I did the following to handle my child fragments, but there may be a better way:
private static final String TAG_FRAGMENT_MAP = "TagFragmentMap";
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
if (savedInstanceState == null) {
// create the fragments for the first time
ft.add(R.id.view_flip, new SupportMapFragment(), TAG_FRAGMENT_MAP);
ft.commit();
}
}
// ...
public void onViewStateRestored(Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
mMapFragment = (SupportMapFragment)findFragmentByTag(TAG_FRAGMENT_MAP);
}
Related
I am extremely new to Android development. I basically have an activity with some buttons, for example "seeTreePicture" and "seeSeaPicture". When I press a button, I want to use a fragment I called "ContentViewer" to display a random tree/sea picture, and also have buttons under the picture to destroy the ContentViewer fragment instance and go back to the menu. The issue is, if I try to use a Fragment Transaction anywhere other than onCreate() of the activity, I get a null pointer exception when I try to access the view in the fragment.
My activity and things related to the fragment:
public class SeeActivity extends AppCompatActivity {
DisplayFragment displayFragment;
Button seeTreeButton;
Button seeSeaButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_see);
seeTreeButton = findViewById(R.id.seeTreeButton);
seeSeaButton = findViewById(R.id.seeSeaButton);
displayFragment = new DisplayFragment();
seeTreeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.fragmentContainer, displayFragment);
fragmentTransaction.commit();
fragmentTransaction.addToBackStack(null);
displayFragment.changeImage(randomTree);
}
});
}
}
In my fragment, change image simply changes the image source of the ImageView:
public void changeImage(int treeResource)
{
img = getView().findViewById(R.id.imageView);
img.setImageResource(treeResource);
}
I get a null pointer exception for trying to access the view from getView() in the fragment, meaning that onCreateView wasn't invoked. Yet if I put the same transaction in the onCreate() method of the activity, it works. What am I doing wrong?
The issue with your code is that you are accessing getView() of your fragment before the fragment has gone through the initialization of the view. The reason why you don't have the crash when you execute the transaction in your activity's onCreate() method is that by the time you click on a button your fragment has already gone through onCreateView() and initialized its view. Check out fragment lifecycle guide and bear in mind that you should not access your fragment view before it was created or after it was destroyed. For more information about why your fragment view is not initialized instantly check out this guide.
As for the solution, consider setting arguments for your fragment before adding it to your transaction like here:
Bundle args = new Bundle();
args.putInt(DisplayFragment.IMG_RESOURCE_ARG, randomTree);
displayFragment.setArguments(args);
Then in your onCreateView() or onViewCreated() methods of your fragment restore the arguments like here and set the image resource:
#Override
public void onViewCreated(#NonNull View view, Bundle savedInstanceState) {
int resourceId = requireArguments().getInt(IMG_RESOURCE_ARG);
img = view.findViewById(R.id.imageView);
img.setImageResource(resourceId);
}
Because the fragment transaction doesn't occur instantly. It occurs async. So the actual work of creating views hasn't occurred yet. Your options are to either use commitNow() instead of commit (which will do it synchronously, but require much more time and possibly cause your app to visibly pause) or to wait for the fragment transaction to actually complete. That can easily be done by putting it in a Runnable and passing that runnable to runOnCommit
I have an app that has a main activity that the user can select an item from. That selection brings up a fragment (TracksActivityFragment) that itself is another list. When an item of that list is selected, a fragment is added that is a DialogFragment. So far so good, but when I rotate the device, the AFragment's onCreate() gets called and then the DailogFragment's onCreate() gets called, then it dies with the IllegalStateException saying that it dies on AFragment's Activity line 20 (setContentView).
Here is a part of that Activity with the line in question:
public class TracksActivity extends AppCompatActivity
{
private String mArtistName;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tracks); //DIES HERE
Here is the onCreate of the fragment
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if(savedInstanceState != null)
{
//We've got data saved. Reconstitute it
mTrackRowItemList = savedInstanceState.getParcelableArrayList(KEY_ITEMS_LIST);
}
}
The DialogFragment gets created in the TracksFragment like this:
PlayerFragment fragment = PlayerFragment.newInstance(mTrackRowItemList, i, mArtistBitmapFilename);
// The device is smaller, so show the fragment fullscreen
FragmentTransaction transaction = fragMan.beginTransaction();
// For a little polish, specify a transition animation
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
// To make it fullscreen, use the 'content' root view as the container
// for the fragment, which is always the root view for the activity
transaction.add(android.R.id.content, fragment).addToBackStack(null).commit();
Here is the DialogFragment's onCreate
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (savedInstanceState != null)
{
//We've got data saved. Reconstitute it
if (mPlayer != null)
{
mPlayer.seekTo(savedInstanceState.getInt(KEY_SONG_POSITION));
}
}
}
Not sure why it goes back to the TracksFragment since it had the DialogFragment active on rotation, but since that is the case, it would seem like I would need to recreate the entireDialogPlayer object, But it seems to keep this around as the call to its onCreate happens.
Anyone know what it is that needs to be done here?
OK, this was asked before but I discounted the solution...I should not have.
For some reason, Android wants the Tracks layout XML to use a FrameLayout instead of a fragment.
So, just replace fragment with FrameLayout in the layout xml file and all is well.
Hello guys i know after reading the title of my question you find it very simple to answer but as i am new in android development so i find it hard to retain state of listview during orientation change and even of fragment state i surf a lot on google but i not find any satisfactory solution for retaing state during orientation change i know their is an onsaveinstancestate() method in which you have to put your each view data but i think that their is a better solution so please help me in finding the solution for it .You can also provide the link of good tutorials on orientation change..
Thanks in Advance
I've noticed that depending on your implementations, listview state is saved by default but to restore the state, recreate the listview and make sure one of the super methods with savedInstanceState as parameter is called afterwards (not before). Why? Since the listview state has been saved, the super method restores it and if you recreate after calling super, you override the restored state.
Another method is to override onSaveInstanceState(outState) of the activity, put the listview state in the bundle,
outState.putParcelable("listview.state", listview.onSaveInstanceState());
Then when you override onRestoreInstanceState(savedInstanceState), after recreating the listview, you call;
Parcelable listViewState = savedInstanceState.getParcelable("listview.state");
listview.onRestoreInstanceState(listViewState);
Check for null values and good luck!
You can use setRetainInstance(true); in fragment
public class MyActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
dataFragment = (RetainedFragment) fm.findFragmentByTag(“data”);
// create the fragment and data the first time
if (dataFragment == null) {
// add the fragment
dataFragment = new DataFragment();
fm.beginTransaction().add(dataFragment, “data”).commit();
// load the data from the web
}
// the data is available in dataFragment.getData()
...
}
}
Create your Listview inside fragment - Fragment will be-
public class RetainedFragment extends Fragment {
// this method is only called once for this fragment
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.d(TAG, "onActivityCreated");
// retain this fragment
setRetainInstance(true);
// create your listview here
}
}
I would retain the state of the entire Activity by adding this line to the manifest, as a property inside the Activity tag:
android:configChanges="keyboardHidden|orientation|screenSize"
Listed below is my basic code for controlling the maps. I do some really advanced stuff later. Everything seems to work perfect, until onResume().
Here is the layout, you navigate through the app in 1 single activity, with multiple fragments. This mapFragment is contained inside of a fragment. This works fine. However when I add another fragment and push this one on the back stack, when i come back to it later, the map is unresponsive.
I tried fixing this by moving my call to setupMaps(); into the onResume(), however this caused gMaps to be null when I get it from gMaps = mapFragment.getMap(); in the setViews().
How should I handle this?
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
root = inflater.inflate(R.layout.fragment_maps, container, false);
setupMaps();
return root;
}
private void setupMaps()
{
gMaps = null;
fm = getActivity().getSupportFragmentManager();
mapFragment = SupportMapFragment.newInstance();
android.support.v4.app.FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.add(R.id.flMapContainer, mapFragment).commit();
}
#Override
public void onResume()
{
super.onResume();
mapFragment.onResume();
setViews();
}
private void setViews()
{
gMaps = mapFragment.getMap();
getData(); // initializes overlays, markers, polygons etc.
}
#Override
public void onPause()
{
mapFragment.onPause();
super.onPause();
}
Do you see anything in your logcat? I've had some issues like this before, and I believe it was related to the old map fragment's View not being removed from its parent ViewGroup before creating a new instance of it. This resulted in errors regarding a duplicate fragment.
Try removing all views from your flMapContainer before you create the new instance of the SupportMapFragment.
To implement Scott Stanchfield's solution:
call cleanFrame() when add/replace another fragment.
public void cleanFrame(){
FrameLayout FL = (FrameLayout) thisview.findViewById(R.id.myfragmentcontainer);
FL.removeAllViewsInLayout();
}
I have disabled hardware acceleration inside manifest.xml and after it everything started to work successfully:
<application android:hardwareAccelerated="false">
...
</application>
I am building a one activity-multiple fragments application. I add to the backstack after every transaction. After a couple of hiding and showing fragments and then I rotate the phone, all the fragments added on the container were restored and every fragment is on top of the other.
What can be the problem? Why is my activity showing the fragments I have previously hidden?
I am thinking of hiding all the previously-hidden-now-shown fragments but is there a more 'graceful' way of doing this?
Use setRetainInstance(true) on each fragment and your problem will disappear.
Warning: setting this to true will change the Fragments life-cycle.
While setRetainInstance(true) resolves the issue, there may be cases where you don't want to use it.
To fix that, setup a boolean attribute on the Fragment and restore the visibility:
private boolean mVisible = true;
#Override
public void onCreate(Bundle _savedInstanceState) {
super.onCreate(_savedInstanceState);
if (_savedInstanceState!=null) {
mVisible = _savedInstanceState.getBoolean("mVisible");
}
if (!mVisible) {
getFragmentManager().beginTransaction().hide(this).commit();
}
// Hey! no setRetainInstance(true) used here.
}
#Override
public void onHiddenChanged(boolean _hidden) {
super.onHiddenChanged(_hidden);
mVisible = !_hidden;
}
#Override
public void onSaveInstanceState(Bundle _outState) {
super.onSaveInstanceState(_outState);
if (_outState!=null) {
_outState.putBoolean("mVisible", mVisible);
}
}
Once the configuration changes (e.g. screen orientation), the instance will be destroyed, but the Bundle will be stored and injected to the new Fragment instance.
I had the same problem. you should check source code in the function onCreateView() of your activity.
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
if(savedInstanceState == null){//for the first time
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
FragmentExample fragment = new FragmentExample();
fragmentTransaction.add(R.id.layout_main, fragment);
fragmentTransaction.commit();
}else{//savedInstanceState != null
//for configuration change or Activity UI is destroyed by OS to get memory
//no need to add Fragment to container view R.id.layout_main again
//because FragmentManager supported add the existed Fragment to R.id.layout_main if R.id.layout_main is existed.
//here is one different between Fragment and View
}
}
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/layout_main">
You might want to try to use the replace() function rather than hide and show. I had the same problem when I started using Fragments and using the replace function really helped manage the Fragments better. Here is a quick example:
fragmentManager.replace(R.id.fragmentContainer, desiredFragment, DESIRED_FRAGMENT_TAG)
.addToBackStack(null)
.commit();