Monday 11 January 2016

Why Xamarin?

These days, I am investing my time and money into studying Xamarin. If someone told me a year ago that I would be doing this, I wouldn’t believe them. And yet here I am, a student of Xamarin University. Why?

Reason 1. I am sick and tired from stretching myself between the two major mobile platforms, and from having absolutely no time left for the third, maybe not so popular but still very interesting platform. I started developing for iOS (at that time iPhone SDK) back in 2008. Since I have a Java background, it was natural for me to adopt Android as well, as soon as it appeared. For several years since then I endured the struggle to keep myself up-to-date on both platforms. It was a lot of fun, but these days an idea like that is just an utopia. Although visually iOS and Android come closer and closer to each other, from the development point of view they are very, very different, each of them moving with an increasing speed in its own direction.

Reason 2. Swift. I would actually suggest to call it Slug, it would be more appropriate, as it is so sluggish. I was present at its birth, as I was an attendee of WWDC 2014 when Swift was announced. At first it looked like a good alternative to the old friend Objective-C. It still does. However, the way how Apple is approaching the development of the new language is quite horrible. Here is just one example: I completed a project in June 2015 using a version of Swift that was current then, 1.2. In November, when handing over the code, I tried to compile it and got 64 errors. The only reason was that the current version of Swift was then 2.1, and a large number of breaking changes was introduced. Most of the errors were about me using the old ‘println’ rather than the new ‘print’. I should also mention that Xcode is becoming buggier and buggier, approaching Eclipse in this respect, whereas Intellij-based Android Studio is doing quite well.

The bottom line is that if you want to use Swift, you need to keep learning it all the time, updating yourself to the most recent version of the language, and updating your code every few months in order to not break. Quite a crazy endeavour, especially if you attempt to be able to develop for Android as well. And now there are rumours that Android will adopt Go in place of Java… Of course, there is an option to completely ignore Swift and to stay with Objective-C, but then you won’t be competitive on the jobs market. In this situation, a single language development environment, like Xamarin, is becoming more and more attractive. After all, C# isn’t that different from Java, and if it is different then in a good way. To be honest, when I worked as a Java developer, I wished I could use C# instead, and I played with the language in my free time.

Reason 3. My CV. When in 2010 I was insisting on creating an Android version of the British Gas app, I thought that my ability to develop for both platforms will give me an edge on the jobs market. I was very wrong. A typical potential employer, when looking for a contractor, prefers to have a one-trick-pony. And that’s understandable: a contractor is a disposable resource. So when I submit my CV for an iOS position these days, it looks ridiculous, as it has a lot of Android stuff in it, and vice versa. A potential employer believes that if I can develop for both platforms then I can’t do it well for any specific platform.

I believe that with Xamarin my versatility will stop being a problem. After all, there is Xamarin.iOS, and there is Xamarin.Android, and to get a Xamarin certification you are supposed to know both. Sounds good to me. So I am looking forward to adding the Certified Xamarin Mobile Developer badge to my CV.

Reason 4. My hobby apps. I submitted to the App Store my very first iPhone app, iLuna, in February 2009, and it was doing well for a number of years, with a few minor updates along the way. When iOS 8 was released, it just stopped working. Of course if I really cared about the app, I could test it with a pre-release version of iOS, but I didn’t. I have so many other things to do that if something works I just don’t touch it. And then there were a few months of agony when I was rebuilding iLuna from scratch. That’s for iOS. iLuna for Android keeps working all these years, with all those changes to the platform, but it looks really old, and it was actually never developed to the same degree of sophistication as the the iOS version. I just don’t have enough time.

I have a similar situation with my second most popular app, Hours. The Android version of it is old and tired, and I don’t have time to complete the new version for iOS either. These are two different jobs. Where do I find time for them?

In fact, I have about a dozen of different hobby apps on the iOS side, and only a few on the Android side. It just so happened, because Android Market (now Google Play) was underperforming for years. It is better now, but again there is no time to create the dedicated Android versions of all of my apps.
And now meet iOS 9. As soon as it was released, I started receiving messages saying that a number of my less popular apps, which kept just working for years, do not work anymore. Oh, thank you, Apple! Now I need to find time to fix those apps too. Yeah…

In addition, I always wanted to re-create my apps for Windows Phone as well. I even have a Lumia phone waiting quietly in the corner as a would-be test device. But unfortunately I never managed to find time to learn properly yet another mobile platform. What a shame.

