Is AspectJ Still Useful for Android? Part 2

In the first part of this post, I showed some ways that AspectJ would be useful in android development when it comes to testing. This final part demonstrates one way of incorporating AspectJ, and how I manage to make the aspect weaving somewhat configurable in the build process.

Android + AspectJ

A search on the internet will show that there are various ways of integrating AspectJ into the Android build process, including doing it manually or using a gradle plugin. I have been using the android-gradle-aspectj plugin for the last few years, since it has some useful features and the author seems to keep it maintained fairly well.

A basic setup for this plugin requires adding it to the buildscript repositories and dependencies blocks. I have this in build.gradle in the project root directory.

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:x.x.x'
    classpath 'com.archinamon:android-gradle-aspectj:x.x.x'
  }
}

Then apply the plugin where required, so in the module build.gradle:

apply plugin: 'com.archinamon.aspectj'

or

plugins {
    id 'com.android.application'
    id 'com.archinamon.aspectj'
}

This should be enough to run a simple case, such as the example from the first post. Please see the plugin documentation for more advanced configurations.

To verify that the aspect has been compiled and weaved during the build process, you can check:

  • the aspectj directory in the module build directory, containing the compiled aspect class files
  • some log files generated by the plugin in the module build directory, ajc-transform.log and ajc-compile.log
  • the build output log for the AspectJ plugin tasks, compile[Build variant]Aspectj and transformClassesWithAspectjFor[Build variant].

Determining When to Use Aspects or Not

If we’re only using aspects for testing, most of the time we don’t want the aspect code to be incorporated into the build process. An easy way to determine when to weave the aspect code or not is to use a Boolean condition.
For instance, in the aspect file include a Boolean condition in the pointcut to determine whether the advice will apply or not:

pointcut myPointcut(): if (aspectjEnabled) && ... // the rest of the pointcut

Here the Boolean value ‘aspectjEnabled‘ is used as a flag that will determine whether the aspect code will apply the advice.

One way of passing this flag to the aspect class is to use the generated BuildConfig class, which can be set in the build file (build.gradle) of the app.

android {
  buildTypes {
    debug {
      // Flags for Aspect testing
      buildConfigField 'boolean', 'aspectjEnabled', true
    }
  }
}

Then in the pointcut of the aspect file, include the flag as a conditional.

pointcut myPointcut(): if (BuildConfig.aspectjEnabled) && ... // the rest of the pointcut

Of course we can go further and set that flag from a gradle project property, rather than hardcoding it in build.gradle.

Now when the build is done, pass a project property to determine whether we want the aspect flag to be set or not.

-PaspectjEnabledProperty=true

I also have this extra bit to set the aspect flag to false if the project property was not specified (which is the default when we want to do a normal build without aspects).

ext.aspectjEnabledProperty = getAspectJEnabledFlag()

def getAspectJEnabledFlag() {
  if (project.hasProperty('aspectjEnabledProperty'))
    return project.property('aspectjEnabledProperty')
  else
    return false
}

Then in the build file for the app, the value of the aspect flag to be put into the BuildConfig class will be set dynamically.

android {
  buildTypes {
    debug {
      // Flags for Aspect testing
      buildConfigField 'boolean', 'aspectjEnabled', "${aspectjEnabledProperty}"
    }
  }
}

In sumary, this means that if the property ‘aspectjEnabledProperty’ is not passed to the build or is set to false, then the AspectJ weaving does not happen. Then when required, the property can be set for testing that particular bit of code.

Of course you are not restricted to one aspect flag, you can set as many flags as you like for the different tests you want to run.

-PexceptionTestProperty=true -PcrashTestProperty=false -PservicesTestProperty=true

Testing Multiple Types of Errors

In the configuration above, I’ve used a boolean value as the flag to enable/disable aspect weaving in the pointcut. Using a boolean flag is the simplest case, and probably the most common type that used.

However what about if you want to test multiple types of errors and error handling. For instance, if you want to test having the adviced method throw different types of exceptions. Another example is if you want to simulate different status codes as the response from a network call.

Then instead of using a boolean value as the flag, use an integer (or any other simple type) for example.

