Orientation change from dual-pane to single-pane - android

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();

Related

Fragment being created despite not being in the layout

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())

Android - Recycler view resizes when app is on background

I have this very weird bug on my app. Every time I open my app my view works perfectly, but when I try to put my app in background and open my app again, my recycler view resizes.
Here's an image to describe it properly:
This is the correct image:
This is the image that messes up my recyclerview when the app is on background, then I open it again.
There are also times that all images in my recyclerview are gone. Here's a screenshot:
This is my onResume() code:
public void onResume()
{
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment instanceof MenuFragment)
{
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.remove(fragment);
transaction.commit();
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, fragment);
transaction.addToBackStack(null);
transaction.commit();
}
}
This is my view pager layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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:background="#drawable/app_background">
<android.support.constraint.ConstraintLayout
android:id="#+id/action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<include
android:id="#+id/nav_bar"
layout="#layout/home_screen_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
<com.yotadevices.widget.RtlViewPager
android:id="#+id/menuFragment_viewPager"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#android:color/transparent"
app:layout_constraintBottom_toTopOf="#+id/buttons"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/nav_bar"/>
<include
android:id="#+id/buttons"
layout="#layout/navigation_layout"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/menuFragment_viewPager" />
</android.support.constraint.ConstraintLayout>
Here's my list layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
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:id="#+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/transparent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
What do you think is the problem here?
PS: The bug occurs only on selected phones. Not all. We've tested this on Samsung Galaxy s8+ and it works well.
I have solved the problem by breaking part the included layouts. So instead of adding an included layout, I just transferred all the included in to the main XML layout.
Even though this fixed the problem, i still do not know why the bug occurred in the first place.
If you are using only single child view, it's better to use FrameLayout. Then in RecyclerView use match_parent parameters in layout attribute, so your code should look like this:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
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:id="#+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/transparent">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
By the way my best method to debug scenarios like this is to write android:background="#0000FF" in every view, to see how it inflates.
You don't need to first remove the old fragment if you are going to call replace(R.id.container_id, newFragment);
You don't need to call commit after every change (remove(), replace(), etc.), call all your changes then end it with a commit()
If your activity did not lose/change its state (e.g. rotate), then you are just removing and adding the same fragment for no reason
You are checking if it is a MenuFragment in the if statement, then, oddly enough, if the condition holds, you go and re-add the MenuFragment, when you should probably replace it if it's not a MenuFragment
if (!(fragment instanceof MenuFragment)) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
Fragment menuFragment = new MenuFragment();
// pass any data...
transaction.replace(R.id.fragment_container, menuFragment);
transaction.addToBackStack(null);
transaction.commit();
}
Adding Log.d(TAG, "method name") in your onResume(), onPause() might help you know what's happening.
Also, checking the view hierarchy in Tools > Layout Inspector might also help.
Enabling Settings > Developer Options > Show Layout Bounds on your emulator might also help.
Let me know if I had anything wrong. Also let me know if any of this works for you.
To start off, declare your layouts in a more specific way rather than using root elements that are certainly not used.
Here's the list layout:
<android.support.v7.widget.RecyclerView
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:id="#+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Second, the onPause is mainly responsible for saving the app state automatically if you don't have much to implement. It is best to remove the onResume and let it take care of itself.
keep width of recyclerview as match_parent and height as wrap_content,
after that ,for your item layout,put height and width as a fixed value,such as 50dp or 70dp, which can keep the item's height and width same in any mode u want.
i hope this helps,if this answer is not helpful,please ignore this answer.
You must use a subclass of android.support.v4.app.FragmentStatePagerAdapter to load Fragments rather then putting code in on resume.
public class PagerAdapter extends FragmentStatePagerAdapter {
private static final int ITEMS = 4;
PagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
switch (i) {
case 0:
return Fragment1.newInstance();
case 1:
return Fragment2.newInstance();
case 2:
return Fragment3.newInstance();
case 3:
return Fragment4.newInstance();
}
return null;
}
#Override
public int getCount() {
return ITEMS;
}
}
The new instance method of each fragment must be:
public static Fragment1 newInstance() {
return new Fragment1 ();
}
In your activity write following in onCreate method:
PagerAdapter pagerAdapter = new PagerAdapter(getSupportFragmentManager());
//rtlViewPager -- object of RtlViewPager
rtlViewPager.setAdapter(pagerAdapter);
rtlViewPager.setOffscreenPageLimit(4);

How to make fragment pick the portrait view

My app for phones has a pager with the main view and the premium features view when swiping right, in landscape mode it has a slightly modified version to fit the landscape orientation.
Now i have to make the tablet UI, and I'd like to show both views on one screen, I've already managed to show them, but the tablet in landscape mode is picking up the fragments from layout-land instead of the portrait layout, and I need it to pick the portrait ones to show side by side.
How can i make the fragment pick the portrait version even if the tablet is in landscape without creating duplicate layouts?
My fragments look like this
public class PremiumFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ViewGroup premiumView = (ViewGroup) inflater.inflate(
R.layout.premium_features, container, false);
return premiumView;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Log.d(TAG, "onView created ran");
Intent intent = new Intent("setup");
LocalBroadcastManager.getInstance(getBaseContext()).sendBroadcast(intent);
}
}
It's been a while but for anyone interested in this matter:
If I understand correctly, you need to have a portrait layout, a landscape layout (for mobile for example) and a different landscape layout for tablets which matches the portrait one. There's not that much duplicity involved. You can include an independent non-qualified layout in your qualified layouts.
Lets say you have these layouts:
layout-land/my_fragment.xml
layout-port/my_fragment.xml
layout-large-land/my_fragment.xml
layout/my_fragment_content.xml
layout-port/my_fragment.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">
<include
layout="#layout/my_fragment_content"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout >
layout-land/my_fragment.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">
//something completely different
</FrameLayout >
and layout-large-land/my_fragment.xml (contents are the same as portrait one):
<?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">
<include
layout="#layout/my_fragment_content"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout >
There is some minimum duplicity involved but the only important thing is that you have no reason to ever touch layout-port/my_fragment.xml or layout-large-land/my_fragment.xml because all vital information is contained in a single file layout/my_fragment_content.xml.
This topic is more thoroughly analysed in this article that the author probably read aleady:
Android Developers: Building a Flexible UI

RoboFragments in layout.xml

I've been trying to get Roboguice to work with fragments declared in a <fragment> block in the layout file and then injected into the activity, but, although the fragment exists somewhere off screen (an EditText in the fragment takes focus and fires events), It is not visible. Does RoboGuice support what I'm trying to do here or should I go about it a different way?
code:
<?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="vertical" >
<fragment
android:id="#+id/myFragment"
android:name="com.example.MyFragment"
android:layout_height="0dp"
android:layout_width="0dp"
android:layout_weight="1" >
<!-- Preview: layout=#layout/my_fragment -->
</fragment>
</LinearLayout>
Java:
#ContentView(R.layout.participant)
public final class Main extends RoboFragmentActivity {
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#InjectFragment(R.id.myFragment) private MyFragment myFragment;
}
Solved the issue, but for anyone else looking - The issue at hand was completely unrelated to RoboGuice, which allows fragment injection exactly as shown above. Rather the issue was that both of my layout dimensions for the fragment were set to 0dp, ensuring that my fragment would never be rendered.

Android - handling orientation changes when using fragments

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.

Categories

Resources