Friday, March 9, 2018

DI on Android (Kotlin) using an SPI

Most developers are intimately familiar with what an API is. However, mention SPI, and many will look like a question mark. This blog entry explains what it is, discusses its merits/shortcomings and shows how it can serve as a simple Inversion of Control or Dependency Injection pattern on Android for providing testing mocks.

So what is an SPI anyway?

The term SPI is an abbreviation of Service Provider Interface, and it's nothing more than a contract (interface) with pluggable implementations (classes) discovered lazily at runtime. I have also seen this pattern referred to as Service Locator and Service Registry. It likely has a older history than I am aware of (you can use it if you have introspection and reflection available in your language) but I made its acquaintance with Java 1.3 which introduced these service providers as a way of facilitating loosely coupled modules. In Java 6 the pattern was embraced further by the addition of the SericeLoader class and whenever you use an XML parser, a JSON parserspecial character sets, an encryption cipher, an image decoder etc. on the JVM you likely went through an SPI to get to it.

How does it work?

I was going to explain how it works in detail but it occurred to me, that the online Android documentation for ServiceLoader does it better than I possibly could! So let me just sum it up; all you do is:
  1. Declare that you implement a given interface by providing a /META-INF/services/INTERFACE_NAME file with the content pointing to the class implementing the interface.
  2. In your app at runtime, you use ServiceLoader to obtain an instance to the implementation (note that there could be more than one).
  3. Use Gradle to configure the implementations

I actually think of this as Dependency Injection and it was a thing long before people started writing complex libraries for achieving IoC/DI (Spring, Seam, Dagger, Guice, EJB3, Koin etc.). The beauty of this pattern is that you do not need a dependency injection container, you do not need a mocking library and there is no configuration beyond what you include in your classpath. It obviously has limitations over the full-fledged DI containers and it also suffers from the problem of being dynamic - as in, there is no compile-time verification when you assemble your application and there is a theoretical runtime overhead of doing discovery and class-loading. As you have probably guessed, I am a fan of Inversion of Control but not necessarily a fan of the many Dependency Injection manifestations that pop up; they all start out wanting to solve a simple problem but usually turn out to become just another layer of indirection one has to comprehend.

Android and Kotlin specifics

I link to the Android documentation for ServiceLoader above, so that obviously means the patterns also works on the Android platform. For it to work for an Android ARchive module (AAR), you need to create a /resources folder (New -> Folder -> Java Resources Folder inside Android Studio) and in here place your service registration file /META-INF/services/INTERFACE_NAME.


You can also use plain vanilla Java JAR's to the same effect. Since Java and Kotlin interoperates so wonderfully, things works equally well using Kotlin!

Dependency Injection through the classpath

I use this mechanism in order to be able to swap out a real (remote) web-service repository with a fake/mock (local) one, so that my unit-tests can run super fast. Depending on your needs, you may also have your functional/UI tests use this fake/mock - or you could even provide a 3'rd implementation - all through the power of Gradle.


If you are a commuter like I am, with flaky Internet connection on the train, this also gives you a way of remaining productive during your transit to/from work. You may do this simply by selecting a Gradle buildtype.


Of course, Android also allows you to use flavors if you makes more sense to your project requirements.



As I mention further up and as should be immediate apparent from reading the documentation for ServiceLoader, you could have multiple implementation of a given interface. Sometimes you really just need one so you grab the first you can get while other times you go through all. At some point, you are likely to require a chain, order or priority, in which case you may simply add a method to your interface and let the various implementations specify their relative order - akin to how you work with z-order for a UI.

It's not rocket science and it has its limitations, but if all you are after is loose coupling and a way to work with multiple implementation of something (in my world this oftes comes up in testing), then the build-in SPI approach is a really simple and powerful mechanism. I am surprised more people don't know about it but resort to much more complex solutions when its really overkill - let me know in the comments what you think. 



Monday, November 27, 2017

Klaphatte til app brugere