-PaspectjTestingProperty=1

Then in the build file:

ext.aspectTestingFlag = getAspectTestingFlag()

def getAspectTestingFlag() {
  if (project.hasProperty('aspectjTestingProperty'))
    return project.property('aspectjTestingProperty')
  else
    // default value if property not specified
    return 0
}

android {
  buildTypes {
    debug {
      // Flags for Aspect testing
      buildConfigField 'int', 'aspectTestingFlag', "${aspectjTestingProperty}"
    }
  }
}

Then in the aspect, you can use the integer flag both as a conditional in the pointcut and as an indicator run different code in the advice.

aspect TestAspect {

  pointcut myPointcut(): if (BuildConfig.aspectjTestingFlag != 0) && ... // the rest of the pointcut

  int around(): myPointcut() {
    switch (BuildConfig.aspectjTestingFlag) {
      case 1:
      // return a particular status code, or throw a particular exception type

      case 2:
      // return a different status code, or throw a different exception type

      case 3:
      // return yet another status code, or throw a third different exception type

      default:
      return proceed();
    }
  }
}

Caveats

Just a few things to be aware of:

  1. As already mentioned, since we can only have AspectJ compile time weaving for Android development, this means some additional build time for the AspectJ tasks.
  2. Unfortunately Android Studio doesn’t have IDE support for AspectJ, since it is based on the community edition of IntelliJ IDEA.
    While the Ultimate version of Intellij does support AspectJ, I prefer using Android Studio.

    Tip:
    Sometimes to get the pointcuts correct I will copy files with the aspect, the classes I want to advice and their lib dependencies into a dummy Eclipse project so that I can use the AspectJ Development Tools (AJDT) to fine tune the pointcut. This allows me to see if the pointcuts are being applied correctly to the joinpoints I want.

  3. Using a Gradle plugin to handle incorporating AspectJ into the build process does make things easier. However this sometimes means having to wait for the authors of the plugin to keep up to date with the latest versions of the android gradle plugin or gradle.

Crashlytics and Android: Clean Way to Use Custom Crash Reports?

I was implementing Firebase Crashlytics for crash reporting for an Android app, and came across their documentation for customizing the reports.

Having things like additional logging and user info for non-fatal exceptions are great, but what I wanted to use were the custom keys that could be used to log application state when the exception occurs. What’s really nice is that the state info is clearly displayed in the Firebase console.

 

My requirements for implementing crash reporting were:

  • Crash reporting is only for the release build, as you don’t really want to pollute Crashlytics with errors during the debug and development phase (especially since you can’t remove them).
  • Non-fatal exceptions could also be reported. Of course you don’t need to report every exception, probably just circumstances like the app being left in an unexpected state or an error in the logic, etc.
  • During development, exceptions would just be logged to LogCat, as usual.
  • To implement it in a clean way, so future changes won’t necessarily have to ripple throughout the codebase.

Here are some possible ways of do it:

1.Use the Crashlytics API

How about just using calling the Crashlytics API where it is required?

try {
  // some operation
} catch (Exception ex) {
  Crashlytics.setUserIdentifier("user id");
  Crashlytics.setString("param 1", "some value");
  Crashlytics.setInt("param 2", 10);
  Crashlytics.log("message");
  Crashlytics.logException(ex);
}

Since we only want crash reporting for the release version, we would need to conditionally invoke it only for the release build.

try { 
  // some operation 
} catch (Exception ex) {
  if (!BuildConfig.DEBUG) {
    Crashlytics.setUserIdentifier("test id"); 
    Crashlytics.setString("param 1", "some value"); 
    Crashlytics.setInt("param 2", 10); 
    Crashlytics.log("message"); 
    Crashlytics.logException(ex); 
  }
}

How about during development, we still want to log the exception. Right?

try { 
  // some operation 
} catch (Exception ex) { 
  if (!BuildConfig.DEBUG) { 
    Crashlytics.setUserIdentifier("test id"); 
    Crashlytics.setString("param 1", "some value"); 
    Crashlytics.setInt("param 2", 10); 
    Crashlytics.log("message"); 
    Crashlytics.logException(ex); 
  }
  else {
    // logging with SLF4J
    log.info("User ID=test id");
    log.info("param 1=some value");
    log.info("param 2= " + 10);
    log.error("message", ex);
  }
}

