3

I am currently switching to a single activity architecture that will manage loading fragments in and out of a container layout. Things are going well, but I have hit a snag. I want to implement the master-detail pattern, but I can't figure out the best way to handle the layout file. There are two methods that I have thought of, but both have consequences that I either don't like or are unsure of.

  1. seperate layout-land file with a second container: The problem here is that I don't want the second container to take up space when I am not in a multi-fragment configuration, so I forsee a lot of programmatic layout param changes that could get messy.
  2. nested fragments: I could load in a special multi-configuration fragment whose layout XML contains the appropriate fragments nested within

I am mainly looking for some guidance about which approach I should take, be it the two options I have mentioned or another method that I am overlooking.

Edit: I put together a Google drawing that illustrates the two ideas I've had thus far.

theblang
  • 10,215
  • 9
  • 69
  • 120
  • If I understand correctly, you do not only want to display the master detail pattern when you are on a tablet, you also want to display it when your phone is on landscape mode? – Emmanuel May 13 '14 at 15:59
  • @Emmanuel At the moment let's assume yes. – theblang May 13 '14 at 16:00
  • "The problem here is that I don't want the second container to take up space when I am not in a multi-fragment configuration, so I forsee a lot of programmatic layout param changes that could get messy" -- use `setVisibility(View.GONE)`. – CommonsWare May 14 '14 at 14:41
  • @CommonsWare No, but I will always have to constantly change the visibility depending on which fragment I am loading and if I add in another multi-fragment configuration in addition to master-detail I may end up with more containers that will have to have their visibility constantly managed. So I guess it just made me second guess myself with this architecture decision. – theblang May 14 '14 at 14:47
  • "but I will always have to constantly change the visibility depending on which fragment I am loading" -- then this isn't really master-detail. The determination of whether you are showing both master and detail, or just one, is based on screen size, not screen size plus other criteria, with the classic master-detail pattern. I am not saying that what you want is incorrect, but you do not really mention this requirement in your question. – CommonsWare May 14 '14 at 14:54
  • @CommonsWare Yeah, I think my thought process was that I would need master-detail sometimes (ListView screens), but not all of the time (About screen). Sorry, maybe I should have made that more clear. So if I go beyond a master-detail configuration, and imagine lots of different multi-fragment configurations when in landscape, or on a tablet, or whatever, then I imagine having to manage a lot of visibilities. This made me really second guess myself. – theblang May 14 '14 at 15:04
  • 1
    "I think my thought process was that I would need master-detail sometimes (ListView screens), but not all of the time (About screen)" -- oh, now I get it. I definitely wouldn't do single-activity for all of that. Having one activity to replace the two-activity approach for pure master-detail is reasonable. Saying that **the entire app** is one activity is, IMHO, not worth the headache. Use a single activity where tight coupling makes multiple activities difficult. Use distinct activities where there is no such need for tight coupling. – CommonsWare May 14 '14 at 15:07
  • I definitely agree about the headache comment. What makes me so sad though is that loading in a fragment via `FragmentTransaction` is **so much** faster than launching a new Activity that only contains that same fragment. I am actually trying to analyze a `traceview` to figure out why that is. But the speed increase is really what motivated me to try a single activity approach. – theblang May 14 '14 at 15:11

2 Answers2

0

Let's break down the question in a couple parts:

1) What type of device is being used? (i.e. tablet or phone)

2) If I am a phone, what is my orientation?

In order to identify if you are running on tablet or a phone, you can use the sw800dp bucket (hopefully they do not come up with a 800dp wide phone) to let the framework inflate a layout if the device's width is at a minimum 800dp. This layout file will contain either 2 static Fragments or two containers (ViewGroups to place the Fragments dynamically). You can figure out if you are in a "tablet" configuration if trying to get a hold of either one of the two Fragments or two Viewgroups does not return null.

If you do get null then you know that your device is a phone. You can figure out if you are in landscape or portrait mode using getResources().getConfiguration().orientation.

Emmanuel
  • 13,083
  • 4
  • 39
  • 53
  • Yes, this I know. The problem is, how should I construct the layout files that are in these buckets? For example, I have a `sw800dp` layout file with two `containers`. What about when I am on a screen that isn't `master-detail`? I don't want the second container taking up space, nor do I want to constantly adjust `layout params` programatically. – theblang May 13 '14 at 16:17
  • If you are on a screen that isn't supposed to be master-detail, you get a reference of the `ViewGroup` that contains the 2 `Fragments` (because you will have to add them dynamically) and then you use `replace()` of `FragmentTransaction` to load the `Fragment` that contains the desired non-master-detail layout. – Emmanuel May 13 '14 at 16:21
  • Okay, so your suggestion would be to go with the second idea that I had, which was to use nested fragments within fragments? – theblang May 13 '14 at 16:26
  • The `Fragments` will not be nested. They will be at the same hierarchical level in the case of the master-detail pattern. One `Fragment` for the list and another `Fragment` for the details. The new non-master-detail `Fragment` will use the container that holds the two master-detail `Fragments` to replace these two `Fragments` with one new `Fragment`. – Emmanuel May 13 '14 at 16:26
  • Okay, so this is something I have wondered about. Can a container have multiple fragments in it? – theblang May 13 '14 at 16:31
  • Yes, e.g. this is perfectly possible: – einschnaehkeee May 13 '14 at 16:35
  • @einschnaehkeee If you do that though, how do you control the weights? – theblang May 13 '14 at 16:36
  • Yes, you can have many `Fragments` per container. To be more specific in my answer, you probably will have to use `remove()` instead of `replace()` because you will want to `add()` the new non-master-detail `Fragment` to a different root container. – Emmanuel May 13 '14 at 16:36
  • @einschnaehkeee Please post this code as an update to your answer so it is more readable. – Emmanuel May 13 '14 at 16:38
  • @Emmanuel If I add the `non-master-detail fragment` to a `different root container` though, I'm having to hide the current container programatically by altering its `layout params` right? I would like to avoid that if possible. – theblang May 13 '14 at 16:57
  • I do not think you do, because you will be adding the `Fragment` to the root `ViewGroup` that contains the `Fragments`, it will probably be set to `match_parent` for height and width – Emmanuel May 13 '14 at 17:00