This entry is in Danish, as it contains quotes in this language which can not readily be translated without loss of meaning.

Som app udvikler, må man være parat til at modtage en del flak (læs: beskydning med spredhagl fra folk der ikke ved bedre men brokker sig i øst og vest) og det er jo et relativ kendt fænomen der er skrevet om utallige steder som f.eks. her. Der sker bare noget med folk når de i relativ anonymitet, får lov til at udtale sig og bedømme på et meget spinkelt grundlag - pludselig er de eksperter og kunne lave det meget bedre selv.

Eksempel på en klaphat
I dag modtog jeg f.eks. følgende review fra Jesper (fulde navn og email bekendt af redaktionen) som jeg, som så ofte før, besvarer inde på Google Play.



Jesper er jo et klassiske eksempel på en fejl-informeret bruger med en inkompatibel tlf. der ikke helt har brugt tid på at undersøge sagen nærmere. Fred være med det tænker jeg, NFC formater er også et tricky emne for alm. mennesker at forholde sig til. Men så modtager jeg en opfølgende email:

Min telefon har en NFC chip. Problemet er at appen ikke er sat op til alle nyere telefoner og kun meget få udvalgte. Det er jo idioti. Måske jeres app ville få en anelse bedre rating hvis den nu var udviklet ordentligt..

Ok, manden har tydeligvis ikke forstået noget af det jeg skriver hverken på Google Play, på min blog eller i svaret til hans review. Tilmed indgår nu også nedladende ord som "idioti" ligesom app'en ikke er "udviklet ordentligt". Jeg bliver lettere irriteret over tonen, og skriver tilbage igen, denne gang på hans eget sprog:

Hej Jesper,
Heldigvis er app'en en af de bedst ratede Rejsekort app's på Google Play. Ratingen ville dog være endnu bedre, hvis det ikke var for klaphatte som dig der tror de ved bedre end de gør. Hvis du vil vide lidt om hvordan det i virkeligheden forholder sig, så kan du læse hvad jeg skriver om sagen her:
http://blog.bangbits.com/2016/05/rejsekort-nfc-og-smartphone.html
For at opsummere; nej NFC er ikke bare NFC, der finder mange variationer og Rejsekort A/S benytter sig af Mifare som desværre ikke alle NFC chip understøtter. App'en er sat op til altid at tillade installation af Rejsekortscanner på nye tlf, kun hvis jeg får tilbagemeldinger fra brugere, har jeg mulighed for at filtrere fra i Google Play. De fleste mennesker er flinke og forstående for denne udfordring som jeg ikke selv er herre over, andre tror de ved bedre og giver dårlig rating selv om det drejer sig om en gratis app der hjælper 20.000 mennesker til daglig. Du bestemmer naturligvis selv hvilken kategori du ønsker at tilhøre. :)

Her synes jeg ligesom jeg gør det synligt hvilket tyndt grundlag Jesper egentlig befinder sig på ligesom jeg giver ham en mulighed for at bekende sig til den kategori at personer der får lært noget nyt og indrømmer det - det gør vi alle jo fra tid til anden. Men nej...

Kalder du mig for en klaphat..?? Nu er det jo ikke mig der har udviklet noget der ikke virker. Det er jo dig. Så klaphatten må jo være dig selv. Måske du kunne finde ud af at udvikle en converter, så app'en kan bruges på endnu flere. Hvis det kun er 20000 ud af 2 mio. Rejsekortbrugere må man jo sige at du laaaaaaaaaangt fra har gjort dit arbejde godt nok. Men fint med mig at du ikke kan tåle ærlig og konstruktiv kritik og at du bare bliver modbydelig og ond. Men det er fint at du har givet mig det på skrift, så jeg kan videregive til én af mine rigtig gode venner der er journalist og som hader sådan nogle mennesker som bare sviner sine kunder eller måske kommende kunder til. Det er i alle fald ikke det bedste udgangspunkt at sætte sig selv i. Der er noget der hedder at kunden altid har ret. Måske du skulle tænke lidt mere over det..
Jeg ved ikke hvad der får Jesper til at opfatte sig selv som kunde (de betaler normalt for noget) for han er allerhøjst bruger af en app jeg gratis stiller til rådighed. Nuvel, jeg overvejer at ignorere klaphatten men vælger alligevel at forsøge at forklare ham hvorfor jeg ikke mener hans overfladiske og usaglige kritik er speciel konstruktiv:

Jesper,
Fakta er, at jeg skriver højt og tydeligt i Google Play følgende:
"Hvis app'en ikke virker på din smartphone p.g.a. manglende hardware support, undlad venligst at give negativ feedback, da det reelt er telefonens og ikke programmets skyld! Jeg hører dog gerne fra dig, således at jeg kan opdatere listen og undgå situationen for andre. :)"
Men det har du tydeligvis ikke taget dig tid til at læse, men brokker dig med en meget negativ tone som om du har forstand på hvad du taler om, og det har du tydeligvis ikke hvorfor jeg skriver direkte til dig omkring årsagen til at app'en ikke virker på din tlf. og hvorfor den ej heller nogensinde kan komme til det. Jeg linker også til en artikel på min blog der i årevis har beskrevet situationen, som du nemt kunne have fundet hvis du havde gjort dig den ulejlighed at søge på Google engang.
Dét du kommer med er ikke konstruktiv kritik, af de årsager jeg nævner ovenover - det er ikke noget at gøre, du er nødt til at købe din en anden tlf. hvis du vil bruge app'en! Desværre fortsætter du din dårlige stil, hvor du overhovedet ikke forholder dig til hvad jeg forklarer (har du overhovedet læst det?) og jeg kan derfor naturligvis ikke bruge mere tid på dig, da du virker uden for pædagogisk rækkevidde. Jeg ved ikke hvad du tror du får ud af din journalist trussel, men det vil jeg da i givet fald glæde mig til at se - du vil nemlig primært udstille dig selv som noget af en klaphat.
Jeg skulle nok bare have fulgt min intuition og ignoreret ham, for snart efter modtager jeg følgende:

Casper klaphat...
Spadser-Casper...
Mongol-Casper...
Casper Knold...
Casper Papkasse...
Lorte-Casper...
Casper Jubel-Idiot...
Koka-Casper...
Ja jeg kunne blive ved. Det er faktisk meget sjovt at du tror at du forstand på udvikling af apps.. Jeg ved tilfældigvis at det kan lade sig gøre at konvertere eksempelvis en bluetooth-version og dermed ville det også, hvis man altså er dygtig og intelligent nok til det, være muligt at udvikle en konverter til en NFC chip..
Men det formår du nok ikke med din mikroskopiske fuglehjerne.. Jeg tænker bare: Du kan jo ikke formulere dig korrekt, ej heller stave, og ej heller argumentere godt nok for din viden. Og jo jeg har set din blog, hvilken der ikke er basis for at prale af.. Den ligner noget min 8-årige nevø kunne lave meget bedre. Han kunne i alle fald have stavet, formuleret og konstrueret den bedre end du har formået.
Men jeg takker for din venlighed og for din uduelighed. Der er jo tydeligvis ingen grænser for hvad jeg kan tillade mig, med den måde du opfører dig på. Nu har jeg jo ikke tidligere kaldt dig alle mulige mærkelige ting, selvom du allerede den første gang du svarede på min forespørgsel svinede mig til.
Jeg tror ikke jeg behøver at kommentere på ovenstående, det taler vist for sig selv.

Konklusion
Man kan ikke gøre alle tilfredse og nogen mennesker forbliver bare uden for pædagogisk rækkevidde. Den tid jeg har brugt på klaphatten Jesper kunne jeg have brugt på min familie eller noget reelt arbejde. Så lektionen for i dag må være, pas på klaphatte hvis eneste formål er at stjæle din tid!