Now, with Xamarin.Forms, I have a fighting chance to re-create all my hobby apps, and to have them for all three platforms at the same time, with the same feature set. Looks like a really attractive proposition for me. And I am already working in this direction.


One of the challenges for me when learning Xamarin was that I need to know C# properly. I did know some of it in the past, but the language keeps developing, and I need to be thoroughly familiar with its most important features. So in addition to the posts describing my Xamarin journey, I decided that I will use this blog to summarise my C# learning experience. Just to keep all the findings in one place, and to be able to easily review them. It’s going to be a series of posts under a header like “C# for Java Developers”. Stay tuned, if you are interested.

Friday 16 January 2015

Animated Accordion for Android 4+. Part 1: Static Version


In the company where I work, designers strongly favour accordion-like interfaces, so I have to implement them in mobile apps time and again. On iOS, it is all about inserting cells into UITableView or removing them — a process that can become quite tedious, especially when more than one level of accordion is needed. On Android, however, creating an accordion doesn't take much effort.

This is only true for Android 4+. A few years ago, when working on the BP UK app, I did manage to create an animated accordion-like menu working on Android 2.1 and above, but the code was horrible. If you are interested, let me know and I'll show how I solved the problem. Thankfully, these days many Android developers can forget about the older versions, so here I am going to show what it takes to create a simple accordion interface for Android 4+.  

This is what a typical accordion looks like:


There are several sections. Tapping on a section header toggles that section: opens if it was closed, exposing the items contained in that section, or closes if it was opened. Typically, if one of the sections was opened, all the others should close; however, your UX guys might wish to have more than one section opened at a time.

There is a standard Android component, ExpandableListView, that offers this kind of UI solution, but, in my opinion, it has some deficiencies:
  • Most importantly, it is not animated, changes happen abruptly.
  • You are limited to a two-level list. 
  • Arguably, (I am not an expert in the usage of this particular component but I saw a fellow developer struggling with it) you might not have sufficient control over which section is open and when.
Now let's see how we can achieve a similar, or even better result, without using complex components. All we are going to do is manipulate nested LinearLauouts.

I created an Android Studio project and checked it into a Github repository. I am going to tag that code as we go along.

At first, let's create the simplest possible, static version of the accordion. It can be useful in those cases when the whole structure of the accordion is known at the outset. The key thing here is the layout file, which is activity_main.xml in the initial project:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:animateLayoutChanges="true"
        android:orientation="vertical"
        >

        <TextView
            android:id="@+id/header1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_green_dark"
            android:gravity="center_vertical"
            android:minHeight="?android:attr/listPreferredItemHeight"
            android:padding="20dp"
            android:text="@string/section_1"
            android:textColor="@android:color/white"
            android:textSize="33sp"
            />

        <LinearLayout
            android:id="@+id/section1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:visibility="gone"
            >

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/white"
                android:gravity="center_vertical"
                android:minHeight="?android:attr/listPreferredItemHeight"
                android:padding="20dp"
                android:text="@string/item_11"
                android:textColor="@android:color/holo_green_dark"
                android:textSize="22sp"
                />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/white"
                android:gravity="center_vertical"
                android:minHeight="?android:attr/listPreferredItemHeight"
                android:padding="20dp"
                android:text="@string/item_12"
                android:textColor="@android:color/holo_green_dark"
                android:textSize="22sp"
                />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/white"
                android:gravity="center_vertical"
                android:minHeight="?android:attr/listPreferredItemHeight"
                android:padding="20dp"
                android:text="@string/item_13"
                android:textColor="@android:color/holo_green_dark"
                android:textSize="22sp"
                />
        </LinearLayout>

        <TextView
            android:id="@+id/header2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_green_dark"
            android:gravity="center_vertical"
            android:minHeight="?android:attr/listPreferredItemHeight"
            android:padding="20dp"
            android:text="@string/section_2"
            android:textColor="@android:color/white"
            android:textSize="33sp"
            />

        <LinearLayout
            android:id="@+id/section2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:visibility="gone"
            >

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/white"
                android:gravity="center_vertical"
                android:minHeight="?android:attr/listPreferredItemHeight"
                android:padding="20dp"
                android:text="@string/item_21"
                android:textColor="@android:color/holo_green_dark"
                android:textSize="22sp"
                />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/white"
                android:gravity="center_vertical"
                android:minHeight="?android:attr/listPreferredItemHeight"
                android:padding="20dp"
                android:text="@string/item_22"
                android:textColor="@android:color/holo_green_dark"
                android:textSize="22sp"
                />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/white"
                android:gravity="center_vertical"
                android:minHeight="?android:attr/listPreferredItemHeight"
                android:padding="20dp"
                android:text="@string/item_23"
                android:textColor="@android:color/holo_green_dark"
                android:textSize="22sp"
                />
        </LinearLayout>

        <TextView
            android:id="@+id/header3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_green_dark"
            android:gravity="center_vertical"
            android:minHeight="?android:attr/listPreferredItemHeight"
            android:padding="20dp"
            android:text="@string/section_3"
            android:textColor="@android:color/white"
            android:textSize="33sp"
            />

        <LinearLayout
            android:id="@+id/section3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:visibility="gone"
            >

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/white"
                android:gravity="center_vertical"
                android:minHeight="?android:attr/listPreferredItemHeight"
                android:padding="20dp"
                android:text="@string/item_31"
                android:textColor="@android:color/holo_green_dark"
                android:textSize="22sp"
                />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/white"
                android:gravity="center_vertical"
                android:minHeight="?android:attr/listPreferredItemHeight"
                android:padding="20dp"
                android:text="@string/item_32"
                android:textColor="@android:color/holo_green_dark"
                android:textSize="22sp"
                />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/white"
                android:gravity="center_vertical"
                android:minHeight="?android:attr/listPreferredItemHeight"
                android:padding="20dp"
                android:text="@string/item_33"
                android:textColor="@android:color/holo_green_dark"
                android:textSize="22sp"
                />
        </LinearLayout>
    </LinearLayout>
