Tech Stuff: Android <include/> layout pitfalls

CoboltForge Berlin  -  May 30, 2012  -  4 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!

4 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

Leave a Comment

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

*