For at få bare en lille smule ud af den tid jeg har brugt på at forsøge at trænge igennem til Jesper, har jeg foreviget den uredigerede dialog i dette blog-entry - så har klaphatten også noget på skrift til hans journalist ven. :)


Thursday, April 20, 2017

Android NFC radio control using instrumentation

I have always worked a lot with NFC on Android. For this reason, I tend to favor real devices over emulators, since missing an NFC radio means there's no way to truly test the intricacies of radio communication. Unfortunately, one can not power cycle the NFC radio using any official API unless going through hoops and using rooted devices, so ensuring NFC radio power state during testing is an uphill battle. For instrumented test scenarios however, there is actually a way forward.

UIAutomator to the rescue

While not as elegant as using an API, we can launch the settings screen for NFC and manipulate it through the use of instrumentation. This is NOT possible using modern Espresso which limits you to the app under test, but thankfully the UIAutomator framework is still available. The accompanying UIAutomator Viewer tool (which has now moved to sdk/tools/bin/uiautomatorviewer) is a great asset in this regard as it helps us identify the widget we need to manipulate.


What the NFC toggle button is named is not consistent across devices and versions of the operating system, so we have to get a bit heuristic here. In practice, looking through my some 10 devices with various versions of Android using various custom skinning, I have identified 3 unique resourceId's for the toggle button. These are com.android.settings:id/switch_widget, android:id/switchWidget and android:id/switch_widget. Unfortunately, on Android 7 (for Huawei devices anyway) it seems as if launching the ACTION_NFC_SETTINGS intent will not actually get you to where you want, but requires an additional navigational step. This complicates the code a bit but it's still possible to make it work.

To launch the Settings activity prior to any Activity under test, we need to pass along the Intent.FLAG_ACTIVITY_NEW_TASK flag. From there, we can write our logic to help us toggle NFC state.

    private void toggleNfc(final Context context) {

        final Intent intent = new Intent(Settings.ACTION_NFC_SETTINGS);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);

        final UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());

        findAndToggleNfcInUI(device);
    }

    private void findAndToggleNfcInUI(final UiDevice device) {

        final UiObject toggleButton = device.findObject(new UiSelector()
                .resourceIdMatches("(com.android.settings:id/switch_widget|android:id/switchWidget|android:id/switch_widget)"));

        try{
            toggleButton.click();
            device.pressBack();
            return;
        }catch(UiObjectNotFoundException e){
            UiObject2 nfcMenuItem = device.findObject(By.textContains("NFC"));

            // Move up in the view hierachy until we're at a clickable item
            while(!nfcMenuItem.isClickable()){
                nfcMenuItem = nfcMenuItem.getParent();
            }

            // Issue click to navigate into menu
            nfcMenuItem.click();

            // Wait for any UI jitter to settle
            getInstrumentation().waitForIdleSync();

            // Try to toggle NFC button using this new child activity
            findAndToggleNfcInUI(device);
        }
    }


Composable test aspect using a JUnit rule

The code above is fine and dandy, but I'm a big proponent of composable and reusable aspects, so lets take advantage of the fact, that we can encapsulate the functionality nicely using JUnit's rule mechanism. If you're new to these you may read up on them here. The resulting NfcStateRule.class can be seen below.

/**
 * JUnit test rule for controlling NFC radio power state. Useful in order to ensure NFC is
 * enabled or disabled prior to executing a test.
 */
public class NfcStateRule implements TestRule {

    private static final String NFC_TOGGLE_WIDGET_RESOURCEIDS =
            "(com.android.settings:id/switch_widget|android:id/switchWidget|android:id/switch_widget)";

    private final boolean desiredState;

    public NfcStateRule(boolean desiredState) {
        this.desiredState = desiredState;
    }

    @Override
    public Statement apply(final Statement base, final Description description) {
        return new Statement() {
            public void evaluate() throws Throwable {

                try{
                    final Context context = InstrumentationRegistry.getTargetContext();
                    ensureNfcState(context, desiredState);

                }catch(final Throwable e){
                    e.printStackTrace();
                }
                base.evaluate();
            }
        };
    }

