The Continued Dreadfulness of LinkedIn

Posted in The Internet by Dan on January 20th, 2016

For months (years?) I have been baffled by the current iteration of LinkedIn’s timeline. Aside from it’s sharp-edged, drop-shadowed ugliness, there seemed to be no logic to what updates it would show and in what order. Sometimes you get stuff from today, sometimes it’s from last week. Worse still, every time you refresh the page you get something different. It seemed to me that the updates should be fixed in reverse chronological order as on other social media sites.

Today I received a brief glimpse of enlightenment from the helpful @linkedinhelp Twitter account. Apparently the timeline shows you the “most engaged updates” by default – although I’m not sure why these change constantly. There is a way to get it to list updates in reverse chronological order but it’s hidden beneath the least obvious piece of UI design I can remember encountering on a major website. There are three tiny grey dots in the gutter between two white panels at the top right of the timeline (below the “Share an update” / “Upload a photo” / “Publish a post” bar). Hover over this tiny target and you get the option of switching from “Top Updates” to “Recent Updates”. Maybe everybody else knows this but I didn’t.

Great, we’ve made it moderately useful again. No. This change doesn’t stick. Every time you reload the page it goes back to “Top Updates”.

LinkedIn hidden navigation

Can you find the display options menu?

Anyway, the reason I was talking to @linkedinhelp in the first place is because they have an unhelpful way of blocking invitations. It used to be that if you set your communication preferences so that people needed to know your e-mail address to connect with you, they would not be able to send you an invitation without it. Sensible enough. However, now they can send an invitation without knowing about this restriction and LinkedIn will not show it to the intended recipient. So you’re both in the dark. You have people who think that you have received their invitation and chosen not to accept it, which may lead to awkwardness. Instead the invitation is hidden away in a “Blocked invitations” list, which you need to go looking for. Mine contained several months-old invitations, some from people I actually knew who likely assumed I was just ignoring them. Had LinkedIn just told them they needed my e-mail address to connect they would have been able to get it from me via other channels.

These are just two of the many reasons I remain mystified as to why there appears to be no obvious direct competitor to LinkedIn. It’s a profitable business that could be done better.

No More Updates to ReportNG

Posted in Java by Dan on December 3rd, 2015

About 8 years ago I made the decision to use TestNG for unit testing a Java project at work because it offered a much neater approach to the problem than JUnit 3.x. The only problem was that the HTML reports it generated were ugly. So I quickly hacked together a plug-in using Apache Velocity that would present the information in a way that I preferred and called it ReportNG. There was nothing elegant about the solution and the reports were functional but not particularly attractive, but it scratched an itch. It seemed to be an itch that irritated a lot of other developers because it attracted probably more users than any other, better written code that I’ve open-sourced before or since. I have made no substantial changes to it in years but there are still people using it.

I don’t do nearly as much Java as I used to and where I do I am either not using ReportNG or it is sufficient as it is. For these reasons, and because it ought to be replaced by something better (which probably already exists somewhere – I haven’t looked), I wanted to make it explicit to people still relying on it that there will be no further updates to ReportNG. You have three options:

  1. Continue using version 1.1.4 and accept that it will never get any better.
  2. Fork it on GitHub and make whatever changes you like. The Apache licence is permissive enough to allow most things.
  3. Stop using ReportNG and find something better.

Looking to Connect with Mobile App Developers in Kent

Posted in Android, iOS by Dan on December 2nd, 2015

One of the problems with operating as a one-man software development company is that the pipeline of work can be quite erratic. When you’re responsible both for drumming up business and for delivering the work, it’s difficult to maintain a steady pace from month to month. Instead quiet months can be followed by periods when you have to turn work away as there are not enough hours in the day. I’m not yet at point where I feel able to commit to taking on permanent staff but I’d like to be able to scale up capacity from time to time. To that end I’m looking to make connections with local app developers (native iOS and Android) who are looking for freelance work now and in the future.

There’s no promise of immediate work (although it is a possibility), I’m more interested in long-term connections that could be of mutual benefit. In particular, I’m looking for introductions to app developers in and around Kent, with whom I can meet face-to-face should the need arise. East Kent (Canterbury area) would be perfect but anywhere this side of London is fine. So if you’re a local app developer, or know somebody who is, please get in touch (Twitter, LinkedIn, or send a message here) whether you’re looking for work right now or not. And if you do want some work, please include some examples of apps you have developed and how much you charge for your services.