Having blocks of code like this scattered in the codebase is starting to look a bit ugly. Also what if you wanted to replace Crashlytics with another crash reporting tool – lots of changes in the code required!

2. The Timber Way

If you use Timber for logging (and I do), another way would be to encapsulate the Crashlytics calls in a Timber.Tree which is only planted for the release build. This idea has already been mooted in various blogs and is based on the Timber example app .

public class ReleaseTree extends Timber.Tree {
  @Override
  protected void log(int priority, @Nullable String tag, @NotNull String      message, @Nullable Throwable t) {

    // only pass on log level WARN or ERROR
    if (priority == Log.VERBOSE || priority == Log.DEBUG || priority == Log.INFO) {
      return;
    }

    Crashlytics.setInt("priority", priority);
    Crashlytics.setString("tag", tag);
    Crashlytics.setString("message", message);

    if (t == null) {
      Crashlytics.logException(new Exception(message));
    } else {
      Crashlytics.logException(t);
    }
  }
}

So for the debug build you would plant the debug Tree.

Timber.plant(new Timber.DebugTree());

For the release build, plant the Tree that uses Crashlytics instead.

 
Timber.plant(new ReleaseTree()); 

The problem here is that the overridden log method only passes a String message and (possibly) a Throwable. So how would we pass the additional data like the custom key state and user info, etc.

Well I suppose we could created a formatted String (e.g. in JSON or your own custom format) containing all that information and pass it in the String message parameter. But then just passing the formatted String to Crashlytics would end up with a long log message in the console that you would have to decipher, and you would not get the state info in that nice neat table.

For instance you would have to decipher a long String like this

|key1=test string|key2=true|key3=1|key4=2.1|key5=3.0|...|

(don’t do this, it is just a simple example)

instead of:

 

If you want to make full use of the Crashlytics API for custom reporting, then you would have to parse the message string and make the appropriate Crashlytics calls.

This is doable, but is starting to get a bit messy again with the need to format and parse the custom data string.

3. Just Log It

As I already want to log the exception somewhere depending on the build, could I just incorporate the Crashlytics API into the logging?

I was using SLF4J for logging anyway, in my case the SLF4J-Timber library (but would also work for SLF4J-Android ).

Logger log = LoggerFactory.getLogger(this.getClass());

try { 
  // some operation 
} catch (Exception ex) { 
  log.error("message", ex);
} 

I can’t extend the SLF4J Logger  that is retrieved from the LoggerFactory, but I can use the decorator pattern to create a wrapper for that Logger.

public abstract class LoggerWrapper {

  private final Logger log;

  public LoggerWrapper(Class clazz) 
  {
    log = LoggerFactory.getLogger(clazz);
  }

  public LoggerWrapper(String className) 
  {
    log = LoggerFactory.getLogger(className);
  }

  public Logger getLogger()
  {
    return log;
  }

  public void trace(String msg) 
  {
    log.trace(msg);
  }

  public void trace(String format, Object arg) 
  {
    log.trace(format, arg);
  }

  // all the other delegated log methods
.

.

.

}

(Hint: In Android Studio, to handle delegating the methods in the wrapper class just use Code -> Delegate Methods… to generate the code, and you only have to delegate the methods that you intend to use.)

I can now subclass the log wrapper and add additional error methods to pass in the custom Crashlytics info.

public class ErrorLogger extends LoggerWrapper {

  public ErrorLogger(Class clazz) {
    super(clazz);
  }

  public ErrorLogger(String className) {
    super(className);
  }

  // additional methods for custom crash reporting

  @Override
  public void error(String userId, Map<String, String> stringState,   Map<String, Boolean> boolState, Map<String, Integer> intState, Map<String, Double> doubleState, Map<String, Float> floatState, List<String> messages, Throwable t)
  {
    // Crashlytics calls here using data from the parameters
    // e.g.
    // stringState.entrySet().stream().forEach(entry -&amp;gt; Crashlytics.setString(entry.getKey(), entry.getValue()));
  }
}