    private void ensureNfcState(final Context context, final boolean desiredState) {
        if(desiredState){
            ensureNfcIsEnabled(context);
        }else{
            ensureNfcIsDisabled(context);
        }
    }

    private void ensureNfcIsDisabled(final Context context) {
        if(isNfcEnabled(context)){
            toggleNfc(context);
        }
    }

    private void ensureNfcIsEnabled(final Context context) {
        if(!isNfcEnabled(context)){
            toggleNfc(context);
        }
    }

    private boolean isNfcEnabled(final Context context) {
        final NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(context);

        if (nfcAdapter == null) {
            return false;
        }

        return nfcAdapter.isEnabled();
    }

    private void toggleNfc(final Context context) {

        final Intent intent = new Intent(Settings.ACTION_NFC_SETTINGS);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);

        final UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());

        findAndToggleNfcInUI(device);
    }

    private void findAndToggleNfcInUI(final UiDevice device) {

        final UiObject toggleButton = device.findObject(new UiSelector()
                .resourceIdMatches(NFC_TOGGLE_WIDGET_RESOURCEIDS));

        try{
            toggleButton.click();
            device.pressBack();
            return;
        }catch(UiObjectNotFoundException e){
            UiObject2 nfcMenuItem = device.findObject(By.textContains("NFC"));

            // Move up in the view hierachy until we're at a clickable item
            while(!nfcMenuItem.isClickable()){
                nfcMenuItem = nfcMenuItem.getParent();
            }

            // Issue click to navigate into menu
            nfcMenuItem.click();

            // Wait for any UI jitter to settle
            getInstrumentation().waitForIdleSync();

            // Try to toggle NFC button using this new child activity
            findAndToggleNfcInUI(device);
        }
    }
}

To use our NfcStateRule in an actual test, simply include it as a member and specify the desired NFC radio power state by passing a boolean with the constructor.

@LargeTest
@RunWith(AndroidJUnit4.class)
public class NfcDisabledTest {

    @ClassRule
    public static final NfcStateRule nfcStateRule = new NfcStateRule(false); // Make sure NFC is disabled

    ...actual test...
}

Voila, now it's possible to setup test scenarios correctly using the NFC radio. This is important for many of my Espresso tests to work consistently and reliably every time, as demonstrated by a screen below which tests the UI when the user has disabled NFC.

An example of an Activity/Fragment whos UI-state depends of the state of the NFC radio.

Conclusion

Where there is a will, there is a way! The above is not nearly as clean as having an API which we have available for WiFi, GPS etc. For acceptance testing however, I much prefer this kind of automated UI manipulation over mocking or polluting short-circuiting logic within the app itself.

By definition, the approach must be considered fragile since the NFC toggle button can be called something different on devices I have not yet had my hands on! If you run into this problem, the fix is easy - simply use the UIAutomator Viewer and expand the regular expression to work with this custom view. In a test scenario you usually have full control of the devices anyway so it's not really a practical concern since end-users will never be exposed to the code.

As usual, the code may be buggy, may not work on all versions of Android and is definitely not production safe. You may assume a Public Domain license of the code snippets above. Feel free to contribute back in the comments if you want to share your findings or experiences regarding the matter.

Thursday, January 5, 2017

BangBits Privacy Policy


Welcome to the BangBits Privacy Policy

When you use apps and other software developer by BangBits, you trust us with your information. This Privacy Policy is meant to help you understand what data we collect, why we collect it, and what we do with it. It is important to understand, that BangBits operate both as an owner of given software and as a proxy for work developer by Customers. App's published by BangBits but taking part of a specific Customer solution are treated separately in the "Specific Products" section below.


Information we collect and why we collect it