Likewise, if you’re a graphic designer doing/interested in doing app work, I’d be interested in connecting with you too.

If you’re a software development outsourcing company, this invitation is not for you. I already reluctantly speak to dozens of such companies on the phone every month.

Stopping Being Part of the Problem

Posted in The Internet by Dan on July 27th, 2015

No trackers on this site.Some time ago I wrote about my issues with Google and its anti-privacy agenda. It was my intention to avoid using Google services where possible because I don’t like Larry Page’s vision and Google’s (and Facebook’s and others’) tracking of individuals’ activity across the web. I have very little to hide but that’s not the point.

Two of the biggest enablers of Google’s surveillance machinery are Adsense and Google Analytics. These services give Mountain View a foothold on millions of properties across the web, from which they can observe users activities and track them from site to site. Today I finally got around to removing these from my own sites. I never paid much attention to the analytics anyway and there are more privacy-friendly alternatives if required. Adsense never generated enough income to compensate for the cheapening of the message.

Other unrelated changes I’ve made to this blog include changing the canonical domain to instead of (the old URLs will redirect). In addition, some time ago I disabled comments. There were occasionally a few worthwhile responses but over 99.6% of all comments submitted were spam. The bottom half of the web is generally a cesspit. If you feel the need to respond to something I’ve written, you can contact me on Twitter or e-mail dan at this domain.

Play Our Online Football Game and Help Raise Money for Charities

Posted in Python, The Real World by Dan on August 8th, 2014

If you’re not in the UK or you’re not interested in the Beautiful Game and helping charities then this post isn’t for you. I’ve generally tried to keep the articles on this site at least tangentially related to software development but on this occasion the link is a bit more tenuous than usual.

Super10 promo image

I’d like to ask you to consider playing our online charity football game, Super10. You can win a share of several hundred pounds in cash prizes and, more importantly, you can help to raise money for charities. The charity aspect is two-pronged. Firstly you can win money for one of the four organisations that the game is supporting this season (Demelza Hospice Care for Children, Kent, Surrey and Sussex Air Ambulance, Pilgrims Hospices, and homelessness charity Porchlight) and secondly all additional proceeds go to Donations With A Difference (DWAD), the newly-registered charity, of which I am now a trustee, that is running the competition. DWAD will use this money to make grants to individuals and organisations to support the improvement of physical health, mental health and education in the UK.

Super10 is a little bit like fantasy football except that instead of picking players, you pick ten clubs – seven from the English game and three from the top European leagues. These teams score points for you throughout the season. Compared to traditional fantasy football it’s a low-maintenance game as there are no substitutions to deal with each week and transfers only occur during the January transfer window. You can make your selections and mostly forget about them. That said, it does have a way of making you care deeply about the results of teams you previously had no attachment to. Never been to Peterborough? Doesn’t matter, you’ll still feel the despair as they concede a late equaliser to Port Vale.

So what’s the software angle? Well Super10 has been around for a few seasons, certainly much longer than I’ve been involved, but this year we’re trying to take it beyond its previously limited scope. That has meant getting it fully online including being able to take payments online so that we can extend the game to a much wider audience and therefore raise more money. To enable this I’ve built the responsive Super10 website using Python, Django and Bootstrap. Much of the website was in use for most of last season but now we’ve extended it to open up Super10 to the whole country for the first time.

We’re restricting Super10 to the UK only for now because we are not familiar enough with the laws governing this kind of competition in other countries. The deadline for entries is noon (BST) on Saturday 16th August. Give it a go and get your friends to play too.



A Sceptic’s View of Google Glass

Posted in Android, Hardware by Dan on July 18th, 2014

Back in 1999, I was one of a group of Computer Science students invited to visit the research labs of a large consumer electronics company. I don’t remember a great deal about the prototype products we were shown but I do clearly remember being told that wearable computing was the next big thing. 15 years later wearable computers are still the next big thing.

This time around the devices have made it beyond the lab with a slew of underwhelming “smart” watches already on sale. In addition, Apple has been rumoured to be preparing its own for years now. Google on the other hand has taken a different approach by creating a new category of product with Google Glass.

