Avoid Gradle Duplication in Multi-Module Android

When you have an Android project with many modules, you may find a lot of the gradle configuration being duplicated (often by copy and paste) across the build.gradle files in the various modules.

I’ve read various ways to try to avoid this on the internet, and came across this blog post recently. This post uses the name of the module to determine which gradle plugins to apply and which configuration to use for that module.

In one of my multiple module projects, I have used something similar. However the difference is that I apply the plugins required in each individual module build.gradle, and in the root build.config I use both the module name and it’s properties to add the appropriate shared configuration.

For example, here is the common shared configuration for all modules in the root build.gradle:

subprojects {
    afterEvaluate { project ->
        if (project.hasProperty("android")) {
            android {
                compileSdkVersion 30
                buildToolsVersion '30.0.2'
                defaultConfig {
                    minSdkVersion 23
                    targetSdkVersion 30
                }
                compileOptions {
                    sourceCompatibility JavaVersion.VERSION_11
                    targetCompatibility JavaVersion.VERSION_11
                }
            }
        }
}

This app is a mixed Java and Kotlin project so only some of the modules use Kotlin. In the Kotlin modules, I apply the kotlin plugin.

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

Then in the root build.gradle, I can add the Kotlin configurations to those modules:

if (project.hasProperty('kotlin')) {
        android {
            kotlinOptions {
                jvmTarget = JavaVersion.VERSION_11.toString()
            }
        }
        dependencies {
            implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
            implementation 'androidx.core:core-ktx'
        }
}

If there is common configuration that is only applicable to some modules, I can also use the module name as a filter to add that configuration for only those modules. For instance for all modules excluding the app module:

if (project.hasProperty('android') && !name.equalsIgnoreCase('app')) {
      ...
}

Likewise for modules with particular names:

if (project.hasProperty('android') && name.equalsIgnoreCase('feature-1')) {
      ...
}

Note that I use the root build.gradle to contain the common configuration to keep things simple, but there are also more complicated methods around that use the buildSrc directory instead.

Paging Library 3 and Content Provider

I needed to display the contents of a Android content provider in a recyclerview with pagination.

I also wanted to try out version 3 of the Android Paging Library (which is currently in 3.0.0.alpha2 release), but most of the sources of documentation and tutorials are targeted at accessing either a network (e.g. Retrofit) or a database.

So I wrote a simple demo app on GitHub to get Paging 3.0 to work with a content provider instead.

Just be aware that working with an alpha release means that there may be changes to the paging library in it’s final release.

Pagination for a Content Provider

How to page the contents of a content provider depends on the specific provider. However for some Android content providers, such as Contacts or Telephony, they access a built-in database so we can just pass database parameters to them.

The demo app uses the paging library with the Telephony provider to access SMS messages, and page the contents to a recyclerview.
Although I could have used the ContentResolver directly to do the query on the content provider, in the app I decided to wrap the query in a repository class to encapsulate the query parameters and the field mapping for the results.

class MessageRepository (
private val context: Context) {

fun getMessages(limit: Int, offset: Int): List<Message>
    {
        val cursor = context.contentResolver.query(
            Sms.CONTENT_URI,
            arrayOf<String>(
                Sms.Inbox.ADDRESS,
                Sms.Inbox.BODY,
                Sms.Inbox.DATE
            ),
            null,
            null,
            Sms.Inbox.DEFAULT_SORT_ORDER + " LIMIT " + limit + " OFFSET " + offset
        )

The LIMIT parameter is the number of records you want to retrieve for each page (page size), and OFFSET is how many rows to skip before retrieving records (to ignore previous pages already retrieved).

Pagination .. and Filtering

Another common scenario is to apply filtering to the query to only retrieve and display a subset of the content provider data.

As an example, to filter the retrieval of SMS messages to only return messages where the phone number contains a substring of numbers. This means we need to pass the filter string to the ContentResolver query, as well as the paging parameters.

fun getMessages(filter: String, limit: Int, offset: Int): List<Message>
    {
        val filterArg = "%" + filter+ "%"

        val cursor = context.contentResolver.query(
            Sms.CONTENT_URI,
            arrayOf<String>(
                Sms.Inbox.ADDRESS,
                Sms.Inbox.BODY,
                Sms.Inbox.DATE
            ),
            Sms.ADDRESS + " LIKE ?",
            arrayOf(filterArg),
            Sms.Inbox.DEFAULT_SORT_ORDER + " LIMIT " + limit + " OFFSET " + offset
        )

For testing ContentResolver queries, I also wrote a testing app in GitHub to quickly run arbitrary queries on content providers. I used this to check the paging and filtering parameters worked as expected before using the queries in the demo.

PagingSource – Connect Paging to the Data

One of the major changes to Paging Library version 3 is replacing DataSource with PagingSource, and it is in the load function of the PagingSource subclass that the ContentResolver query will be done (in the demo app code, indirectly via the repository class).

class MessagePagingSource(val repo: MessageRepository) : PagingSource<Int, Message>() {

    // the initial load size for the first page may be different from the requested size
    var initialLoadSize: Int = 0

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Message> {
        try {
            // Start refresh at page 1 if undefined.
            val nextPageNumber = params.key ?: 1

            if (params.key == null)
            {
                initialLoadSize = params.loadSize
            }

            // work out the offset into the database to retrieve records from the page number,
            // allow for a different load size for the first page
            val offsetCalc = {
                if (nextPageNumber == 2)
                    initialLoadSize
                else
                    ((nextPageNumber - 1) * params.loadSize) + (initialLoadSize - params.loadSize)
            }
            val offset = offsetCalc.invoke()

            val messages = repo.getMessages(params.loadSize, offset)
            val count = messages.size

            return LoadResult.Page(
                data = messages,
                prevKey = null, // Only paging forward.
                // assume that if a full page is not loaded, that means the end of the data
                nextKey = if (count < params.loadSize) null else nextPageNumber + 1
            )
        } catch (e: Exception) {
            return LoadResult.Error<Int, Message>(e)
        }
    }
}

To make the PagingSource work with the content provider, we needed to do 2 things:

1. Translate the parameters you receive in the load() function into the paging parameters to pass on to the ContentResolver query.

Page number – except for the first page, the page number for the query is the key field from the LoadParams parameter. This is also required to work out the offset parameter for the ContentResolver query.

val nextPageNumber = params.key ?: 1

Page size – set the pageSize parameter for the PagingConfig class.
For the demo app I just use a page size of 10 to make it easier for testing.

Pager(
        PagingConfig(pageSize = 10)
    )

One complication is that for the initial load (for the first page), the params.loadSize may be different from the page size requested. This can be set with the initialLoadSize parameter. It is recommended by the documentation to load enough data initially to fill several screens to handle small scrolls without having to load more data.

Pager(
        PagingConfig(pageSize = 10, initialPageSize = 20)
    )

Note that if you do not specify initialLoadSize, then the paging library will set this for you and load more than the page size you requested anyway.

(From PagingConfig source code:)

val initialLoadSize: Int = pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER

    companion object {
           internal const val DEFAULT_INITIAL_PAGE_MULTIPLIER = 3
    }

So you need to take this into account when working out the offset for the ContentResolver query.

if (params.key == null)
            {
                initialLoadSize = params.loadSize
            }

            // work out the offset into the database to retrieve records from the page number,
            // allow for a different load size for the first page
            val offsetCalc = {
                if (nextPageNumber == 2)
                    initialLoadSize
                else
                    ((nextPageNumber - 1) * params.loadSize) + (initialLoadSize - params.loadSize)
            }
            val offset = offsetCalc.invoke()
2. Tell the adapter to stop paging when the end of the data has been reached.

To stop loading data, we just need to pass null as the nextKey parameter of the LoadResult returned from the function.

We assume that no more data is available from the content provider when the data returned from the query contains less than the page size requested.

           val count = messages.size

            return LoadResult.Page(
                data = messages,
                prevKey = null, // Only paging forward.
                // assume that if a full page is not loaded, that means the end of the data
                nextKey = if (count < params.loadSize) null else nextPageNumber + 1
            )

RxJava 2, too

The demo app uses Kotlin flow as that is what is used in the Paging library 3 documentation and sample code, which makes the code easier to follow along with the documentation samples. However the demo code also has alternative classes that uses RxJava 2 instead. To run the RxJava code, modify the manifest to use an alternative activity and rebuild.

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.

Is AspectJ Still Useful for Android? Part 1

For Java projects I sometimes use AspectJ for various tasks, but for Android projects I initially decided against using it.

There were various reasons for that:

  • The structure of an Android project, and its build process, was already a lot more complicated and slower than for generic Java projects.
  • For Android development, only compile time weaving is supported for AspectJ, which makes it less useful.
  • The Android Studio IDE does not have support for AspectJ.

However I eventually found some uses for aspects when it comes to testing. Another reason I decided to give AspectJ a try was the availability of Android AspectJ plugins for Gradle. They make integrating AspectJ into the Android build process easier, I use this one.

Using aspects I can weave test code into the application code when required for a particular test. Then when no longer needed, I would just do a normal build without the aspect weaving, and the test code is kept totally isolated in the aspect file.
I found this to be a good alternative to what sometimes happens during testing:

  • adding test code for a particular test, and then having to remove it or comment it out afterwards
  • adding temporary logging to find a bug, profile some code, etc

What is it Good For, Some Examples

Testing Crashlytics

without AspectJ:

When testing for Crashlytics, the Firebase documentation recommends adding a temporary button with a click handler that crashes the app.

with AspectJ:

Instead I can weave the crash inducing code into any existing app event I like, e.g a button click, menu click, etc.

Testing exception handling

without AspectJ:

Write some temporary test code to throw an exception to see if the app handles it correctly.

with AspectJ:

Have an aspect that throws the exception from any method I choose. I can also have the pointcut advice multiple methods so that the test exception can be thrown from multiple locations.

Testing interactions with external services

(For instance, checking that Google Play services are available.)

without AspectJ:

Write some test code to simulate the response from the external service that you want to test for. For example, when checking if Google Play services are available then you might want to test that your app correctly handles an unsuccessful result code such as SERVICE_MISSING, SERVICE_VERSION_UPDATE_REQUIRED, etc.

with AspectJ:

Write an aspect that advices where you call the external service, and have it return the result that you want to test for. It’s up to you whether you want to actually make the call to the external service in the advice using proceed(..) or just have the advice return the result you want.

The Usual Stuff, like Tracing

One of the most common uses of AspectJ is for method tracing, this can be useful for profiling the app or when trying to track down that hard-to-find bug.

without AspectJ:

Add temporary trace logging to the app code that you think might need profiling or be causing a bug.

with AspectJ:

Enable tracing of method calls with logging for any or all method calls that I want by adjusting the pointcut for of a tracing aspect. It’s quick and easy to write a pointcut to advice all methods in a class, for example.

There are plenty of examples of AspectJ tracing aspects around, or you can use android tools to generate trace logs by instrumentation.

An AspectJ Example

Let’s start with a common scenario, a service class with a method that makes a network call (presumably to get some data and apply business logic to it). This service method would be called by an activity or fragment in the app.
Note that this example assumes that you are already familiar with how to program in AspectJ native syntax.

public class BusinessService {

  public Result serviceNetworkCall() {
    return ... // return some data from a network call
  }
}

I want to test that the activity/fragment correctly handles the situation where there is an error when the service method is called. Here is an aspect that can be used to simulate an exception being thrown on the method call.

aspect TestExceptionInjector {

  pointcut injectTestException(): target(BusinessService) && execution(public Result *(..));

  Result around(): injectTestException() {
    // can allow the adviced method to run first before the exception is thrown by calling <em>proceed()</em> first, in case we want some side effects to occur

    // simulate an exception being thrown from the method(s) adviced
    throw new RuntimeException("TEST");
  }
}

In this aspect the pointcut will advice the service method, and throws an exception instead its normal execution. Then we can run the app to check that the Activity / Fragment that calls the service method does handle this error properly.

I can also do this where a reactive library, such as RxJava, is being used in the service class.

public class BusinessServiceRx {

  public Observable<Result> serviceNetworkCallRx() {
    return ... // return some data from a network call
  }

  public Observable<Result> anotherServiceNetworkCallRx() {
    return ... // return some data from a network call
  }
}

privileged aspect TestExceptionInjectorRx {

  pointcut injectTestExceptionRx(): target(BusinessServiceRx) && execution(public Observable<Result> *(..));

  Observable<Result> around(): injectTestExceptionRx() {
    // simulate an exception being thrown downstream from the method(s) adviced
    return Observable.error(new RuntimeException("TEST"));
  }
}

This example also shows that the pointcut in the aspect can advice multiple service methods.

Determining When to Build with Aspects (or Not)

Next, we need to weave the aspect in the example into the app code, and then find some way to determine when to do a build with aspects for testing, and when to just do a normal build. In the concluding part of this post, I will show the workflow I use as one way of doing this.

For Testing Only?

As I have mentioned, when using aspects the test/logging code resides in an aspect file that is isolated from the app code and should never get into a release build. However it is always available when I want to run the tests again, and there is no need to worry about forgetting to remove temporary test code after the testing is done.

The use of AOP (Aspect Oriented Programming) for testing only is purely my personal preference. Of course you can just as easily use AspectJ as part of your development process and include aspects in your app code.

Checking for Artifactory in a Jenkins Pipeline

One of my projects uses the Artifactory as the repository manager. Unfortunately when doing a Jenkins pipeline build, I sometimes forget to ensure the Artifactory server is up first and find the job has failed after running for a while.

I’ve added some script to my Jenkinsfile that will check for the Artifactory server early on and fail fast if it is not running.

Artifactory Check

For my purposes I just try to ping the Artifactory server.
This can be done by sending a http request:
http://[Your Artifactory URL]/artifactory/api/system/ping
and if successful should return the string ‘OK’.

Jenkins Pipeline example

This particular example requires the HTTP Request plugin to be installed. I added a declarative stage in the pipeline before the actual build for the Artifactory check.

In this stage the HTTP request call from the plugin will be successful if the response status code is in the default range (100 to 399) and the response content includes the string ‘OK’. If the response does not fulfil these conditions, then the Jenkins job will fail quickly.

pipeline {
  agent any
  options {
    // Stop the build early in case of compile or test failures
    skipStagesAfterUnstable()
  }
  stages {
    stage('Build') {
      environment {
        // artifactory server url
        artifactoryUrl = 'http://[Your Artifactory URL]/artifactory'
     }

    // stage to check the Artifactory server is up, else will fail the job
    stage('Artifactory check') {
      steps {
        script {
          echo 'Pinging Artifactory'

          // for a successful ping, the response status code must be in default acceptable range (100:399)
          // and contain 'OK' in the content
          def pingResponse = httpRequest url: "${artifactoryUrl}/api/system/ping", validResponseContent: 'OK'

          echo "Ping response status code: ${pingResponse.status}"
          echo "Ping response: ${pingResponse.content}"
        }
      }
    }

    // continue with other stages for the job

Alternatively if you don’t want the Artifactory check to fail the job, just change the parameters to the HTTP request to allow all response status codes and any response content to pass. Then if the ping fails, you can just set a flag, send a notification, print some info, etc, instead and let the job continue.

stage('Artifactory check') {
  steps {
    script {

      // Allow all response codes returned from the Artifactory ping request so it doesn't fail,
      // normal allowable codes are 100:399.
      def pingResponse = httpRequest url: "${artifactoryUrl}${artifactoryPingPath}", validResponseCodes: '100:599'

      echo "Ping response status code: ${pingResponse.status}"
      echo "Ping response: ${pingResponse.content}"

      if (pingResponse.status == 200 && pingResponse.content == 'OK')
        // flag successful check
      else
        // flag ping failure
    }
  }
}

Espresso Idling Resource for RecyclerView Data Changes

I was having a problem with using Android Espresso to test a RecyclerView when it’s data was updated.

This is for an Android app where a list of contacts is displayed by a RecyclerView. There is a SearchView in the action bar that can filter the contacts list to display matching contact names.

The Espresso test ran like this:

  • Start the activity.
  • Espresso verifies that the full list of contacts is displayed in the RecyclerView. This works fine.
  • A query string is entered in a SearchView, and the filtering of the data in the RecyclerView is initiated (I’m using a SearchView to get the query string, but other controls such as EditText, etc, could be used instead).
  • Espresso verifies that the list of contacts has changed to only display matching items. Fails.
@RunWith(AndroidJUnit4.class)
public class RecyclerViewIdlingResourceTest {

@Rule
public ActivityRule&amp;lt;MainActivity&amp;gt; activityRule = new ActivityRule&amp;lt;MainActivity&amp;gt;(MainActivity.class);

// number of items in the original list
int allItemsCount = ...;

// number of items after the list has been filtered
int filteredItemsCount = ...;

@Test
public void testRecyclerviewFilter()
{
  // verify all test items loaded
  // SUCCESS
  onView(withId(R.id.recyclerview)).check(withItemCount(allItemsCount));

  // since the search view is initially collapsed, open it first before     tests are run
  onView(withId(R.id.action_search)).perform(click());

  // enter some text into the search view, and then press the action button.
  String searchText = "test"
  onView(withId(android.support.design.R.id.search_src_text)).perform(typeText(searchText), pressImeActionButton());

  // verify the number of items in the recyclerview list has been altered
  // FAIL!
  onView(withId(R.id.recyclerview)).check(withItemCount(filteredItemsCount));
}
}

Unfortunately it seems like that the Espresso assert to verify that the list of items has changed happens before the RecyclerView has finished reloading the updated data and redrawing itself. So the test fails when it finds that the RecyclerView still has the original number of items because it has not yet redrawn itself with the new list of data.

The code for this post is in this gist. It is in the form of incomplete code that only includes stuff relevant to the post. Also there are various ways of implementing and filtering a RecyclerView, so I will leave that part to the reader.

So, What’s the Problem?

After I have changed the data for the RecyclerView, I am calling notifyDataSetChanged() on the adapter.

Based on this StackOverflow question, the issue seems to be that when notifyDataSetChanged() is called, it only invalidates the data in the RecyclerView, but doesn’t update the widget immediately. Hence I suspected that the Espresso assertion was happening before the RecyclerView had updated.

To test this, I introduced a pause before the Espresso assertion to allow time for the RecyclerView to update, and the test passed.

@Test
public void testRecyclerviewFilterWithPause()
{
  // verify all test items loaded
  // SUCCESS
  onView(withId(R.id.recyclerview)).check(withItemCount(allItemsCount));

  // since the search view is initially collapsed, open it first before tests are run
  onView(withId(R.id.action_search)).perform(click());

  // enter some text into the search view, and then press the action button.
  String searchText = "test"
  onView(withId(android.support.design.R.id.search_src_text)).perform(typeText(searchText), pressImeActionButton());

  // pause for arbitrary period of time, InterruptedException handling left out to simplify example
  Thread.sleep(1000);

  // verify the number of items in the recyclerview list has been altered
  // SUCCESS - assuming the pause time was long enough
  onView(withId(R.id.recyclerview)).check(withItemCount(filteredItemsCount));
}

Here I was using Thread.sleep() but any Android equivalent with Handlers, etc, would have done too. Of course this is all a bit of a hack. The recommended way to introduce a wait for some process to complete before Espresso continues to test is to use Idling Resources.

Using a pause for some arbitrary time period is less than ideal, as it often leads to either flaky tests or making the tests run longer than necessary.

The RecyclerView Callback

For an Idling Resource to work, it needs to know when the RecyclerView RecyclerView is redrawing with the new list data.

There are various callbacks that the RecyclerView (and its support classes) have that can signal that the RecyclerView is in the process of redrawing. After searching on StackOverflow, I found these possibilities:

I decided to use onGlobalLayoutListener, but there seem to be multiple ways for the RecyclerView to signal that it being redrawn.

The Idling Resource is listening in …

Firstly we need some interfaces to use as callbacks to communicate between the RecyclerView, the activity/fragment containing the RecyclerView and Idling Resource.

Firstly here is an interface for the RecyclerView to notify the activity when the redrawing process with new data has occurred.


public interface RecyclerViewIdlingCallback {

public void setRecyclerViewLayoutCompleteListener(RecyclerViewLayoutCompleteListener listener);

public void removeRecyclerViewLayoutCompleteListener(RecyclerViewLayoutCompleteListener listener);

// Callback for the idling resource to check if the resource (in this example the activity containing the recyclerview)
// is idle
public boolean isRecyclerViewLayoutCompleted();
}

Then another interface to use as a callback for the activity to notify the Idling Resource to .. idle.


public interface RecyclerViewLayoutCompleteListener {

// Callback to notify the idling resource that it can transition to the idle state
public void onLayoutCompleted();
}

Here is an example activity, showing just the relevant code to work with the idling resource.

public class RecyclerViewCallbackContactsActivity extends AppCompatActivity implements
  SearchView.OnQueryTextListener,
  ViewTreeObserver.OnGlobalLayoutListener,
  RecyclerViewIdlingCallback {

  /**
   * Flag to indicate if the layout for the recyclerview has complete. This should only be used
   * when the data in the recyclerview has been changed after the initial loading.
   */
  private boolean recyclerViewLayoutCompleted;

  /**
   * Listener to be set by the idling resource, so that it can be notified when recyclerview
   * layout has been done.
   */
  private RecyclerViewLayoutCompleteListener listener;

  @Override
  public void onCreate (Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // CODE HERE to initialize the recyclerview

    recyclerViewLayoutCompleted = true;
    recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(this);
  }

  @Override
  public boolean onQueryTextSubmit(String query) {

    // CODE HERE to filter the recyclerview using the query string,
    // - this should eventually result in notifyDataSetChanged() being called on the adapter
        
    // flag that a new layout will be required with the filtered data
    recyclerViewLayoutCompleted = false;
  }

  @Override
  public void onGlobalLayout() {
    if (listener != null)
    {
      // set flag to let the idling resource know that processing has completed and is now idle
      recyclerViewLayoutCompleted = true;

      // notify the listener (should be in the idling resource)
      listener.onLayoutCompleted();
    }
  }

  @Override
  public boolean isRecyclerViewLayoutCompleted() {
    return recyclerViewLayoutCompleted;
  }

  @Override
  public void setRecyclerViewLayoutCompleteListener(RecyclerViewLayoutCompleteListener listener) {
    this.listener = listener;
  }

  @Override
  public void removeRecyclerViewLayoutCompleteListener(RecyclerViewLayoutCompleteListener listener) {
    if (this.listener != null && this.listener == listener)
    {
      this.listener = null;
    }
  }
}

The important parts in the activity are:

  • the listener method, onGlobalLayout(), which signals the recyclerview has inflated it’s layout for the redraw
  • the Boolean flag, recyclerViewLayoutCompleted, which is used by the idling resource to check if the Espresso test can continue to run after the recyclerview redraw.

This is the idling resource to be used to test the recyclerview in the activity.

public class RecyclerViewLayoutCompleteIdlingResource implements IdlingResource {

  private ResourceCallback resourceCallback;
  private RecyclerViewIdlingCallback recyclerViewIdlingCallback;
  private RecyclerViewLayoutCompleteListener listener;

  public RecyclerViewLayoutCompleteIdlingResource(RecyclerViewIdlingCallback recyclerViewIdlingCallback){
    this.recyclerViewIdlingCallback = recyclerViewIdlingCallback;

    listener = new RecyclerViewLayoutCompleteListener() {

      @Override
      public void onLayoutCompleted() {
        if (resourceCallback == null){
          return ;
        }
        if (listener != null) {
          recyclerViewIdlingCallback.removeRecyclerViewLayoutCompleteListener(listener);
        }
        //Called when the resource goes from busy to idle.
        resourceCallback.onTransitionToIdle();
      }
    };

    // add the listener to the view containing the recyclerview
    recyclerViewIdlingCallback.setRecyclerViewLayoutCompleteListener (listener);
  }
  @Override
  public String getName() {
    return "RecyclerViewLayoutCompleteIdlingResource";
  }

  @Override
  public boolean isIdleNow() {
    return recyclerViewIdlingCallback.isRecyclerViewLayoutCompleted();
  }

  @Override
  public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
    this.resourceCallback = resourceCallback;
  }
}

The activity is passed to the idling resource in it’s constructor as an RecyclerViewIdlingCallback implementation. Then when the recyclerview in the activity is ready, the activity will invoke the callback in the idling resource to indicate that it is ‘idle’.

Finally we can put this together in an Espresso test.

@Test
public void testFilterRecyclerViewUsingSearchView()
{
  // CODE HERE use espresso to use the SearchView to filter the recyclerview

  RecyclerViewLayoutCompleteIdlingResource idlingResource = new RecyclerViewLayoutCompleteIdlingResource((RecyclerViewCallbackContactsActivity) activityTestRule.getActivity());
  IdlingRegistry.getInstance().register(idlingResource);

  // CODE HERE to verify the recyclerview with the updated data

  IdlingRegistry.getInstance().unregister(idlingResource);
}

Caveat
I actually started writing this post a while ago, so the code was for the Recyclerview from the Android support library rather than from the Androidx library.

Restart Android Activity with ActivityScenario

I was writing an instrumentation test which required restarting the activity during a test. As I was trying out the ActivityScenarioRule to replace an ActivityTestRule, the documentation says I can use this method on the ActivityScenario to restart the activity after it has been launched:

scenario.recreate()

So I wrote this function to for restarting the activity:

    @Rule
    @JvmField var rule = ActivityScenarioRule(MyActivity::class.java)
	
    fun restartActivity() {
        var scenario = rule.getScenario()
        scenario.recreate()
    }

However I was getting an error message for androidx.test.core.app.InstrumentationActivityInvoker.

In source code for InstrumentationActivityInvoker, looking at the method recreateActivity(), the comments section explains why there may be some indeterminate behaviour depending  on the state of the activity and the version of Android being run on.

Recreates the Activity by {@link Activity#recreate}.

Note that {@link Activity#recreate}’s behavior differs by Android framework version. For example, the version P brings Activity’s lifecycle state to the original state after the re-creation. A stopped Activity goes to stopped state after the re-creation in concrete.
Whereas the version O ignores {@link Activity#recreate} method call when the activity is in stopped state. The version N re-creates stopped Activity but brings back to paused state instead of stopped.

In short, make sure to set Activity’s state to resumed before calling this method otherwise the behavior is the framework version dependent.

So for my particular test, the recreate() method wasn’t working (although it might have worked for someone else for a different test).

A simple change to my function fixed this problem:

    fun restartActivity() {
        var scenario = rule.getScenario()
        scenario.moveToState(Lifecycle.State.RESUMED)
        scenario.recreate()
    }

Of course it seems that ActivityScenario and ActivityScenarioRule are still a bit of a work in progress for now, so hopefully the documentation will catch up some time (or they may change the code again in future versions).

Update Your Fake AndroidInjector

Using Dagger 2 for dependency injection means that you can inject fake / mock objects for testing. I had been using a fake AndroidInjector to do this in my Espresso tests for an old Android app I was updating, but found that on updating Dagger to a more recent version (from 2.14 to 2.21) the custom AndroidInjector no longer compiled.

The fake AndroidInjector I was using was based on these blogs that were written a couple of years ago:

Another version of that idea can be found here:

This is a quick post on how this can be fixed for anyone who is still using the code from these articles (also a  belated thank- you to these authors for coming up with these ideas).

It Does Not Compile …

The problem came with these internal changes to the Dagger code in version 2.19.

https://github.com/google/dagger/releases/tag/dagger-2.19

Simple Fixes

Based on the code from the articles mentioned above, just need some simple changes (that unfortunately took me a while to work out!).
1. Replace the deprecated annotation

@ActivityKey

with

@ClassKey

2. The generic typing for AndroidInjector.Factory and other classes used in the AndroidInjector code have changed from

<? extends Activity> // java
<out Activity> // kotlin

to

<?> // java
<*> // kotlin

3. The DispatchingAndroidInjector  has changed it’s  constructor signature. Instead of having a single parameter of a Map of the Provider  of the AndroidInjector.Factory keyed by class, there is now an additional parameter of a Map using a String (class name) as the key

So in the Dagger generated code (and in the fake AndroidInjector), the method DispatchingAndroidInjector_Factory.newDispatchingAndroidInjector() also needs this extra parameter (even if just an empty Map).

// java (pseudo-code)

Map<Class<?>, Provider<AndroidInjector.Factory<?>>> classMap = new HashMap<>(1);
// create a custom AndroidInjector.Factory and add it to the provider
Provider<AndroidInjector.Factory<?>> provider = ...
map.put(MyActivity.class, provider);

// empty map to satisfy method signature for newDispatchingAndroidInjector()
Map<String, Provider<AndroidInjector.Factory<?>>> stringMap = new HashMap<>();

return DispatchingAndroidInjector_Factory.newDispatchingAndroidInjector(classMap, stringMap);
// kotlin

val classMap : Map<Class<*>, Provider<AndroidInjector.Factory<*>>> = mapOf(
Pair<Class<*>, Provider<AndroidInjector.Factory<*>>>(T::class.java, Provider { factory }))

// empty map to satisfy method signature for newDispatchingAndroidInjector()
val stringMap : Map<String, Provider<AndroidInjector.Factory<*>>> = emptyMap<String, Provider<AndroidInjector.Factory<*>>>()

return DispatchingAndroidInjector_Factory.newDispatchingAndroidInjector<Activity>(classMap, stringMap)

Why use a fake AndroidInjector?

The more common way to have Dagger inject test dependencies is to maintain a parallel universe of test components and test modules to provide the fake dependencies. This works fine and has the advantages that it shouldn’t break due to internal changes in the Dagger code. However it also means more boilerplate code to maintain.

 

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.

 

RxPreferences and Dagger

I have been rewriting a Settings (Preferences) activity for an old Android app. Since I was using RxJava in the app, I decided to try out RxPreferences which allows you to use SharedPreferences with a reactive wrapper.

I found it quite good to use, although the blog post that accompanies it is a bit out of date. While I did’t use RxBinding with it, it still had these advantages:

Inject RxPreferences with Dagger

Getting the preferences to where they are needed can be done with Dependency Injection with libraries such as Dagger 2. For instance, this is an example of a Dagger module that provides application scope dependencies with RxSharedPreferences setup:

@Module
class AppModule {

  // the application context required to get the shared preferences
  @Singleton
  @Provides
  Context provideContext(Application application) {
    return application.getApplicationContext();
  }

  @Provides
  @Singleton
  SharedPreferences provideSharedPreferences(Context context) {
    return PreferenceManager.getDefaultSharedPreferences(context);
  }

  @Provides
  @Singleton
  RxSharedPreferences provideRxSharedPreferences(SharedPreferences sharedPreferences) {
    return RxSharedPreferences.create(sharedPreferences);
  }
}

Then inject RxSharedPreferences into the classes that will use it.

public class MyActivity extends AppCompatActivity {

  // RxSharedPreferences injected into a field for an activity
  @Inject
  RxSharedPreferences rxSharedPreferences;

  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    // I'm using the Dagger Android library to inject dependencies
    AndroidInjection.inject(this);

    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_activity);
  }

  private void someMethod()
  {
    <Preference<String> myPreference =   rxSharedPreferences.getString("pref_key");
    String stringFromPreference = myPreference.get();

    // do something with the string obtained from the preference
  }
}

Inject Preferences directly

Alternatively you can inject the Preference(s) rather than RxSharedPreferences, by setting up the Preference in the Dagger module.

class AppModule {

  @Singleton
  @Provides
  Context provideContext(Application application) {
    return application.getApplicationContext();
  }

  @Provides
  @Singleton
  SharedPreferences provideSharedPreferences(Context context) {
    return PreferenceManager.getDefaultSharedPreferences(context);
  }

  @Provides
  @Singleton
  RxSharedPreferences provideRxSharedPreferences(SharedPreferences sharedPreferences) {
    return RxSharedPreferences.create(sharedPreferences);
  }

  // Preference configured with hard-coded preference key
  @Provides
  @Named("myPreference")
  @Singleton
  Preference<String> provideMyPreference(RxSharedPreferences rxPreferences) {
    return rxPreferences.getString("my_preference_key");
  }

  // Preference configured with preference key retrieved from string resource
  @Provides
  @Named("myPreferenceUsingResource")
  @Singleton
  Preference<String> provideMyPreferenceUsingResource(Context context, RxSharedPreferences rxPreferences) {
    return rxPreferences.getString(context.getString(R.string.pref_my_preference_key));
  }
}

Notice that, as an example, I have configured 2 Preference’s in the module, one with the preference key hard-coded and the other with the preference key retrieved from string resources.

Also I have added the @Named anotation, which in this case is to distinguish the two Preferences since they are both of the type String. Even if I didn’t have multiple Preferences of the same type, I think it is a good idea to qualify them for better documentation (see the Dagger documentation to learn about qualifiers.

Then inject the Preference(s) instead of RxSharedPreferences.

public class MyActivity extends AppCompatActivity {

  @Inject
  @Named("myPreference")
  Preference<String> myPreference;

  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    AndroidInjection.inject(this);

    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_activity);
  }

  private void someMethod()
  {
    String stringFromPreference = myPreference.get();

    // do something with the string obtained from the preference
  }
}

Then we can also use the injected Preference to observe changes to the it.

public class MyActivity extends AppCompatActivity {

  @Inject
  @Named("myPreference")
  Preference<String> myPreference;

  private Disposable disposable;
 
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    AndroidInjection.inject(this);

    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_activity);
 
    disposable = myPreference.asObservable()
      .subscribe(pref -> doSomething(pref));
  }
}

Custom Preference Types

In the blog for the RxPreferences library, it demonstrates getting a Preference for a custom type by creating an Adapter class for it. In the current version, this has been replaced with a class that implements the Preference.Converter interface instead.

Here is a simple example that converts a preference stored as a String into a Preference<Boolean>. Firstly create the Converter class:

public final class BooleanStringConverter implements Preference.Converter<Boolean> {

  @NonNull
  @Override
  public Boolean deserialize(@NonNull String serialized) {
    return Boolean.parseBoolean(serialized);
  }

  @NonNull
  @Override
  public String serialize(@NonNull Boolean value) {
    return String.valueOf(value);
  }
}

Then to get the Preference, use getObject() with the Converter class as a parameter. In this example I have added it to the Dagger module.

@Provides
@Singleton
BooleanStringConverter provideBooleanStringConverter() {
  return new BooleanStringConverter();
}

@Provides
@Named("myBooleanPreference")
@Singleton
Preference<Boolean> provideMyBooleanPreference(RxSharedPreferences rxPreferences, BooleanStringConverter booleanStringConverter) {
  return rxPreferences.getObject("my_preference_key", FALSE, booleanStringConverter);
}

Using the Preference with the Converter, as well as Dagger, meant I didn’t have type conversion code scattered throughout the app.