I'm following the tutorial here: https://developer.android.com/training/basics/fragments/communicating and am receiving a crash when I rotate the device to landscape, then portrait, then back to landscape. The crash occurs in the following snippet.
public void onArticleSelected(int position) {
ArticleFragment articleFrag = (ArticleFragment)
getSupportFragmentManager()
.findFragmentById(R.id.article_fragment);
if (articleFrag != null) {
// Crash happens here.
articleFrag.updateArticleView(position);
} else {
//...
}
}
}
The reason for this is that when the app is rotated back to portrait, the fragment manager is returning a fragment despite it not being present in the portrait layout. To investigate this, I created a small app containing a horizontal layout like the following:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<fragment android:name="com.example.matt.personfragments.PersonListFragment"
android:id="#+id/person_list"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<fragment android:name="com.example.matt.personfragments.PersonDetailFragment"
android:id="#+id/person_detail"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
and a portrait layout like the following:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<fragment android:name="com.example.matt.personfragments.PersonListFragment"
android:id="#+id/person_list"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
I've overridden the constructor in both fragments to log their creation.
As expected, when the app is first created I see PersonListFragment being created. When I rotate the device I see both PersonListFragment and PersonDetailFragment. So far so normal.
However, when I rotate the device back to portrait I see both PersonListFragment and PersonDetailFragment being created again. I believe this is due to the fragment being present in the saved instant state. Is this correct, and if so does that mean that the tutorial is incorrect?
Among the things that the Activity will save (actually technically the activity's FragmentManager) during a configuration change (like rotation) is Fragments.
Normally, this doesn't cause any issues, because the same fragments are used in any orientation. But in master/detail screens where the detail fragment is only visible in landscape orientation, you can run into problems.
Once the detail fragment has been created for the first time (when you rotate to landscape), that fragment will exist in the FragmentManager until you exit the activity. This is true even when you rotate back to portrait. However, the detail fragment will not be added in situations where your activity layout doesn't include that fragment.
So you can change your check from:
if (articleFrag != null)
to
if (articleFrag != null && articleFrag.isAdded())
Related
I have a ViewPager with a PagerTitleStrip which paints fragments. Each fragment has a StableArrayAdapter which fill the fragment with information.
After an orientation change, when I click on the fragments, I'm getting a crash: java.lang.IndexOutOfBoundsException: Index: 8, Size: 7
When the orientation change is produced, I can see the same fragment page as before the orientation change. But if I click on it, it produces the crash because probably the viewpager has loaded the first fragment instead of the selected fragment.
It seems that when the orientation change is produced, the activity is being recreated (I want it because I want the smart banner from admob to recreate with the new width), but the fragment probably is not being recreated or something, so probably it's pointing to a different fragment from the painted one.
How can I avoid this crash? maybe there is a way to tell the viewpager that it must load the page which is displaying? how can that be done?
I tried storing the current fragment number and selecting it when recreating but the crash is still happening:
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt("current_item", viewPager.getCurrentItem());
super.onSaveInstanceState(savedInstanceState);
}
.
.
.
//code in onCreate()
viewPager.setAdapter(collectionPagerAdapter);
if (savedInstanceState != null) {
int currentItem = savedInstanceState.getInt("current_item");
viewPager.setCurrentItem(currentItem);
}
This is my xml layout.
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/pager"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="3dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="#+id/adView">
<android.support.v4.view.PagerTitleStrip
android:id="#+id/pager_title_strip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:paddingTop="4dp"
android:paddingBottom="4dp"
style="#style/CustomPagerTitleStrip"/>
</android.support.v4.view.ViewPager>
This is the line where is crashing in the StableArrayAdapter which fills the fragments with lines
Override public long getItemId(int position) {
return Math.abs(getItem(position).hashCode());
}
You can go through below link, they have explained handling Fragments & viewPager orientation changes.
https://medium.com/#roideuniverse/android-viewpager-fragmentpageradapter-and-orientation-changes-256c23bee035
In one of scenario, I have implemented Surface View which is part of an Activity. Now I need to hide this Video view so can roam in other screen of application.
AFAIK, we can't minimise/hide Activity, what could be other approach to handle such scenario.
I come across Hangout & WhatsApp Video calling scenario, they hide video view while move back to other activity & resume when need.
I also come across Youtube mobile application, but they might be managing everything in single activity. Also found some solution available here. & here. I still need to try.
How they do. Any suggestion !
Simple answer: Don't keep a view alive between activities. You can save and pass states/variables, but it is not smart to keep it in memory. So either :
Switch to a fragment structure and create a fragment containing the SurfaceView attached to a View and move it to the background when not needed
Or keep an instance (or the states) of the surfaceview and detach it from the attached View and reattach it when needed
I think these are the only legit ways to go for. I once did this just using a Singleton for the surfaceview and attaching/detaching it from a view when needed, but this is not the cleanest way.
Comment Pseudo code transaction
You should have the following: An Activity, FragmentA, FragmentB and VideoFragment. The Activity layout only consists about two viewholders, one that is full screen (for FragmentA and B) and the other one that is for the video view. To create fragments just right click New->Fragment->Blank. First you'll have to init the fragment in the view to do this and eventually replace FragmentA with FragmentB you could use the following code:
Fragment fragmentB = new FragmentB();
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction transaction = fm.beginTransaction();
transaction.replace(R.id.viewHolder, fragmentB);
transaction.commit();
Your activity layout should look something like this:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="nl.coffeeit.clearvoxnexxt.activities.TestActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:id="#+id/viewHolderFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F00"/>
<FrameLayout
android:id="#+id/videoHolder"
android:layout_width="300dp"
android:layout_height="200dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="16dp"
android:alpha="0.5"
android:background="#00F"/>
</RelativeLayout>
</android.support.design.widget.CoordinatorLayout>
orientation change between different layouts for landscape and portrait seems to be easy, however I cannot find a solution for my current situation.
I want to use a dual-pane layout with two fragments in landscape mode
res\layout-land\dashboard_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<fragment class="xxx.DashboardFragment"
android:id="#+id/master_frag"
android:layout_width="#dimen/timeline_size"
android:layout_height="match_parent"/>
<fragment class="xxx.TaskInstanceFragment"
android:id="#+id/details_frag"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
and a single-pane layout in portrait mode
res\layout\dashboard_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<fragment class="xxx.DashboardFragment"
android:id="#+id/master_frag"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
I want to note that the DashboardFragment consists of a ViewPager. Do not know if it is of importance.
Now, when I change the orientation from landscape (dual) to portrait (single) the app crashes, because it wants to update the details_frag. When I start the activity in portrait, it works, only switching from landscape to portrait makes problems.
In the activity there is the following code:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dashboard_activity);
Fragment frag = getSupportFragmentManager().findFragmentById(R.id.details_frag);
mDualFragments = frag != null;
/* .. */
}
Now the FragmentManager still finds the details fragment when I switch to portrait mode and thus mDualFragments is still true but should be false, which leads to a later call to a method in the details fragment.
So, why is this? :)
Regards,
Konsumierer
Because findFragmentById not only returns the fragment in the current layout, but also previously displayed fragments. Try this instead:
mDualFragments = frag != null && frag.isInLayout();
I read some questions like it, but I dont understand well what should I do in my hiearchy?
I have
<fragment
android:id="#+id/tabs"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
class="com.****.****.ui.TabsFragment" />
for tab menu on bottom of screen
and
<FrameLayout
android:id="#+id/details"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_above="#+id/tickerLL"
android:layout_below="#id/header"
android:background="#drawable/background" />
for other will created fragments.
I have 9 tab menus, everyone is a fragment.
Some fragments's landscape are another activity. And I rotate back phone, fragments are working well. But if I rotate fast after and after, My fragment Activty, TabsFragment and and other created fragments are recreated and my fragments are overlapping.
I manage my fragments in TabsFragment. And they works dynamicly, my abstract fragment class, has a previous fragment and subfragment. When I press back, previous fragment closes its subfragment and null it.
What should I do?
During a configuration change, Android restores or recreates all fragments that the old activity had when setting up the new activity. Hence, you need to take that into account when working with the new activity instance -- do not assume that you are starting with a clean slate.
I made two layout files - one for portrait and one for landscape. Here for portrait:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<fragment
android:id="#+id/fragment_newslist"
android:name="com.app.NewsListFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" >
</fragment>
</LinearLayout>
Here for landscape:
<fragment
android:id="#+id/fragment_newslist"
android:name="com.app.NewsListFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" >
</fragment>
<fragment
android:id="#+id/fragment_viewnews"
android:name="com.app.ViewNewsFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2" >
</fragment>
Then I created an Activity which loads the layout in the onCreate() method. So far, this works fine of course. This Activity doesn't contain more code than that.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_news);
}
Inside the NewsListFragment class I am checking if the ViewNewsFragment is available. If not and the user tapped a ListItem a new Activity (that is ViewNewsActiviy) will be started. If it is available the data will show in the existing fragment. So there are two classes: 1. ViewNewsActivity and 2. ViewNewsFragment
But what I actually want is to change to layout on orientation changes. When the device is turned from portrait to landscape I want to have the typical Dual-Pane layout and if it's turned from landscape to portrait I want to show the list solely and the details should be viewed as separate "view".
But how to do this? Till now it works fine when you start the app either in landscape or in portrait. But when you change the orientation the layout remains as initially set.
I really appreciate any help :)!
Thank you very much!
Jens
But how to do this? Till now it works fine when you start the app either in landscape or in portrait. But when you change the orientation the layout remains as initially set.
Android will automatically destroy and recreate your activity on an orientation change, and so each call to onCreate() will get the right layout.
If that is not happening for you, then you did something to stop it, such as by adding an android:configChanges attribute to the <activity> in the manifest.