Unfortunately, this can be a bit messy as you would have to pass in different parameters for each type of state data (String, Boolean, Integer, Double, Float). This is so you would know whether to call Crashlytics.setString(), Crashlytics.setBool(), Crashlytics.setInt(), etc.

How about encapsulating that custom data into a data class, so that we would only need to pass one parameter.

Here is one that I use:

Now I just need to pass that in the log wrapper subclass, and make the appropriate Crashlytics calls.

Of course for the debug build, I would have a different log wrapper subclass where the additional log method just logs to LogCat.

private static final String MAP_FORMAT_STRING = "{} = {}";
// additional methods for error reporting
public void error(ErrorReport errorReport)
{
  errorReport.getStateString().entrySet().stream().forEach(entry -> getLogger().error(MAP_FORMAT_STRING, entry.getKey(), entry.getValue()));
  errorReport.getStateBool().entrySet().stream().forEach(entry -> getLogger().error(MAP_FORMAT_STRING, entry.getKey(), entry.getValue()));
  errorReport.getStateInt().entrySet().stream().forEach(entry -v getLogger().error(MAP_FORMAT_STRING, entry.getKey(), entry.getValue()));
  errorReport.getStateDouble().entrySet().stream().forEach(entry -> getLogger().error(MAP_FORMAT_STRING, entry.getKey(), entry.getValue()));
  errorReport.getStateFloat().entrySet().stream().forEach(entry -> getLogger().error(MAP_FORMAT_STRING, entry.getKey(), entry.getValue()));
  errorReport.getMessages().stream().forEach(m -> getLogger().error(m));
  errorReport.getException().ifPresent(ex -> getLogger().error("Exception:", ex));
}

Now I just have to use my log wrapper subclass instead of the SLF4J Logger.

ErrorLogger log = new ErrorLogger(this.getClass());
// instead of ...
// Logger log = LoggerFactory.getLogger(this.getClass()); 

try { 
  // some operation 
} catch (Exception ex) {
  ErrorReport errorReport = new ErrorReport();
  errorReport.setId("test id")
     .addState("param 1", "some value")
     .addState("param 2", true)
     .addState("param 3", 10)
     .addMessage("message")
     .setException(ex);
  log.error(errorReport); 
}

This is just one possible way that I have chosen, please let me know if anyone has any other good ways of doing this.

Additional Things to Do

I have kept the code examples in this post as simple as possible, but you would probably have to make additional changes to incorporate these ideas. For instance you might want to disable Crashlytics in the debug build, either programmatically or in the manifest.

<meta-data android:name="firebase_crash_collection_enabled" android:value="false" />

Also it is up to you how to separate the different versions of the classes for use in different builds. I just use different source sets for release and debug.

 

Android Studio 3.0 – Initial Impressions of Tool Support

I have been using Android Studio 3.0 since since the alpha versions, and it is good to see it finally released.

This is just some initial comments on using some common and new Android tools and libraries with the 3.0 version of Android Studio and the associated Android Gradle plugin.

Hopefully this will be useful for anyone thinking of upgrading from Android Studio 2.x.

Java 8 Finally, Maybe

Java 8 was released back in 2014, and with Android Studio 3.0 it is finally supported in Android. Finally I can say goodbye to RetroLambda (although many thanks to the authors for this very useful library).

Just be aware that many of the Java 8 language features will only be available if the minSkdVersion is 24.

Since devices running Nougat or Oreo are still in the minority, supporting older versions will continue to be an issue for a while if you want to use Java 8. Some ideas floated around to support older API version, are to have min API version flavors or  do Build.VERSION checks for the API version in the code to provide alternative code paths.

Desugar

The Jack tool chain has been replaced with the desugar tranformation process to support Java 8. Some older libraries and code may have some problems with this build stage.

JUnit 5

JUnit 5 has also been released recently, and with the help of this plugin you can use it for unit testing in Android Studio.

However if you use JUnit 4 Rules in your tests, there is limited support in JUnit 5 as they are meant to be replaced with extensions. Some Rules may be supported with the JUnit 5 migration support, but if not you will have to wait until the Rules are rewritten as extensions or not use the Rules at all.