0

EDIT: From the discussion in the comments, I see that your major problem seems to be the handling of the orientation change.

  1. As Emmanuel said, when a configuration change occurred (and you're basically back to onCreate(), you need to check for getResources().getConfiguration().orientation. According to that, you have to check if you need to remove or add fragment/s. So e.g. you have a smartphone in portrait, only one Fragment is present. Then you change to landscape, you have to either

    • add a Fragment into old layout or
    • remove whole fragment stack and add new layout
  2. There's no way out of dealing with layout param changes (like setting visibility from GONE to VISIBLE, etc.), if you want to use an old layout for your Fragment container. If you don't use your old layout and create a new one, you have to save the UI state, so that the user gets his selections, etc. back (you know this stuff probably - use Intents, etc.).

I prefer to create a new layout for a new configuration and recreate the state, so I do something like this in onCreate():

if (CheckApp.isScreenBig()) {
        setContentView(R.layout.frag_multi);
    } else {
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            // search and remove former fragments, if there were any
            setContentView(R.layout.frag_multi);
        } else {
            if (getSupportFragmentManager().findFragmentById(android.R.id.content) == null) {
                getSupportFragmentManager().beginTransaction().add(android.R.id.content, new FragSingle()).commit(); // single Fragment
            }
        }
    }

Detect Multi-configuration for example like this Determine if the device is a smartphone or tablet?

In your Activity, start your configurations in onCreate

if (App.isScreenBig()) {
        setContentView(R.layout.frag_multi);
    } else {
        if (getSupportFragmentManager().findFragmentById(android.R.id.content) == null) {
            getSupportFragmentManager().beginTransaction().add(android.R.id.content, new FragMainPaging()).commit(); // single Fragment
        }
    }

Depending on your overall design, you don't need to do anything more. If you need to interact between your Fragments, you can propagate changes through your Parent Fragment's/Activity's ViewPager or a Controller or whatever you have in place.

I usually go with nested fragments, and use something like that to communicate changes

((FragManagedPaging) getParentFragment()).forwardChanges(NotifyState.NOTIFY_ALL, result);

NotifyState is an enum indicating if the change is relevant for all Fragments or certain ones (I pass in the Class names if I want certain ones) or if I want to create a new Fragment for a certain event (NotifyState.ADD), etc. The parent fragment hosts the other Fragments (also a ViewPager, if needed) and forwards changes, if there are other Fragments present. The other Fragments have a notify method, that indicates to do work.

void onChangeOccurred(Object... args) {
// look what changed, what work to do in args, etc.

But overall it's up to you to decide.

Example of multi pane layout:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:baselineAligned="false"
    android:orientation="horizontal" >

    <fragment
        android:id="@id/frag_travel_list"
        android:layout_width="0dp"
        android:layout_height="fill_parent"
        android:layout_weight="2"
        class="de.einschnaehkeee.travel.check.frag.FragTravelBags" />

    <fragment
        android:id="@id/frag_travel_items"
        android:layout_width="0dp"
        android:layout_height="fill_parent"
        android:layout_weight="3"
        class="de.einschnaehkeee.travel.check.frag.FragBag" />

    <fragment
        android:id="@id/frag_choose_items"
        android:layout_width="0dp"
        android:layout_height="fill_parent"
        android:layout_weight="3"
        class="de.einschnaehkeee.travel.check.frag.ChooseItemsFragment" />

</LinearLayout>

For configurations with multi pane layouts, use appropriate layout directories, like layout-land/frag_multi.xml, etc.

Community
  • 1
  • 1
einschnaehkeee
  • 1,858
  • 2
  • 17
  • 20
  • In the question's comments, he says that he also wants to use a master-detail pattern when his phone is on landscape mode, not only when he is running on a tablet. – Emmanuel May 13 '14 at 16:45
  • @Emmanuel well he doesn't need anything to do except put a layout inside layout-land the rest stays the same, because either there is a Fragment to get the propagated change or not. :-) – einschnaehkeee May 13 '14 at 16:48