</ScrollView>

At the root is a ScollView, to make sure that the content can scroll when necessary. It contains a vertical LinearLayout which, in its turn, contains three TextViews, for section headers, and three nested vertical LinearLayouts, for section contents. The essential details are that the main LinearLayout has the animateLayoutChanges attribute set to true, and that the visibility of all nested LinearLayouts is set to GONE.

Here is the code that manipulates this layout:
View section1, section2, section3;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        section1 = findViewById(R.id.section1);
        section2 = findViewById(R.id.section2);
        section3 = findViewById(R.id.section3);

        View header1 = findViewById(R.id.header1);
        header1.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                if (section1.getVisibility() == View.GONE)
                {
                    section1.setVisibility(View.VISIBLE);
                }
                else
                {
                    section1.setVisibility(View.GONE);
                }
            }
        });

        View header2 = findViewById(R.id.header2);
        header2.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                if (section2.getVisibility() == View.GONE)
                {
                    section2.setVisibility(View.VISIBLE);
                }
                else
                {
                    section2.setVisibility(View.GONE);
                }
            }
        });

        View header3 = findViewById(R.id.header3);
        header3.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                if (section3.getVisibility() == View.GONE)
                {
                    section3.setVisibility(View.VISIBLE);
                }
                else
                {
                    section3.setVisibility(View.GONE);
                }
            }
        });
    }

The code is so simple that I doubt it needs any comments. Run the app, try tapping on the section headers and see how the sections expand or collapse as appropriate. Here is how it looked on my Nexus 4, with only the first section open:



At the moment, you can expand all three sections. Can you change the code so that when one of the sections expands two other collapse?

The more realistic scenario is when the sections are known but their content has to be created dynamically — perhaps because it has to be retrieved from the server. I will show how I am dealing with that scenario in the next post.

Sunday 9 June 2013

SherlockActivity and AlertDialog Don't Work Together

I decided to refresh my knowledge of Android by porting one of my iOS apps while using all the new features of Android 4.* I might find useful. Knowing that Android documentation isn't always reliable, I selected as my guide the most up-to-date version of The Busy Coder's Guide to Android Development (I will refer to it as BCG further on). I used to rely on this book's predecessors in the past, they were quite good.

There is a catch with Android: many desirable new features were introduced to it starting from version 4, Ice Cream Sandwich, which was introduced in October 2011, more than a year and a half ago. However, as of the beginning of June 2013 there are still more than 40% of devices out there using the older, pre-4.0 versions of Android.

This is really a pain, because every developer would like to use the new advanced features of Android, but doing that he will lose 40% of the potential audience, which is clearly not acceptable.

Google made some effort to help us and provided a compatibility library with backports of many of the new features to the earlier versions of the platform. However, on some reason that library doesn't offer any solution for the ActionBar, while most developers these days would wish to use the ActionBar, and they would want their app to look consistently across all the different versions of Android.

Thankfully, a third-party library ActionBarSherlock addresses the problem, and it is well implemented and reliable, so everyone I know in the Android development world uses it.

