Tech Stuff: Android <include/> layout pitfalls

CoboltForge Berlin  -  May 30, 2012  -  10 Comments

This post is the first one in our tech stuff series, appearing at irregular intervals, i.e. when something interesting shows up…

This time it’s about Android XML layout re-use tricks and all too possible pitfalls (i.e. the interesting stuff). And of course the solution.

The boring stuff

As you may know, you can create re-usable UI layouts on Android by putting them into an extra XML file and then <include/> them. This way, it’s possible to re-use complete layouts, as opposed to just re-use single widgets. This definitely comes in handy when you’re developing for different screen sizes.

Thus, for a portrait layout the layout XML of our open-source VNC viewer MultiVNC looks like

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbars="vertical" >

  <LinearLayout
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:focusable="true"
  android:focusableInTouchMode="true"
  android:orientation="vertical"
  android:padding="10dip" >

    <include
    android:id="@+id/discovered_servers"
    layout="@layout/discovered_servers_element" />

    <include
    android:id="@+id/bookmarks"
    layout="@layout/bookmarks_element" />

    <include
    android:id="@+id/new_conn"
    layout="@layout/new_conn_element" />

  </LinearLayout>

</ScrollView>

which will put all three included layouts one under the other:

For a landscape tablet layout you would put the following XML in res/layout-xlarge-land:

<?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" >

  <ScrollView
  android:layout_width="0dp"
  android:layout_height="fill_parent"
  android:layout_weight="1"
  android:layout_marginRight="50dp"
  android:scrollbars="vertical" >

    <LinearLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:focusable="true"
    android:orientation="vertical"
    android:padding="10dip" >

      <include
      android:id="@+id/discovered_servers"
      layout="@layout/discovered_servers_element" />

      <include
      android:id="@+id/bookmarks"
      layout="@layout/bookmarks_element" />

    </LinearLayout>

  </ScrollView>

  <ScrollView
  android:layout_width="0dp"
  android:layout_height="fill_parent"
  android:layout_weight="1"
  android:scrollbars="vertical" >

    <LinearLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:orientation="vertical"
    android:padding="10dip" >

      <include
      android:id="@+id/new_conn"
      layout="@layout/new_conn_element" />

    </LinearLayout>

  </ScrollView>

</LinearLayout>

which will put the discovered servers and bookmarks layouts in a left pane like this:

The pitfall

Nice and all, but what happens if you decide to <include/> the same UI XML snippet several times in the same layout? Suppose we’d want to have a second bookmark list (e.g. for really really favourite bookmarks or the like…). Then our portrait XML would look like

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbars="vertical" >

  <LinearLayout
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:focusable="true"
  android:focusableInTouchMode="true"
  android:orientation="vertical"
  android:padding="10dip" >

    <include
    android:id="@+id/discovered_servers"
    layout="@layout/discovered_servers_element" />

    <include
    android:id="@+id/bookmarks"
    layout="@layout/bookmarks_element" />

<include android:id="@+id/bookmarks_favourite"
    layout="@layout/bookmarks_element" />

    <include
    android:id="@+id/new_conn"
    layout="@layout/new_conn_element" />

  </LinearLayout>

</ScrollView>

Now we have included res/layout/bookmarks_element.xml twice. For reference, it looks like the XML below. Note that there’s an ID defined.

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >

  <TextView
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:paddingBottom="10dip"
  android:paddingTop="10dip"
  android:text="@string/bookmarks"
  android:textAppearance="?android:attr/textAppearanceLarge" />

  <LinearLayout
 android:id="@+id/bookmarks_list"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:orientation="vertical"
  android:padding="10dip" />

</merge>

That has some interesting consequences: What happens if we call

LinearLayout fav_bookmarks = findViewById(R.id.bookmarks_list); // WRONG!

Which one will we get? Remember that bookmarks_element.xml is now included twice!

The answer is: If we do it this way, we will always get the first included bookmarks LinearLayout and never ever the second one (the ‘favourite bookmarks). Why? Because R.id.bookmarks_list is now ambiguous (it’s given to two LinearLayouts in the resulting XML) and will always resolve to the first match…

The solution

The solution? Resolve the ambiguity! This is done by first resolving the ID of the <include /> and then using that handle to get the wanted ID:

View bookmarks_container_2 = findViewById(R.id.bookmarks_favourite); 

bookmarks_container_2.findViewById(R.id.bookmarks_list);

This code gets thethat contains the second bookmarks list and using that handle,  R.id.bookmarks_list now resolves to the right widget!

10 Comments to Tech Stuff: Android <include/> layout pitfalls

  1. ckp
    July 22, 2012 02:36

    Thanks. Useful information.

  2. Asaf
    August 27, 2012 06:10

    Great article.
    Give a simple solution to annoying problem.

  3. Manuel
    January 16, 2013 17:00

    Hi,

    It’s a great a brief article to a important problem, it looks like easy to solve, but… i’m getting a problem recovery the “include” element, always, when i try to get the findViewById(R.id.bookmarks_favourite); i get null, i’m using fragments, but i supposes that it’s not the problem.

    Any idea?

    Code:

    fragment.xml

    merge.xml

    ….whatever without layouts, only a textview for example

    Code in fragment to get the include, in the method onActivityCreated (after onCreateView in lifecycle fragment)

    View include = this.getTabActivity().findViewById(R.id.news);
    View include2 = this.getTabActivity().findViewById(R.id.news2);

    Both are null… but if i look for the text view i can get the first one as you has commented.

    Thanks very much and if someone get the solution email me to manu_gv84@hotmail.com

  4. Pingback: Getting more XML-layouts in Listview : Android Community - For Application Development

  5. Pingback: Simple example of <merge> and <include> usage in Android XML-layouts - Android Questions - Developers Q & A

  6. Peter
    April 1, 2014 14:08

    @Manuel
    I had the same problem with the merged layout.

    <TableLayout
    android:layout_width="wrap_content"

    Then I removed the

    <TableLayout xmlns:android="http://schemas.android.com/apk/res/android&quot;
    android:layout_width="wrap_content"

    And now it workes.

  7. GSP
    April 16, 2014 13:53

    Thank you for publishing this resolution. It’s nice to know that the way I think to solve it is similar to how others have. Thank you for the research, its helpful to know that the if there are multiple views with the same id within a layout, the first occurrence in the viewgroup tree is return always and that no error occurs. saves me and others time on R&D.

    Best
    G

  8. Alexander
    July 22, 2014 16:50

    This is for everyone that got stuck like I did! This example is great, but when I was trying to findViewById to find the it was returning null

    This is because the tag does not carry an id, even if you give the merge tag an id, it doesn’t have one. The root element, the top most element in the layout file you’re including absolutely has to be a ViewGroup in order to override the id in , otherwise you cannot reference the overridden id in code.

  9. giuliocc
    August 5, 2015 04:16

    Not sure if this is entirely correct. As per Romain Guy’s blog post here (http://android-developers.blogspot.com.au/2009/02/android-layout-tricks-2-reusing-layouts.html). Adding an id to the “include” tag set the ID for the root element of the included layout object or over-rides the id of the included root object, if it’s defiend… Quote:

    “…. The above example shows that you can use android:id to specify the id of the root view of the included layout; it will also override the id of the included layout if one is defined. …”

    I was having a similar problem as you described, and could actually get a handle to the included layout using it’s overriddent id attribute in the include statement..

  10. Pingback: Android: Repetir un include y encontrar su ID con un string utilizando ciclos. | Hunabsys Blog

Leave a Comment

Your email address will not be published. Required fields are marked *

*