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 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.

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.

Gradle Dependencies for Java, use compile or implementation?

While I was explaining to a colleague about using Gradle for Java projects (he was moving away from Maven), we came across various code samples. Some of the examples were using the compile configuration for dependencies, while others were using implements and api.


dependencies {
compile 'commons-httpclient:commons-httpclient:3.1'
compile 'org.apache.commons:commons-lang3:3.5'
}


dependencies {
api 'commons-httpclient:commons-httpclient:3.1'
implementation 'org.apache.commons:commons-lang3:3.5'
}

This post was a summary based on the documentation and StackOverflow questions to explain to him which configurations to use.

New Dependency Configurations

Gradle 3.4 introduced the Java-library plugin, which included the then new configurations implementation and api (amongst others). These were meant to replace the compile configuration which was deprecated for this plugin. The idea was that the new configurations would help to prevent leaking of transitive dependencies for multi-module projects.

Please note that in this post I am just using the compile vs implementation/api configurations as an example. Other new replacement configurations were introduced as well, please read the documentation for further information.

Java

For a Java project using Gradle 3.4+, then it depends on whether you are build an application or a library.

For a library project or a library module in a multiple module project, it is recommended to use the Java-library plugin, so in build.gradle use this

apply plugin: 'java-library'

instead of

apply plugin: 'java'

Then you would use either implementation or api, depending on whether you want to expose the dependency to consumers of the library.

For a plain application project, you can stick with the java plugin and continue to use the compile configuration. Having said that, I have tried using the Java-library plugin for an app project and it seems to work fine.

Android

For an Android project, the new configurations came with the Android Gradle Plugin 3.0. So unless you are still using the 2.x version of Android Studio / Android Gradle plugin, the use of compile is deprecated. So you should use implementation, even for an app.

In fact, when I recently upgraded my Android Studio, it came up with the message:

Configuration ‘compile’ is obsolete and has been replaced with ‘implementation’.
It will be removed at the end of 2018

I think this also applies if you use Kotlin instead of Java.

Groovy

How about a project with Groovy as well as Java? This can be for a mixed Groovy / Java project, or for a Java project which needs Groovy for some support tools (such as Spock or Logback configuration).

In the past I have used the Groovy plugin instead of the Java plugin for mixed projects. The Groovy plugin extends the Java plugin and will handle the compilation for Java sources as well as Groovy sources.

apply plugin: 'groovy'

You can continue to do this for Java application modules, but the documentation states that the Groovy plugin has compatibility issues with the Java-library plugin so will need a work around for library modules.

Of course this short post is for newbies, and has only scratched the surface in terms of learning about all the new dependency configurations.

Mocking Static Methods in Android: Let’s Wrap it up

When writing local unit tests in Android, one of the limitiations that you face is that the tests are run against a version of android.jar that does not have any code. As the documentation explains, any dependency on Android code must be mocked.

A quick example of a simple unit test:

public class ClassUnderTest {

public String methodUnderTest(String str)
{
    if (PhoneNumberUtils.isGlobalPhoneNumber(str))
    {
      return "yes";
    }
    else
    {
      return "no";
    }
  }
}

@RunWith(JUnit4.class)
public class TestThatFails {

private ClassUnderTest classUnderTest;

  @Before
  public void setup() {
    classUnderTest = new ClassUnderTest();
  }

  @Test
  public void testTheClass() {
    String result = classUnderTest.methodUnderTest("1234");
    assertEquals("yes", result);
  }
}

When this test is run, it will fail with the following error:

java.lang.RuntimeException: Method isGlobalPhoneNumber in android.telephony.PhoneNumberUtils not mocked. See http://g.co/androidstudio/not-mocked for details

The class we are testing has a dependency on the Android utility library PhoneNumberUtils. In order for the test to run successfully, this Android dependency must be mocked.

All the example code for this post is available at this gist.

Mockito: No to Static Methods

Google’s suggested way to mock Android dependencies is to use Mockito. This would generally be fine, however in our example this will not work because Mockito does not support mocking static methods.

This discussion shows that the Mockito contributors consider static methods to be an anti-pattern for various reasons, e.g.

  • The dependency on the static method becomes hard wired in the code.
  • This makes mocking and testing difficult.

Hence they do not support it as they don’t want to encourage poor design.