Quite naturally, BCG recommends the AndroidBarSherlock and uses it in most examples. And that's fine. I followed the advice and now all the activities in my new app are SherlockActivities.

Everything was great until I needed an AlertDialog. I looked into BCG, and it recommends to use "the modern way to display the dialog" by using a DialogFragment. So I did that, created an implementation of the DialogFragment, and since I am targeting pre-ICS versions of the platform, I used the support library version of the fragments (android.support.v4.app.DialogFragment).

Everything went smoothly until I tried to display my new dialog. The incantation for that is
new SampleDialogFragment().show(getSupportFragmentManager(), "sample");

Unfortunately, my SherlockActivity had no idea how to get a fragment manager from a Google support library. And naturally, Google pretends that it has no idea of the ActionBarSherlock. So DialogFragment simply doesn't mix with ActionBarSherlock.

I checked the Android documentation, and it is all about the DialogFragment, as if other options never existed at all. Which is understandable, they did provide a backport of fragments for the older platforms, and the DialogFragment solution does offer some benefits compared to the old way in the situation when the device is rotated. Well, in the ideal world either the support library would offer an ActionBar backport, or everyone would be using Android 4.* by now. But in the real world we have to use ActionBarSherlock for now.

What unpleasantly surprised me though is that Mark Murphy, who in his BCG almost insists on using ActionBarSherlock, follows Google in offering only the DialogFragment solution, without even hinting that you have no way to use it with ActionBarSherlock.

Anyway, here is the solution. I had to dig it out from my old code, written more than three years ago:

AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.title))
.setMessage(getString(R.string.message))
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() 
{
    @Override
    public void onClick(DialogInterface dialog, int which) 
    {
     // Handle positive button    
    }
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() 
{
    @Override
    public void onClick(DialogInterface dialog, int which) 
    {
        // Handle negative button
    }
}).show();

Thursday 9 February 2012

Sencha Touch 2 + PhoneGap = Blank Web Browser Window Opening On An Android 3.2 Tablet

I was trying to make a Sencha Touch 2 app with some CSS3 animations in it performing reasonably on the Galaxy Tab with Android 3.2. When running the app in the Android web browser there were two major problems: the view was flashing after every transition quite unpleasantly and some bars of the chart were appearing/disappearing randomly, there seemed to be some problem with the page refresh.

I tried creating a simple native Android app and running the Sencha Touch app in a naked WebView. That didn’t work at all, the native WebView is more or less useless without an additional tweaking.

Finally, since I wanted to have an APK as an end result anyway, I’ve wrapped the app in PhoneGap 1.3. Immediately, it started behaving much-much better, the old problems were gone and the performance of the animation was close to native, and close to that on the iPad.

All this at the cost of a new major problem though: the very first interaction with the app after it had started was always causing a blank web browser window to appear, with about:blank URL in it. Since this window covered the app, the impression was like the app suddenly disappeared, like if it had crashed. The app was running just fine though, and after retrieving it from underneath the web browser one could appreciate all the benefits of PhoneGap wrapping.

While I was investigating this problem, PhoneGap 1.4.1 was released, but it didn’t fix the ‘about:blank’ problem.

I was able to find the reason for the about:blank window in the source code of PhoneGap. It is a minor bug that wouldn’t matter in most normal circumstances. There is a bigger problem that causes the minor bug to manifest, and I still don’t know whether that mysterious bigger problem caused by Sencha Touch or by Phone Gap. However, I fixed the minor problem, created a custom PhoneGap build, and everything works just fine now.

Below, I will describe the details of my investigation. I will also submit this discourse to Phone Gap in a hope that they will fix the bug in one of the coming releases.

I was debugging on a device, reproducing the problem and checking what LogCat says at that time. The message that corresponded to the blank browser window appearance was quite obvious:

I/ActivityManager(80): Starting: Intent { act=android.intent.action.VIEW dat=about:blank cmp=com.android.browser/.BrowserActivity } from pid 404

This means that some process has sent to Android an Intent requesting to view a specific URL, and the URL was specified as “about:blank”. The natural Android’s reaction to such a request is to start its web browser and to display in it a blank page. The next question was: which exactly code sends such an Intent? I obtained the source code of Phone Gap and searched through it. There is only one place in the code where such an Intent is created, in DroidGap.java:

public void onDestroy() {
    ...

        // Load blank page so that JavaScript onunload is called
        this.appView.loadUrl("about:blank");

    ...
}