Of course this is only for unit testing. Instrumentation tests still depend on JUnit 4 and the AndroidJUnitRunner.

Update (7 Dec 2017)

The authors of the JUnit5 plugin have stated that they now have support for JUnit 5 in instrumentation tests. I won’t be using it for a while since it requires the minSkdVersion to be 26. If anyone has tried this, please let me know how well it works.

Architecture Components, RxJava

I have also been using some of Google’s Architecture Components, but only the ViewModel and LiveData. This is another alternative to design patterns such as MVP or MVVM with Databinding.

If you use RxJava, then this will of course continue to work as it is compatible with Java 6. You may consider replacing RxJava with the more light weight LiveData for activities and fragments, and they can be adapted to each other.

Dependency Injection with Dagger

I have had no problems with Google’s Dagger, but be aware of the API changes for the Android Gradle plugin 3.0 in the gradle build file (e.g. implemention instead of compile, annotationProcessor instead of apt, etc).

See the Android Gradle plugin 3.0 migration guide.

Butterknife, Timber

Some common utilities like Butterknife and Timber for logging are working fine. I’m also using the slf4j binding for Timber without any trouble.

JRebel for Android

During the Android Studio alpha and beta process, JRebel for Android was always playing catch up with the latest version. I still have issues with the current version (2.4.14) of their free edition, so I am using instant run instead at the moment.

I’m a big fan of JRebel so I’m hoping they will have a stable version of their android plugin for Android Studio 3.0 soon.

Update (31 Jan 2018)

JRebel for Android is no longer being actively developed.

AOP with AspectJ

There are various AspectJ plugins for Android,  and the one I have been using in Android Studio 2.x was this one. Unfortunately it doesn’t support the Android plugin for Gradle 3.0 yet, but the author seem to be working on it so hopefully the support will be there soon.

Update (15 Nov 2017)

Version 3.2.0 of the GradleAspectJ-Android plugin now supports the Android plugin for Gradle 3.0. I like this plugin because it supports both aj files and annotation style aspects.

Things will get better

Of course since Android Studio 3.0 has only recently got general release, we should expect tool and library support to improve going forward.

I have only scratched the surface with some common Android tools and libraries, and there are many more around. Please let me know of your experiences with others.

Another Defection to Android Studio

Like many other developers out there, I have been using Eclipse as my main IDE for many years now. However for Android development I have decided to take the plunge and migrate to Android Studio (especially since it has finally been released).

Here is a blog post I found that closely echoes what I have long thought regarding the issues with Eclipse:

http://engineering.meetme.com/2014/02/a-tale-of-migrating-from-eclipse-to-android-studio/

Build, build, build

For me, another reason was that the Ant build files I was using to handle building different versions (free vs paid, dev vs release, etc) were getting too complicated to manage easily. So I can now change over to Gradle at the same time, since that’s what Android Studio uses by default.

Gradle has the concept of build variants to handle building different versions of an Android app.

The Recurring Eclipse Re-install

Here are some other problems that I personally have had with using Eclipse.

  • Plugins, well not the plugins themselves, but having too many plugins. I’ve found that having lots of plugins in one Eclipse installation can cause Eclipse to misbehave , especially after several updates. There are several ways I use to get around this:
    • Keep separate Eclipse installations for different types of development, e.g one for Java, one for Android, one for Cloud, etc. Therefore each installation will only have a few plugins relevant to the type of development. However this is not always convenient if a project does require multiple types of development.
    • Every so often, when Eclipse starts to play up, do a fresh re-install of Eclipse (along with the latest version of the plugins required).
  • Intermittent miscellaneous bugs, e.g. cut and paste stops working, builds not alway done automatically, etc. A lot of these issues are more of a nuisance rather than being a serious problem, but all the same it tends to kill your productivity (and isn’t that why we use IDE’s in the first place?).

No Pain, No …

Make no mistake, despite what the Android Studio documentation might try to tell you, migrating a non-trivial project will take some time and probably involve some pain. But worth the effort I think.