We collect information to provide better customer experience. This may happen through various forms of remote logging using Google Analytics, Firebase Analytics or similar tool. At no time is personal data directly mappable to an identifiable user being collected. What can be collected is:
  • Device identifiers (DeviceID, IMEI and handset identifiers) in order to black-list and/or white-list otherwise fraudulent and/or abusive users.
  • Stack traces and associated debugging data when app is behaving unexpectedly
  • Behavioral data to better understand how the user is using the software

Specific Products

The following notices explain specific privacy practices with respect to specific products offered by BangBits that you may use:

"Rejsekort Kontrol"

The software known as "Rejsekort Kontrol" is located on a closed business domain on Google Play, and as such, is only accessable to (invited) users of that Organization. The app under control of BangBits takes part of a bigger software system owned by Rejsekort A/S, the danish national transit ticketing authority. No personal identifiable data is collected or transmittet to/from the app. Recent travel data from a Travelcard is inspected and collected to be submitted for backend processing by Rejsekort A/S, but this is governed by Rejsekort A/S' own Privacy Policy at: 


https://www.rejsekort.dk/~/media/rejsekort/pdf/privatlivspolitik/privatlivspolitik---13-10-2014.pdf

Monday, September 19, 2016

Best-practice Android logging

A blog post about Android logging? Must be a slow news day you are probably already thinking, but hear me out on this one. This is a quick tour around my personal best-practices and you may likely not agree with all of them - if so, let me know in the comments. ;)

Android Studio and LogCat

On Android, logging has always been an integral part of the development experience, first through the standalone LogCat utility and since through the Android Studio integration. The build-in LogCat now even offers advanced filtering capabilities based on logging level, regular expressions for package names, logging tag and the actual log messages. However, all this power is really only useful if you play along nicely at the source level as well, or you will drown in log messages without really knowing how to specify what you want to see. This is a common problem as the Android OS and any libraries you may use will spit out an obscene amount of logging, especially at the verbose level.

Use correct log levels

This isn't really Android specific since it's a problem all over the Java space. Many projects don't classify log severity correctly and some can't even be bothered to use logging at all and pipes stuff to System.out and System.err. While there are a LOT of logging frameworks for Java and they differ in functionality, most of them agree on the logging levels. On Android, the levels are:
  • ERROR – Don't use; if something this severe has happened, it is the result of an exceptional state rendering the application context invalid! The application should crash gracefully and collect relevant info for submission as a crash report to Google, CrashLytics, TPA etc.
  • WARN – Something bad happened, but the application is still able to recover and continue executing albeit perhaps in a limited fashion. A developer can filter on the WARN level in order to learn about application states which are candidates for being handled better.
  • INFO – Important business process that changes the state of the application significantly (database update, external system request, network connectivity change etc.) has completed. A developer can filter on the INFO level in order to get the big picture of what is going on at the task and flow level, which a non-developer should also be able to understand.
  • DEBUG – Detailed developer insight into core decisions, states and events.
  • VERBOSE – Don't use; very detailed trace information, intended only for temporary development insight (debugging and troubleshooting), should not risk being left in code and potentially be checked into Git (you are using Git right?). Instead you'll want to use a logging breakpoint as explained below.
I think one of the problems with logging being used wrong it that it takes too much energy deciding on the appropriate level. By reducing the levels down to just 3, it becomes somewhat simpler to decide which one to use. Use only WARN, INFO and DEBUG and use them accordingly; WARN for things that should not happen but is recoverable, INFO for major changes and DEBUG for minor changes.

Logging breakpoint

I mentioned in the above section, that VERBOSE should not be used. This is because the stuff you'd put at the VERBOSE level is so detailed that it doesn't belong in source code (logging statements riddled everywhere makes code harder to read). Instead, Android Studio offers a powerful hybrid mechanism between debugging and logging - basically it allows you to set breakpoints and rather than have these suspend program execution as usually happens, they merely log an expression to LogCat.


This makes so-called logging breakpoints a perfect fit what you would otherwise have put in verbose logging statements at the source level. You can see this feature demoed here.

LogCat Filtering