A question that could be asked here is why the onDestroy() method of the DroidGap activity is invoked at the first interaction with the app, and this is exactly where the mysterious bigger problem manifests itself. Since the app continues to run successfully, I guess that another instance of DroidGap is being quietly created and destroyed behind the scenes. It might have been totally unnoticed if not a minor bug that I had discovered, so let me concentrate on the bug for now and report the other findings later.

The problem with PhoneGap is that the above line of code doesn’t actually achieve what it is expected to achieve according to the comment, i.e. it doesn’t load a blank page into this.appView, where this.appView is a reference to the instance of the WebView that is used by PhoneGap to display the app.

This is because the appView has a WebViewClient attached to it (GapViewClient or CordovaWebViewClient depending on the version of the source code). That client has the shouldOverrideUrlLoading() overriden, which means that each time the WebView is about to load a URL, that method is invoked first. If the method returns false, the WebView proceeds with loading whatever URL was given to it, otherwise it does nothing and it is up to the developer to do any appropriate coding to react to the URL.

If we look inside the shouldOverrideUrlLoading() method, we’ll see that it checks for some special cases, like URLs beginning with ‘mailto:’, ‘sms:’ and so on, but ‘about:’ isn’t one of those special cases. For those URLs that aren’t special, the WebViewClient does the following:

// All else
else {

    ...
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setData(Uri.parse(url));
            ctx.startActivity(intent);
    ...
}
return true;

Basically, it creates an Intent, attaches to it the URL (which the WebView was intending to load) and throws the Intent to the Android OS to handle (and so open a blank browser window). Then it returns true which tells the WebView something like “don’t bother handling this URL, I’ve already done everything that was needed”.

So as a result of this line of code:

this.appView.loadUrl("about:blank");

WebView does nothing while WebViewClient passes “about:blank” to Android to handle. Thus the developer’s intent to “load blank page so that JavaScript onunload is called” is circumvented, and this is a bug.

To fix the bug, I’ve added to shouldOverrideUrlLoading() the following one-liner:

else if (url.startsWith("about:"))
{
    return false;
}

So that for a URL like “about:blank” WebViewClient tells WebView: go on and load this URL, I don’t care.

I’ve created a custom PhoneGap JAR with this patch and it works fine, the dreaded blank page doesn’t appear anymore.

Now a few thoughts on what’s going on behind the scenes, why a DroidGap Activity is being destroyed invisibly. Here is the complete logging that accompanies the appearance of the problem:

W/webview(404): Stale touch event ACTION_DOWN received from webcore; ignoring
D/DroidGap(404): DroidGap.startActivityForResult(intent,-1)
I/ActivityManager(80): Starting: Intent { act=android.intent.action.VIEW dat=about:blank cmp=com.android.browser/.BrowserActivity } from pid 404
W/WindowManager(80): Failure taking screenshot for (230x135) to layer 21010

When I tried to find out which code could be calling startActivityForResult() on DroidGap, I’ve found myself digging into the Capture class and methods like captureImage(). So it looks like some failed attempt of capturing a screenshot. But why would it happen when the only thing I was trying to do is to press a button (any button) on the app right after its startup?

If anybody knows an answer to this question, please let me know!

Friday 4 November 2011

Android Annoyances: RadioGroup

I have a RadioGroup with two RadioButtons in it. In the code, I want to know which of the buttons was selected. Here is the way to find out that information using Android API:

int index = group.indexOfChild(group.findViewById(group.getCheckedRadioButtonId()));

Pure elegance…

Tuesday 25 October 2011

Android Annoyances: Text Length

It is incomprehensible to me why there are properties that you can set in an XML attribute but not in code. Here is one quite obvious example.

I have an EditText control for text input and I want to limit the length of input to just two characters. No problem, I can do that easily in XML layout:

android:maxLength="2"

The problem is: in some circumstances I want to limit input to only 1 character, and can only do that in code. Surely enough, if there is such an XML attribute, there should be a setMaxLength() method, right? Wrong. The guys who designed Android API just didn’t think it might be needed.

Rubbish.

Saturday 22 October 2011

Quartz 2D Article

When developing for iOS, a few times I was working on a bespoke graph, either copying a design created by someone else or implementing strict design guidelines. I decided to share the experience I've gained then in a series of articles. Here they are:

Part 1. Preparing the environment, drawing the grid lines.
Part 2. Drawing a bar graph, with gradient fill.
Part 3. Drawing a line graph, with gradient fill.
Part 4. Making graphs interactive.
Part 5. (to be published soon) Drawing text on graphs (labels etc).