So what are some other way to make our test work?

  • If this was plain old Java instead of Android, I could have used PowerMockito to mock the static methods. However I have found using PowerMock to be problematic in Android.
  • If you only use a few static methods, you could just copy the code into your app assuming the the source was available. Of course this means more code to maintain, and is not sustainable if you use a lot of static methods.
  • You could wrap the static method call and internally delegate to the static method. The wrapper could then be mocked. This is the option we will be discussing.

Wrapper Classes

One solution is to create a wrapper class for the Android classes that have the static method, and add that wrapper as a dependency.

public class PhoneNumberUtilsWrapper {

  public boolean isGlobalPhoneNumber(String phoneNumber)
  {
    return PhoneNumberUtils.isGlobalPhoneNumber(phoneNumber);
  }
}

public class ClassUnderTestWithWrapper {

  private PhoneNumberUtilsWrapper wrapper;

  public ClassUnderTestWithWrapper(PhoneNumberUtilsWrapper wrapper) {
    this.wrapper = wrapper;
  }

  public String methodUnderTest(String str)
  {
    if (wrapper.isGlobalPhoneNumber(str))
    {
      return "yes";
    }
    else
    {
      return "no";
    }
  }
}

Here I have created a wrapper class for PhoneNumberUtils which is now a dependency of the class under test.

@RunWith(JUnit4.class)
public class TestWithWrapper {

  @Mock
  PhoneNumberUtilsWrapper wrapper;

  private ClassUnderTestWithWrapper classUnderTest;

  @Before
  public void setup() {
    MockitoAnnotations.initMocks(this);

    classUnderTest = new ClassUnderTestWithWrapper(wrapper);
  }

  @Test
  public void testTheClass() {
    when(wrapper.isGlobalPhoneNumber(anyString())).thenReturn(true);

    String result = classUnderTest.methodUnderTest("1234");
    assertEquals("yes", result);
  }
}

Because the wrapper class can be mocked, and the method call in the class under test is not static, the test can now pass.

One problem with this solution is when the class under test depends on static methods from many Android libraries. For instance, what happens if the class under test also needs to use TextUtils, DateUtils, etc. Suddenly you end up with lots more boilerplate code, more constructor parameters, etc.

Wrapper Methods

Another way is to wrap the static method call in a non-static method in the class under test.

public class ClassUnderTestWithWrappedMethod {

  public String methodUnderTest(String str)
  {
    if (isGlobalPhoneNumber(str))
    {
      return "yes";
    }
    else
    {
      return "no";
    }
  }

  // can't be private access
  boolean isGlobalPhoneNumber(String phoneNumber)
  {
    return PhoneNumberUtils.isGlobalPhoneNumber(phoneNumber);
  }
}

In order for this to work, in the test we have to use Mockito spy. Also note that the wrapped methods have to be accessible in the test, and therefore can’t be private.

@RunWith(JUnit4.class)
public class TestWithWrappedMethod {

  private ClassUnderTestWithWrappedMethod classUnderTest;

  private ClassUnderTestWithWrappedMethod classUnderTestSpy;

  @Before
  public void setup() {
    MockitoAnnotations.initMocks(this);

    classUnderTest = new ClassUnderTestWithWrappedMethod();
    classUnderTestSpy = Mockito.spy(classUnderTest);
  }

  @Test
  public void testTheClass() {
    doReturn(true).when(classUnderTestSpy)
    .isGlobalPhoneNumber(anyString());

    String result = classUnderTestSpy.methodUnderTest("1234");
    assertEquals("yes", result);
  }
}

Here we are running the test on the spy class, which will delegate method calls to the real class under test. However we can create stubs for the methods that wrap the static method calls to Android libraries.

As I have mentioned, one disadvantage is that the wrapped methods cannot be private, which is not ideal from an OO design point of view. But then you have to make similar compromises if you are using libraries such as Dagger or Butterknife.

Conclusion

Both of these wrapping solutions can work, but it’s probably a good idea to be consistent and stick with doing it one way if you can. Which method works better may depend on your app architecture, e.g. are you using dependency injection.

Static Methods: Good or Bad? Does it matter?

In this post I am not getting into the argument about whether static methods are good or bad design (although my personal opinion is that their use should be limited). There are plenty of arguments about this issue on the internet already.

However, in the Java world they are a fact of life.

Many utility classes in Java, Android and many popular libraries are not real OO classes, but collections of procedural functions. The functions are often written as static methods and grouped by functionality.

Whether you like static methods or not, we all have to learn to deal with them in a pragmatic way.