It’s been over two years since Glass sky-dived into public view but it remains subject to a pretty exclusive public beta that you have to pay a hefty premium to join ($1500/£1000). Until a few weeks ago Glass headsets could only be purchased in the US. They are now also available in the UK. On Wednesday evening Google held its first European Glass developer meet-up at Skills Matter in London.

Since first hearing about Glass I’ve been deeply sceptical about it. It’s clearly capable of doing a few neat things that could be useful in a few niche areas but it just seems so inessential, it looks thoroughly ridiculous, and the current price tag is not destined to appeal to sensible individuals. However, I’ve often been too dismissive of new technologies in the past, so I was prepared to at least give it a go.

There was a full house at the Skills Matter Exchange including several Glass-adorned Googlers and a surprising number of other people who had presumably been parted from a grand of their own money. Interestingly, almost everybody had opted for the version with the plastic lenses. When the alternative is the bizarre lens-less titanium forehead band with nose perch it is entirely understandable, whether you need vision correction or not. The spectacle facade makes Google Glass look a lot less conspicuously weird. It’s still not a good look though, even without the beyond-parody third-party add-ons.

Google developer advocates Hoi Lam and Timothy Jordan delivered a couple of presentations suggesting how you should approach building apps for Glass (or Glassware as Google likes to call them). One major drawback for those who might be interested in building these apps is that at present there is no direct way for developers to make money from developing Glassware. Presumably that has to change at some point but for now apps can only be distributed free-of-charge (subject to Google’s approval), and in-app advertising is, mercifully, banned.

Following the presentations, those of us who hadn’t experienced Glass firsthand were given the opportunity to try out the headsets. Due to time constraints and the number of people who wanted to have a go, we didn’t get long enough to be able to get a feel for what it would be like to have this thing on your face all day but here are a few things I noted that might be of interest to those who haven’t tried one of the devices yet.

  1. The early promo shots of Glass tended to avoid showing the battery pack that rests behind your right ear and those pictures that did show it made it look awkwardly bulky. In reality this battery is quite thin and the headset is not as heavy as it looks.
  2. As a POV camera, Glass works well. It’s very easy to take snapshots or video (although I didn’t get an opportunity to check the quality of the results on a bigger screen). Unfortunately, according to Scoble, the battery is only good for 45 minutes of video and it will cook your face in the process. If all you care about is POV photography there are probably much better/cheaper options available.
  3. In a room with a lot of background conversation, the voice recognition worked well. The microphone clearly does a good job of isolating the voice of the wearer.
  4. The “screen” was underwhelming, albeit in a poorly-lit environment. The resolution wasn’t great and the focus didn’t feel entirely comfortable. It may well have been possible to adjust the focus but I didn’t have the time to find out.
  5. The user interface doesn’t feel like it would scale well to having a large number of apps installed. At the moment there is a lot of swiping through cards in a linear fashion.
  6. Unsurprisingly, Glass appears to be tightly integrated with Google+.

I was never going to be a person who would consider paying £1000 for one of these devices but having tried it very briefly I’m now certain that I wouldn’t buy one at a lower price either, even if I ignored the way it looks. There is, as yet, no compelling use case for the average person. One of the main features is that you can get your e-mails, SMS messages and other Android notifications beamed directly on to your retina without having to remove your phone from your pocket. I really don’t have any need for that kind of urgency. I’d rather ignore interruptions until I choose to deal with them. That’s not to say that there aren’t people who could find a use for Glass given better battery life and a more attractive price tag, but for most of us it’s a clever solution in search of a problem – the hardware equivalent of Google Wave.


Deploying an APK to Multiple Devices/Emulators Simultaneously Using Ant

Posted in Android, Ant by Dan on January 4th, 2014

Following my detour to Djangoland, the last week or so I have been back in the world of mobile app development. One of the things I’ve been working on is updating an Android app that has two versions built from the same codebase. Part of this update has been to re-skin both versions of the app and to make sure that everything looks reasonable on various devices running versions of Android from 2.2 up to the latest 4.4. When I build a new APK I want to check it on each of the three devices connected to my machine. If I had more spare USB ports I would conceivably be testing with even more devices. In some scenarios I might also have one or more emulators running to test configurations not represented among my devices. Installing the new APK on each of these from the command line is cumbersome. The adb tool will only install on one device/emulator at a time and, if you have more than one connected, you have to specify a long unique device ID to tell it which one to use. You could write a bash script to retrieve the IDs of connected devices and emulators and to run the adb install command for each, but since I’m using Ant I have written a custom task to do the job. For bonus points I’ve made it issue the adb commands in parallel so that the total install time is determined by the slowest device and not the sum of all the devices.

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
 * Custom task that uses the ADB tool to install a specified APK on all
 * connected devices and emulators.
 * @author Daniel Dyer
