Tuesday 27 September 2011

Android Annoyances: Finding a View Size

I find Android development a lot more time consuming than iOS development of similar complexity, and the two main reasons for this are the unintuitive API and poor documentation. Here is one simple example.

You can create an Android user user interface either declaratively, in an XML layout, or in code. This works fine in many cases, but at some point you’ll want to do something more creative, for example to modify an existing layout dynamically depending on some options. For this, you’ll probably need to know the sizes and positions of the already existing parts of the user interface.

Considering the wide variability of screen sizes and densities of Android devices, finding out where exactly a view is located and which exactly size does it have should be an easy task, shouldn’t it?

Let’s say, I want to know the height of a view in order to do some adjustments before the user sees the activity. If we check Android online documentation, we’ll find that every View has a method named getHeight(). So this is what we can use, right? The comment says: “Return the height of your view”. Sure, this is exactly what we want.

Let’s create a standard Android project and then modify it a little bit. Here is the layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  android:id="@+id/hello"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    />
</LinearLayout>

And here is the code of the activity:

public class SizeTestActivity extends Activity 
{
    private View hello;

    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        hello = findViewById(R.id.hello);

        Log.i("In onCreate", "" + hello.getHeight());
    }
}

If you run this code though, you will discover that the height of the hello view is exactly 0. Hmmmm… Okay, Android needs some time to complete the layout, it is probably just not ready to report a view’s dimensions yet. But surely, there is a life cycle method that runs a bit later and in which we should be able to find out the view’s height. Let’s look into the documentation again.

We’ll find that the next method that runs after onCreate is onStart, and it is “Called when the activity is becoming visible to the user.”. Surely, the layout should be completed by then, and we’ll certainly find the view’s height in that method. Let’s add it to the activity:

@Override
public void onStart()
{
    super.onStart();

    Log.i("In onStart", "" + hello.getHeight());
}

Run the app, and you’ll see that the view’s height is… 0.

Okay, but there is still one more lifecycle method that runs before the user sees the interface, it is called onResume, and the documentation says about it: “Called when the activity will start interacting with the user. At this point your activity is at the top of the activity stack, with user input going to it.”. Hey, there is no way the view’s dimensions will still be unknown at that point. Let’s try:

@Override
public void onResume()
{
    super.onStart();

    Log.i("In onResume", "" + hello.getHeight());
}

Guess what you’ll see in the log? 0.

Well, maybe there is something wrong with the view that I am trying to measure? Does it have any height at all? Let’s try this: add to the layout a button:

<Button 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content"
    android:text="Check Size"
    android:onClick="checkSize"
    />

Then add to the activity a method to handle the button’s clicks:

public void checkSize(View v)
{
    Log.i("In checkSize", "" + hello.getHeight());
}

Click the button when the activity is running, and you’ll see that the view’s height is actually 29, not 0. So is there any possibility to know this before the user can interact with the activity? There is actually a solution. It’s a bit ugly, and you’ll need to spend some time searching the forums in order to find it, but it does exist. Modify the onCreate method like this:

@Override
public void onCreate(Bundle savedInstanceState) 
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    hello = findViewById(R.id.hello);

    hello.post(new Runnable() 
    {   
        @Override
        public void run() {
            Log.i("In onCreate", "" + hello.getHeight());
        }
    });
}

You will see that the height is now reported properly, and that line of code in the Runnable runs a split of a second after onResume.

Well, this is exactly what I mean by unintuitive API and poor documentation.

Saturday 10 September 2011

New Website

Finally, I decided to leave the old website Sundraw.ws where it is, with its contents, to use a new domain name for this website, and to create a completely new content here, with an emphasis on my current specialisation, mobile development.

It am planning to keep this blog updated with my findings, discoveries, and any interesting stuff that happens in my life of a Mobile Developer.