Image for post
Image for post
Photo from by Brian Wangenheim

A/B testing is not easy: designing an experiment and analyzing data properly is a massive task, and technical implementation adds even more complexity to the mix. In this article, I will show how to streamline that last part of the A/B testing process on Android to focus on what matters the most — your data.

A/B testing complexity

Let’s start with the complexity associated with A/B testing before I offer the solution. So, what are the problems?


Scope functions in Kotlin are powerful tools that can make the code more concise but, like any powerful tool, can also ruin it if not used properly. This article covers the typical scope function issues and gotchas and offers mitigation practices.

(The article is based on my 25 min Droidcon Online presentation, but provides more examples and details. Scroll down for the best practices list.)

0. Scope functions overview

First, let's do a recap.

Image for post
Image for post
Kotlin standard library offers five scope functions, four of which are defined as extensions

Scope function is the function executes arbitrary code (lambda that you pass) in the scope of the context-object.

Extension function is the function that takes a receiver, which becomes this inside the function and serves as the context. …


Image for post
Image for post
Image by Matt Crampton

So what’s the problem with NullPointerException in Kotlin? Both Google and JetBrains promote Kotlin as safe & interoperable language, not necessarily emphasizing enough that it’s not intrinsically free from NPEs, in fact, if misused Kotlin code will throw as many KNPEs and similar exceptions. How do we convert old Java code into Kotlin safely then?

1. Know the exact sources of the problems

  • Kotlin is not that safe on the boundaries: Nullable/NonNull annotations in Java code by default produce warnings in Java and compiler errors Kotlin and passing null to the NonNull parameter does nothing in Java, until you dereference this null, but it throws a runtime error immediately in Kotlin. …


Image for post
Image for post

I decided to learn how to surf several months ago, I watched YouTube tutorials and have practiced weekly since then. Yet, I’m still pretty bad at that. So bad that this weekend I finally took the first professional lesson and that lesson taught me not just how to surf but also how to answer the question “Why do we need to learn all these basics like algorithms and data-structures?

With my surfing activity, I was trying to overcompensate the lack of stability and speed with different timing and more intense paddling, effectively wasting a lot of time and energy doing so. …


For anything but Unit-tests.

Image for post
Image for post

My team works on one application, it has decent (80-90%) code coverage and yet every month we have regressions detected during manual QA process, which slip through automated tests. Why is that?

Well, because code coverage doesn’t warrant good tests. Test coverage information only helps with unit-tests and almost useless for everything else. Moreover, the mere presence of the coverage number can be harmful!

What was wrong with our code coverage?

Our SDET team measured code coverage for end-to-end UI tests (those that would click on buttons in the app on users’ behalf). Some time ago management asked SDETs to measure the coverage and to achieve a certain number in it. So they did. In fact, to maintain a high number they have to update the tests every time the UI changes (and it changes with every release for a mobile app) so they spent more time (and money) fixing tests and reacting on false-positives than on increasing the actual test coverage (management even went that far to recommend measuring coverage for manual tests, which fortunately never happened, and we have different guidelines now so I can safely tell this story). …


Image for post
Image for post

RxJava in the unit-tests can swallow the exception which would crash the real Android app. Here is how to fix the tests.

Suppose we’re using RxJava in our Android app, and we cover it with unit-tests. Does it mean the same code that passes the test won’t crash the app in production on the same input data? No! But why?

Let’s check the simplified example. The waitAndFail function emulates some background work and then it throws an exception. In theory, this test should fail, in practice, it prints an error message to the console and finished successfully. …


Image for post
Image for post

Kotlin Data Classes are great; they’re concise, they’re null-safe. Gson is great as well; it’s a de-facto standard for JSON parsing on Android for a good reason. But! Data classes parsed via Gson guarantee neither null-safety nor even default values. Or do they?

The Problem

There is an excellent article describing an issue. TL;DR if you have a data class like that

the default values will be ignored, so JSON like {"intValue":2} will produce object {intValue=2,srtValue=null} instead of expected {intValue=2,srtValue="default"}

Good news, there is a way to fix that!

The Solution

Let’s change our data class a little bit.

Notice the subtle difference? This class defines default values for all fields. That’s it! As soon as all the fields have the default values, Gson will respect them when the corresponding fields are missing in JSON (if you don’t need the default values, use nulls). …


The simple strategy for dealing with exceptions.

Image for post
Image for post

Exception handling is still a debatable topic nearly 60 years after the invention. Thirty years later Java pioneered the checked exceptions, another clever but often misused concept which drew a lot of criticism and was abandoned by followers like Scala and Kotlin. Anyway, the exceptions are still there for most of the application-level languages, and handling is on us.

So how should we work with exceptions? I want to advocate for the simple strategy I follow for the last several years.

Pre-conditions

Before starting with the strategy itself, let’s cover the typical edge cases and the assumptions it relies on. …


Image for post
Image for post

A lot was written about the TDD approach, in essence, it relies on one assumption — your tests are fast. If the tests are slow Red—Green—Refactor cycle is permanently stuck on the tests execution phase rather than on coding. That also assumes that you do know how to write tests. Many people try and give up because they’re not feeling productive, and it’s hard to keep doing something that slows and sacrifice the performance for some imaginary future stability or maintainability gain.

But what if there is a way to try TDD, even with slow UI tests, and still increase the productivity and the code coverage at the same time? …


Image for post
Image for post

Dagger is an excellent tool for dependency injection (DI). It does most of the job in compile time, making applications smaller and faster and dependency resolving safer.

What’s not great is the documentation. Dagger is not a simple tool by any means. It requires a certain level of understanding to be used properly and efficiently.

Unfortunately, Dagger’s official page provides only a simplified example and explains how to build the dependency graph. It doesn’t provide insights into what we’re building and why. …

About

Dmitry Si

Software developer. Most recently Android Java/Kotlin engineer. Former manager, desktop and embedded software creator. https://github.com/fo2rist/

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store