public class InstallAPK extends Task
    private File apkFile;
    public void setAPK(File apkFile)
        this.apkFile = apkFile;
    public void execute() throws BuildException
        if (apkFile == null)
            throw new BuildException("APK file must be specified");
            List<String> devices = getDeviceIdentifiers();
            System.out.printf("Installing %s on %d device(s)...%n", apkFile, devices.size());
            ExecutorService executor = Executors.newFixedThreadPool(devices.size());
            List<Future<Void>> futures = new ArrayList<Future<Void>>(devices.size());
            for (final String device : devices)
                futures.add(executor.submit(new Callable<Void>()
                    public Void call() throws IOException, InterruptedException
                        return null;
            for (Future<Void> future : futures)
            executor.awaitTermination(60, TimeUnit.SECONDS);
        catch (Exception ex)
            throw new BuildException(ex);
    private void installOnDevice(String device) throws IOException, InterruptedException
        String[] command = new String[]{"adb", "-s", device, "install", "-r", apkFile.toString()}
        Process process = Runtime.getRuntime().exec(command);
        consumeStream(process.getInputStream(), System.out, device);
        if (process.waitFor() != 0)
            consumeStream(process.getErrorStream(), System.err, device);
            throw new BuildException(String.format("Installing APK on %s failed.", device));
    private void consumeStream(InputStream in, PrintStream out, String tag) throws IOException
        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            for (String line = reader.readLine(); line != null; line = reader.readLine())
                out.println(tag != null ? String.format("[%s] %s", tag, line.trim()) : line);
    private List<String> getDeviceIdentifiers() throws IOException, InterruptedException
        Process process = Runtime.getRuntime().exec("adb devices");
        List devices = new ArrayList<String>(10);
        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            for (String line = reader.readLine(); line != null; line = reader.readLine())
                if (line.endsWith("device"))
            if (process.waitFor() != 0)
                consumeStream(process.getErrorStream(), System.err, null);
                throw new BuildException("Failed getting list of connected devices/emulators.");
        return devices;

Once compiled and on your classpath, using it is simple:

<taskdef name="installapk" classname="yourpackage.InstallAPK" />
<installapk apk="path/to/yourapp.apk" />

Django ModelChoiceField and HTML <optgroup>

Posted in Python by Dan on December 21st, 2013

I’ve been dabbling with Django over the last couple of months. I’ve played around with it a couple of times previously but this time I’ve actually built something reasonably substantial, which has meant that I’ve had to delve a bit deeper. One of the minor problems I solved today was how to group items in the HTML <select> element generated by a form’s ModelChoiceField. HTML has the <optgroup> tag for this purpose.

It’s not immediately obvious how you can get ModelChoiceField to use optgroups without over-riding the render method and re-implementing the HTML generation yourself, or bypassing ModelChoiceField completely and building something based on the basic ChoiceField (which does support optgroups). The only potential solutions I found from searching took the former approach (here and here). The reason I’m writing this post is because I think I’ve found a better, more concise solution that might be of use to future searchers.

ChoiceField accepts a choices parameter to its constructor. In the simple case this is just a list of items (each item is value/label pair). However, it can also accept a list of groups where each group is a tuple consisting of the group label and a list of items. The problem is that ModelChoiceField is different in that it has a queryset parameter instead, so there is no way to pass in the group information.

However, a comment in the source code says that we can set a property called choices after constructing the ModelChoiceField instance and the queryset will be ignored. The HTML <select> will instead be populated from this data structure with <optgroup> elements as required.

Assuming we want to group items by a field on the model, we can build the list of tuples from the queryset by sub-classing ModelChoiceField and over-riding the constructor. In this example I’m assuming that the field is a list of countries grouped by continent, where the continent is just a text field on the country model.

from django.forms import ModelChoiceField
from itertools import groupby
from operator import attrgetter
class CountryChoiceField(ModelChoiceField):
    def __init__(self, *args, **kwargs):
        super(CountryChoiceField, self).__init__(*args, **kwargs)
        groups = groupby(kwargs['queryset'], attrgetter('continent'))
        self.choices = [(continent, [(, self.label_from_instance(c)) for c in countries])
                        for continent, countries in groups]

In order for this to work, the queryset must be sorted by the field that it is to be grouped by so that items in the same group are adjacent. You can do this before you pass it to the constructor or you can change the code above to call order_by on the queryset.

ReportNG 1.1.4

Posted in Java by Dan on June 11th, 2013

It’s been a couple of years since the last release of ReportNG, my cobbled together alternative reporting plug-in for TestNG. In the time since I’ve done nothing but a few users have submitted useful improvements. So, after some prompting, I’ve released version 1.1.4 (download zip, tgz).

Thanks to Kayla Nimis, criccio, Arcadie, and Nalin Makar the new version has the following improvements:

  • The report now shows the reason for skipping a test.
  • You can specify the org.uncommons.reportng.failures-only property to generate a more minimal report.
  • ReportNG will now create any missing parent directories of the report directory rather than failing if they are absent.

Google Skynet

Posted in The Internet by Dan on May 16th, 2013

Google policy is to get right up to the creepy line and not cross it.

These are the words of former Google CEO Eric Schmidt, speaking to The Atlantic in October 2010.

We don’t need you to type at all. We know where you are. We know where you’ve been. We can more or less know what you’re thinking about.

This was two and a half years ago. Today Google is so far over the creepy line it can’t even see the line any more. The problem is not so much individual Google products but the way in which the vast data from all these disparate services is combined to build a very detailed profile of you. Not just what you do on Google sites but also any other site you visit that includes Google’s +1, Adsense or Analytics JavaScript (including this one). Google can read every e-mail you send and receive, it knows everything you search for online and knows pretty much every website you ever visit. It knows where you are, where you’ve been and probably with whom. Google knows more about you than your mother does and with Google+ it’s all neatly connected to your real identity.

I’ve so far avoided signing up for Google+ but it’s increasingly difficult to ignore, particularly as an Android developer, as it’s becoming more tightly integrated into everything that Google does. It’s the keystone of Google’s anti-privacy agenda.

The privacy implications are not the only issue. Most users perceive Google’s search results to represent some sort of objective truth, with links ranked only by their relevance to the search query, but in fact each user is served personalised results. If you and I both search for information on some contentious political topic, Google won’t necessarily give us the same response; it will show us each what it thinks we want to see based on its profiling of us.

In his Google I/O Q&A yesterday, Google CEO Larry Page dismissed concerns over the implications of this profiling:

[Audience member]: Most of my opinion, I can trace back to a Google search. As search becomes more and more personalized, and predictive, I worry that it informs my world view and rules out the possibility of some other serendipitous discovery. Any comment on that?

[Page]: People have a lot of concern about that – I’m totally not worried about that at all.

Personally, I don’t like Google’s all-encompassing vision. It’s not the only company that employs such methods but I can easily ignore the likes of Facebook and Bing as I don’t use them. Google is everywhere.

Spurred more by the closure of Google Reader than anything else, a couple of months ago I began to consider alternatives to relying on the benevolence of one omniscient company for the services I use every day.

The first thing to go was GMail. E-mail is important enough to be worth paying for so I signed-up with FastMail (now owned by Opera), which offers an ad-free service from $4.95 per year. It has a refined web interface, IMAP access, and a simple way to import your existing messages from GMail.

I eventually replaced Google Reader with The Old Reader and for search I’m now using DuckDuckGo, which promises not to track its users or filter search results. DuckDuckGo doesn’t quite match Google in terms of the freshness or depth of its results but it’s fast and has some useful features. On DuckDuckGo’s recommendation I also installed Ghostery for Opera to block Google and others’ attempts to track me on third-party sites. I’ve removed the Google +1 buttons from this blog and will be looking for more privacy-friendly alternatives to Google Analytics and Adsense (both of which would be useless anyway if everybody is driven to use the likes of Ghostery).

I’m not giving up on Google entirely, I just prefer to keep it at arm’s length. I’ll still be developing for Android (for which there were many very welcome developer announcements at I/O yesterday) and no doubt I’ll continue to use some of its other services.

« Older Posts