As an alternative to viewing the complete system log, the "Show only selected application" context filter is not too hard to understand and use, but even this filter will result in a lot of irrelevant garbage from app and OS libraries being used from the app process. I would like to be able to recommend using custom logging filter configurations, but unfortunately I find them too unreliable to trust in practice. For instance, it seems like (with the present version of Android Studio anyway) there is no way to whitelist debug and verbose messages (anything more detailed than info messages) and that's just a no-go for me. However, it's Ok as we can filter at a line level instead and accomplish much the same. Unless I'm hunting for something specific, I always use a black-list approach, since you never want to exclude relevant messages you do not know about in advance! This means entering a rather long and complex regular expression, but it only has to be done once. The following filter clause is a common one I use, but it will vary a lot depending on the amount of noise being generated on the specific device you are using:
^((?!(?:art|ResourceType|System|GED|libEGL|
DynamiteModule|Timeline|OpenGLRenderer)).)*$
This way it becomes a joy to see what's going on and simply jump up or down in the severity filter depending on how fine grained you want to follow along.

Tweak the LogCat settings

To the left of the LogCat window is a vertical toolbar which you may also want to utilize a bit more - it took me a while before I started taking full advantage. Press the cog wheel to go to Settings to configure the LogCat headers and remove the checkbox in "Show date and time" as well as "Show process and thread IDs". This will give you 30 characters more on each line for actual valuable info. If you need to time something, there are better ways to do so using profiling tools like i.e. systrace. Sometimes I also remove the package name since the truly interesting part remains the TAG and associated message. While you are in the LogCat toolbar, familiarize yourself with the other options. Soft wraps can be very handy when you'd rather not miss something in a large message - typically serialized data. The restart button is also going to be needed from time to time, as adb or its integration with the device, is notoriously unstable and can play tricks on you.


Even with filtering in place, sometimes you just have groups of related log info you'd rather be collapsed somehow. This is easy to do by right-clicking on the line in question (typically first line of what you want hidden in the fold) and writing an expression to match what will get folded. It's also here you can shorten excessive stack-traces and hide irrelevant internal calls.



The above screenshot shows 3 similar lines for each Firebase remote config property loaded. The screenshot below shows how the 3 related yet different lines are now folded up under the parent "Successfully obtained Firebase remote config" log line.


Last but not least, the color scheme used by Android Studio for LogCat can be improved upon a bit by making level-indication a bit more obvious (this becomes even more important when you are running a dark theme). I like to make all levels different in color and style so its easy to identify.

Log application aspects rather than class names

While the vanilla approach to writing logging statements on Android is to define a TAG constant with the same name as the class, this is a stupid practice in my opinion. Sure it is easy to adhere to (no thinking required) and can work well for small projects with only a few activities or views. This however, does not scale to projects with 50+ views and/or apps with complex life-cycles - it is simply too difficult to troubleshoot a problem and sooner rather than later you'd have to resort to a slow and tedious debugging session. So what to do instead then?
During execution of an app, one is typically interested in certain course-grained aspects of an app, and probably not so much which file logic lives in. For this reason, I like to define global logging tags based on app aspects. It doesn't matter too much what you choose for these aspect names, but make them generic and/or something that makes sense to your app. I typically always have the following:

    /**
     * Logging tag used for common UI lifecycle events
     */
    public static final String LOG_UI = "UI";

    /**
     * Logging tag used for any kind of network I/O communication
     */
    public static final String LOG_NET = "NET";

    /**
     * Logging tag used for storage; local files, preferences and databases
     */
    public static final String LOG_DATA = "DATA";

    /**
     * Logging tag used for business logic and app related things not
     * already covered by the other log tags
     */
    public static final String LOG_APP = "APP";

Reading a log now becomes super easy and at the INFO level, even non-technical people should be able to follow along if need be:

Always log major UI flow events

