Coder Thoughts on software, technology and programming.

Piotr Mionskowski

  • Anchor child element click

    25 January 2015 html and javascript

    I had to solve a seemingly trivial bug in an angularjs based application that turned out to be more interesting than usual.

    The bug

    The bug report stated that "Clicking on a label causes page reload". That should be an easy one I thought to myself and openeded chrome inspector to see a structure of DOM. Here's a simplified version of markup:

    <a  href="" ng-click="anchorAction($event)" ng-controller="ActionCtrl">
      Anchor
      <span ng-click="childAction($event)">
      A child
      </span>
    </a>
    
    module.controller('ActionCtrl', function($scope){
      $scope.anchorAction = function($event){
        console.log('anchorAction');
      };
      $scope.childAction = function($event){
        $event.stopPropagation();
        console.log('childAction');
      };
    });
    

    My intention was to have different behaviour when an anchor or a span element is clicked. Just as in the example above when a is clicked anchorAction should be printed whereas the same event triggered on span should only print childAction. Interestingly the actual behaviour is different.

    When the anchor is clicked indeed a function attached by ng-click is executed properly. Note that even though we did not call $event.preventDefault() a page reload is not triggered. This is due to htmlAnchorDirective provided by angularjs which effectively prevents empty href attribute from taking action.

    A click on span element will stop event from bubbling up document tree - thus preventing anchorAction from executing. In addition it will obviously print childAction and to my surprise it will cause a page reload.

    Wait a second didn't we just prevent the event from traveling up to the anchor element? Yes. So why does the page reload anyway?

    Searching for a root cause.

    Almost immediately I've verified that calling $event.preventDefault() inside childAction fixes the problem. The fix got checked in and will be deployed soon - case closed. I was unhappy though because I didn't understand this behaviour at all.

    At first I naively thought that it might be a Chrome bug - a quick check in FF and IE diminished this stupid idea.

    Then I thought that it may be related to angularjs in some strange manner so I've prepared an example fiddle that demonstrates the issue. I've searched for and read many answers on Stack Overflow and other forums but none of them gave an in-depth explanation.

    The HTML spec

    Since the example fiddle demonstrated same behaviour in all major desktop browsers I realised that it must be part of HTML spec - after an hour or so it turned out that it was a good hunch.

    Up until now I thought that an event (and a click event in particular) default action is dependent on an element it visits when it is dispatched through a DOM tree. In the above example it would mean that since I've stopped click from bubbling up it should not reach a element and because of that it should not execute its default action - in our case a page reload.

    It turned out that my assumptions about events dispatching and elements default actions were wrong.

    The relevant part of the specification describes activation behavior with an explanation of what I've experienced:

    1. Let target be the element designated by the user (the target of event).
    2. If target is a canvas element, run the canvas MouseEvent rerouting steps. If this changes event's target, then let target be the new target.
    3. Set the click in progress flag on target to true.
    4. Let e be the nearest activatable element of target (defined below), if any.
    5. If there is an element e, run pre-click activation steps on it.
    6. Dispatch event (the required click event) at target. If there is an element e and the click event is not canceled, run post-click activation steps on element e. If there is an element e and the event is canceled, run canceled activation steps on element e.
    7. Set the click in progress flag on target to false.

    The most relevant steps are 4. and 6. as they clearly indicate that target and nearest activatable element that triggers default action can be separate. What's left to have a complete understanding is how nearest activatable element is defined:

    Given an element target, the nearest activatable element is the element returned by the following algorithm:

    1. If target has a defined activation behavior, then return target and abort these steps.
    2. If target has a parent element, then set target to that parent element and return to the first step.
    3. Otherwise, there is no nearest activatable element.

    Now it is obvious why a default action of an anchor is executed even though a click event did not bubble up from its child.

    This article is cross-posted with my company blog

  • Lazy Apk - a simple TeamCity artifact downloader

    06 January 2015 android and TeamCity

    At Bright Inventions we use TeamCity as a continuous integration server. Apart from building, running tests and uploading artifacts we sometimes use it to quickly distribute an android application to clients and test team. However we found using TeamCity UI on a mobile device isn't as pleasing as it could be. That's why we usually recommend downloading updates through a dedicated application such as TeamCity Downloader.

    While TeamCity Downloader is really easy to use I found it lacks a couple of features I need. First of all I would like to be able to use multiple TeamCity servers at once. Moreover it would be nice to see commit messages related to particular build to streamline change communication.

    Lazy Apk - a simple apk downloader

    Since I wanted to test some of new APIs available in Lollipop I thought it would be a good idea to try them out in an application that could actually be useful. That's why I've build another artifacts downloader - Lazy Apk.

    The tool allows you to configure multiple TeamCity build servers:

    Multiple sources

    As I mentioned the commit messages are visible next to each build:

    Commit messages

    Downloaded APKs are stored inside android "Downloads" so there is no need to pull the same package twice:

    Downloads

    Source available on Github

    Lazy Apk source code is available on github. I've tried couple of different techniques to extract code from Activity classes - I'm not yet sure if I like the results though...

    This article is cross-posted with my company blog

  • Google Play Services is no longer a giant monolith

    09 December 2014 android

    Nowadays it's getting harder and harder to build a meaningful app and not rely on Google Play Services to aid us in some commonly required features such as maps, better location provider, geo fencing and so much more. Unfortunately up until now the library shipped as a giant monolith ripping us from one third of dex method limit. For curious reader here's are method counts in couple of versions:

    Version Method Count
    3.2.65 6330
    4.4.52 16933
    5.0.89 20312
    6.1.71 23641

    and a full breakdown.

    Google Play Services 6.5 granular dependency management

    Today Google has made the awaited, more than usual, version of their SDK available. With the update apart from new features you can finally depend only on a subset of enormous API. Here's a table from documentation along with dex method counts:

    API Name Gradle depdenency Dex method count
    Google Play Services com.google.android.gms:play-services:6.5.87 24525
    Google+ com.google.android.gms:play-services-plus:6.5.87 1525
    Google Account Login com.google.android.gms:play-services-identity:6.5.87 181
    Google Activity Recognition com.google.android.gms:play-services-location:6.5.87 857
    Google App Indexing com.google.android.gms:play-services-appindexing:6.5.87 482
    Google Cast com.google.android.gms:play-services-cast:6.5.87 976
    Google Drive com.google.android.gms:play-services-drive:6.5.87 2328
    Google Fit com.google.android.gms:play-services-fitness:6.5.87 1895
    Google Maps com.google.android.gms:play-services-maps:6.5.87 2568
    Google Mobile Ads com.google.android.gms:play-services-ads:6.5.87 3278
    Google Panorama Viewer com.google.android.gms:play-services-panorama:6.5.87 94
    Google Play Game services com.google.android.gms:play-services-games:6.5.87 5046
    Google Wallet com.google.android.gms:play-services-wallet:6.5.87 1116
    Android Wear com.google.android.gms:play-services-wearable:6.5.87 1187
    Google Actions
    Google Analytics
    Google Cloud Messaging
    com.google.android.gms:play-services-base:6.5.87 5212

    A small change to improve build time

    For me the biggest win is that in one of the apps we are actively developing granular dependency declaration means with a simple change from

    compile 'com.google.android.gms:play-services:6.1.71'
    

    to

    compile 'com.google.android.gms:play-services-maps:6.5.87'
    compile 'com.google.android.gms:play-services-location:6.5.87'
    compile 'com.google.android.gms:play-services-base:6.5.87'
    

    I no longer have to run Proguard during development. No wonder my build time just improved by 15 seconds.

    This article is cross-posted with my company blog

  • Integrate slf4j with Crashlytics

    20 November 2014 android

    As I mentioned in my previous post having meaningful log entries comes handy during development. When an app reaches beta testers as well as goes live it's equally or even more important to be able to figure out why the app you've carefully coded isn't behaving as it should. Testing the app on all android flavours is literally impossible that's why getting an insight into what caused a crash is vital.

    Crashlytics

    Error reporting providers are getting more and more popular. There are plenty of options to choose from: Raygun, Airbrake and Crashlytics are just frew examples. At Bright Inventions we use the last one and are more and more pleased with it. Setting it up is really easy - if you don't mind installing an IDE plugin it provides. Frankly I would prefer being able to configure a project with a simple command line tool but I understand a motivation behind it which is making the installation as seamless as possible.

    Crashlytics let's you not only report uncaught exceptions but also handled errors with additional information provided by log entries:

    try {
      Crashlytics.log(Log.INFO, "HomeActivity", "Make request");
      makeRequest();
    }catch(RuntimeException ex){
      Crashlytics.log(Log.ERROR, "HomeActivity", "Error making request " + ex);
    }
    

    This will print messages to logcat as well as well as well as make them available in Crashlytics dashboard.

    Using slf4j with Crashlytics

    I've already explained why I don't like this approach to logging. Thankfully with slf4android it's really easy to replace a default logcat appender with a CrashlyticsLoggerHandler:

    public class CrashlyticsLoggerHandler extends Handler {
      MessageValueSupplier messageValueSupplier = new MessageValueSupplier();
    
      @Override
      public void publish(java.util.logging.LogRecord record) {
        LogRecord logRecord = pl.brightinventions.slf4android.LogRecord.fromRecord(record);
        StringBuilder messageBuilder = new StringBuilder();
        messageValueSupplier.append(logRecord, messageBuilder);
        String tag = record.getLoggerName();
        int androidLogLevel = logRecord.getLogLevel().getAndroidLevel();
        Crashlytics.log(androidLogLevel, tag, messageBuilder.toString());
      }
    
      @Override
      public void close() {}
      @Override
      public void flush() {}
    }
    

    All that is left is to add the handler to root logger in your custom application onCreate method:

    LoggerConfiguration.configuration()
      .removeRootLogcatHandler()
      .addHandlerToRootLogger(new CrashlyticsLoggerHandler());
    

    From now on all log messages will go through Crashlytics API and couple of last log entries will be available next to crash report details in dashboard.

    This article is cross-posted my company blog

  • Introducing slf4android - a simple slf4j implementation for android

    01 November 2014 android

    Every now and then you have a bug that is hard to reproduce or only happens on certain phones or android versions. The thing that really comes handy in such case is a detailed application log. That's why it's so important to take time to add useful log entries in every non trivial part of the codebase. At the very minimum you'll want to log any errors.

    Logging frameworks

    That's why it's so important to create log entries easily. The default solution that comes with Android by means of Log is the most commonly used. However for me it's really the least pleasant to use:

    Log.e("MyTag", "Failed to download " + url +  "due to occurred " + ex);
    

    I really don't like that I have to specify a tag each time I need to log something. Moreover having to concatenate strings seems tedious and error prone.

    Logging with java.util.logging

    Another alternative that is available by default on android is packaged inside java.util.logging.*. And here's an example of how to use it:

        private static final Logger LOG = Logger.getLogger(MyActivity.class.getName());
        // and then
        LOG.log(Level.FINE, "Starting activity {0} saved instance {1}", new Object[]{this, savedInstanceState});
    
    

    You're able to use MessageFormatter style to format log entries. However no variable arguments method overload makes it both harder to read and write. More importantly by default if you use above statement the message will not be printed anywhere. It's easy to fix when you know where to look for.

    Powerful logback

    logback is probably the most powerful and configurable logging framework. Among many features you can for example send an email with 50 last log entries - I've used it in one project, it can be a bit hard to configure but it really comes handy during testing. In order to use it on android one needs to use a ported version logback-android. There is one caveat though - this library is costs about 512kB - and takes about 4200 out of dex method limit.

    Simple android-logger

    android-logger is a small (<50KB) library that let's you use slf4j api to print to logcat. It ships with various configuration options that let you change the format of output messages as well as log level based on hierarchical logger names. However you won't be able to print messages to an additional file and you can only configure the logger through properties files.

    slf4android

    Since I wasn't perfectly happy with above and because some design decisions made in android-logger make it not so easy to add features like logging to a file, creating custom patterns and configuring it from code I decided to create yet another logging utility.

    It's a tiny wrapper around slf4j api baked by the java.util.logging logger mechanism. This means you can easily hook in any existing java.util.logging.Handler implementations.

    To use this little gem you'll need to add http://bright.github.io/maven-repo/ to repository list:

    repositories {
        maven {
            url "http://bright.github.io/maven-repo/"
        }
    }
    

    and then declare a dependency inside a module:

    dependencies {
        compile('pl.brightinventions:slf4android:[email protected]'){
          transitive = true
        }
        //other dependencies
    }
    

    As with any slf4j compatible implementation using slf4android looks like this:

    class HomeActivity extends Activity {
        private static final Logger LOG = LoggerFactory.getLogger(HomeActivity.class.getSimpleName());
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            LOG.debug("Hello from {} saved instance state {}", this, savedInstanceState);
        }
    }
    

    Logging to a file

    To print messages to a separate file just add:

    LoggerConfiguration.configuration().addHandlerToLogger("", LoggerConfiguration.fileLogHandler(this));
    

    inside your custom android.app.Application onCreate method. This will create rotated log files inside context.getApplicationInfo().dataDir with a name derived from context.getPackageName() and a default message pattern %date %level [%thread] %name - %message%newline

    More features

    slf4android let's you register custom message patterns and configure logging level - although the api for that is still rough around the edges. It also features a simple (and not well tested) mechanism for error reporting that, when enabled, will display a Dialog prompting tester to notify developer through email whenever an error is encountered. You can enable it with:

    LoggerConfiguration.configuration().notifyDeveloperWithLogcatDataHandler(applicationContext, "[email protected]")
    

    and receive emails with attached logcat output which comes handy during development.