Apps are, by definition, highly interactive and UI heavy. It thus makes a whole lot of sense, to trace UI aspects accordingly. The nice thing about UI events, is that it's surprisingly simple to get some broad standardized logging up and running which you won't have to worry about again when adding more views in the future.
To easily log UI lifecycle events for any and all activities, simply provide a custom application class and in its onCreate() method, register for lifecycle callbacks:

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(LOG_APP, "Creating Application");

        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                Log.i(LOG_UI, activity.getClass().getSimpleName() + " created");
            }

            @Override
            public void onActivityStarted(Activity activity) {
                Log.i(LOG_UI, activity.getClass().getSimpleName() + " started");
            }

            @Override
            public void onActivityResumed(Activity activity) {
                Log.i(LOG_UI, activity.getClass().getSimpleName() + " resumed");
            }

            @Override
            public void onActivityPaused(Activity activity) {
                Log.i(LOG_UI, activity.getClass().getSimpleName() + " paused");
            }

            @Override
            public void onActivityStopped(Activity activity) {
                Log.i(LOG_UI, activity.getClass().getSimpleName() + " stopped");
            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
                Log.i(LOG_UI, activity.getClass().getSimpleName() + " saved");
            }

            @Override
            public void onActivityDestroyed(Activity activity) {
                Log.i(LOG_UI, activity.getClass().getSimpleName() + " destroyed");
            }
        });
    }

Oh and while you are in there, do yourself the favor and enable StrictMode for your debugging builds so that you bad dynamic behaviors of your app are logged.

    if (BuildConfig.DEBUG) {
        StrictMode.enableDefaults();
    }


Modern apps are often revolving around just one activity but many fragments instead, so it's equally if not more important (due to their insane complex lifecycle) to be able to log fragment events. It can be done in a similar way, by overriding onAttachFragment() in a base class for your activities:

    @Override
    public void onAttachFragment(Fragment fragment){
        Log.i(LOG_UI, "Fragment " + fragment.getClass().getSimpleName() + " attached to " + this.getClass().getSimpleName()  );
    }

Now you have an easy way to follow the UI flow of your application without which should help interpret the context of surrounding log messages. I find these major UI events to be important enough to deserve an "info" level classification. In fact, I also add various other UI/INFO level logging lines for menus etc, as in the following:

    @Override
    public boolean onNavigationItemSelected(MenuItem menuItem) {
        Log.i(LOG_UI, "Option '" + menuItem.toString() + "' selected");
    }

Logging templates

Android Studio is based on one of the most powerful IDE's available in modern programming so it also comes with handy templates. The logging templates available out of the box are context aware and will save you much time once you get used to them.


Simply enter the template characters and expand with the tab key. The templates "logm", "logr" and "logi" remains my favorites.

Don't log in production!

Logging is for development purposes - there are plenty of other tools for analytics and crash-reporting, so respect these separation of concerns and avoid compiling logging into your app. Only error, warning and info logs are acted upon at runtime, but verbose and debug levels are still compiled in. Regardless, there simply is no reason to keep any kind of logging for a production app - it's just unnecessary noise. I like to use ProGuard to strip the logging, which is a simple matter of adding the following to your ProGuard file:

-assumenosideeffects class android.util.Log {
    public static boolean isLoggable(java.lang.String, int);
    public static int e(...);
    public static int w(...);
    public static int i(...);
    public static int d(...);
    public static int v(...);
}

Note that if you are using other logging frameworks or custom wrappers, you may also want to target these explicitly to remove the bad weed "at the root" so to speak.

In conclusion

The IDE and associated toolbox is our friend, and we do best to get to know it well since much time and frustration can be saved on that account. Hopefully there's a trick or two in the above you take away from this blog post, but let me just end with showing a matrix I use conceptually when narrowing down a problem in an app based on the proposed logging levels and application aspects above.


The first matrix demonstrates filter settings when just wanting to following along and learn how the UI is put together when i.e. getting to know a new application. The second matrix is how you would think (and thus configure the logging) when wanting to screen an app quickly after a test suite has run, whether something exceptional happened anywhere. The last example shows how one would configure the filter when looking for issues in some app logic doing network communication.