Contents


Retrofit2, Apollo and Ktor

Retrofit, Apollo and Ktor all use OkHttp under the hood (for Ktor it’s optional) and this enables fore to handle their networking calls in a very similar way: by wrapping them with a CallWrapper class (CallWrapperRetrofit2 | CallWrapperApollo3 | CallWrapperKtorX). For usage examples, please see the appropriate example apps in the repo.

The CallWrapper allows us to abstract all the networking related work so that the models can just deal with either successful data or domain error messages depending on the result of the network call (the models don’t need to know anything about HTTP codes or io exceptions etc).

The Java and Kotlin implementations have slightly different APIs, while the Java implementation takes advantage of lambda expressions, the Kotlin implementation uses suspend functions and returns an Either.


//Retrofit2 example

callProcessor.processCall(service.getFruits("3s"), workMode,
    successResponse -> handleSuccess(successResponse),
    failureMessage -> handleFailure(failureMessage)
);
 

launchMain(workMode) {

    val result = callWrapper.processCallAwait {
        service.getFruits("3s")
    }

    when (result) {
        is Fail -> handleFailure(failureWithPayload, result.value)
        is Success -> handleSuccess(success, result.value)
    }
}
 

The API is very slightly different depending on whether we are wrapping Retrofit2 calls, Apollo calls or Ktor calls, please refer to the sample apps for details and test strategies.

In all cases though, using the CallWrapper ensures clear separation of concerns (between data/api layer code and feature/domain layer code), testability (via the ability to mock callWrapper responses) and error handling (the callWrapper API requires that an error handler is supplied like this one for example - so no more catching IOExceptions or handling HTTP 401s in view layer code).

carryOn

carryOn is an extension function available on the fore Either that lets you chain network calls together using the CallWrapper to ensure all networking errors are being handled. It looks like this:

val response = createUserUseCase()
  .carryOn { user ->
    createUserTicketUseCase(user.userId)
  }.carryOn { ticket ->
    ticketRef = ticket.ticketRef
    getEstimatedWaitingTimeUseCase(it.ticketRef)
  }.carryOn { minutesWait ->
    if (minutesWait > 10) {
        cancelTicketUseCase(ticketRef)
    } else {
        confirmTicketUseCase(ticketRef)
    }
  }.carryOn {
    claimFreeGiftUseCase(ticketRef)
  }

when (response) {
    is Fail -> handleFailure(response.value)
    is Success -> handleSuccess(response.value)
}

If all those useCases return fore Eithers, we basically have:

You can see this technique being used to create a weather report by calling separate wind speed, pollen level, and temperature services in this clean modules sample app

Custom APIs

All APIs will be slightly different regarding what global headers they require, what HTTP response codes they return and under what circumstances and how these codes map to domain model states. There will be a certain amount of customisation required, see the sample apps in the repo for an example of this customization.

The sample apps all use JSON over HTTP, but there is no reason you can’t use something like protobuf, for example.

Testing Networking Code

Another advantage of using the CallWrapper is that your network code can easily be mocked out during tests. The networking sample apps in the fore repo take two alternative approaches to testing:

As with testing any asynchronous code with fore, the calls are processed synchronously when using the TestDelegateDefault which simplifies our test code considerably (no need for latches, or magic).

Adapter animations [non-compose only]

For some robust and testable implementations, please see the Adapter Example Apps

Firstly, if you don’t care about adapter animations, just call notifyDataSetChanged() from inside the syncView() function:


public void syncView() {

  // set enabled states and visibilities etc
  ...

  adapter.notifyDataSetChanged();
}
 

fun syncView() {

  // set enabled states and visibilities etc
  ...

  adapter.notifyDataSetChanged()
}
 

In this way you let your adapters piggy back on the observer which you have already setup for your view (it’s the observer that calls syncView() whenever the model changes). Your adapter needs to be setup as in the examples, i.e. most of the logic will be moved to the model, and the adapter will take the model as a construction parameter, and that is where the adapter will get its list data from (the model is the source of truth for the list data, not the adapter) - see the sample app linked to above.

Animations

Adapter animations are probably one of the most complicated parts of android to get completely right as subtle timing and threading differences will make them break in edge case situations. But getting adapter animations to work “99% of the time” is not too difficult (especially if your list changes are small and infrequent).

To be totally clear: if your list is being updated infrequently (e.g. based on the result of a single network connection), and the list data is small so that running DiffUtil on the UI thread is an option, and if the UI prevents a user from rapidly smashing buttons to change the list items (e.g. swipe-to-delete makes it infeasible that a user will be able to delete enough items, quickly enough to cause issues). Then you probably won’t need the fore adapter classes, just add a DiffUtil.Callback in your adapter, run it whenever you receive a new list and it should work fine.

But if you’re chasing 100% robustness and you’d rather not depend on luck and timing for your app’s performance (because your app has a lot of users for instance, or you’re dealing with multiple rapid changes to your data) it can require some very carefully written code.

No crash list implementations

As most android developers know, in order to get animations you need to tell the adapter what kind of change actually happened i.e. what rows were added or changed etc. There are two ways to do this on android:

Android also provides us with AsyncListDiffer / ListAdapter (which also use DiffUtil under the hood). Depending on your situtation you might find these useful (one disadvantage is that it moves management of your list out of a model class and into an adapter, short-cutting some of the benefits of MVO). You’ll find example code for this in the kotlin sample for comparison anyway, please see the guidance in the view source code: view, adapter, (there is no model).

Once things have been setup correctly with fore, the only thing you’ll need to do in the view layer is call notifyDataSetChangedAuto() instead of notifyDataSetChanged() from the syncView() function.

“just call notifyDataSetChangedAuto() instead of notifyDataSetChanged() from the syncView() function”

Why do adapters crash in the first place?

More specifics regarding adapters and threading are in the source of ObservableImp where it talks about the notificationMode. One of the subtle gotchas with android adapters is that when you update list data that is driving an adapter, the actual change to the list must be done on the UI thread and the adapter must be told straight after (or at least before the thread you are on, yields). Call it at the end of the method you are in, for example.

“the change to the list data MUST be done on the UI thread AND the adapter MUST be told before the current thread yields”

The “fruit fetcher” screen of the full app example demonstrates that quite well, it’s deliberately challenging to implement in a regular fashion (multiple simultaneous network calls changing the same list; user removal of list items; and screen rotation - all at any time) it’s still totally robust as a result of sticking to that rule above.

This is because android will call Adapter.count() then Adapter.get() on the UI thread and you must NOT change the adapter’s size between these calls. If after android calls Adapter.count(), you change the list but don’t immediately let the adapter know that its count() call is out of date (by calling the notify… methods for example), when android next calls Adapter.get() you will have problems. Synchronizing any list updates is not enough. Even posting the notify… call to the end of the UI thread is not enough, it needs to be done immediately (before the UI thread yields) because once the UI thread yields it may let android in to call Adapter.get().

Occasionally you may encounter people who believe that the key to robust adapter implementations is to have the adapter driven by an immutable list - I don’t know where this advice comes from but it’s nonsense unfortunately. When the list data changes (regardless if you are changing one immutable list for another immutable list, or driving the whole thing with a single mutable list), the adapter needs to be notified immediately, and both things need to happen on the UI thread, that’s it. It’s a shame the android docs do such a terrible job of explaining this.

Database driven RecyclerView Animations

The fore 6 db example (which uses classes from fore’s immutable package) shows all the code needed for this, and also how to trigger view updates from a Room database using its InvalidationTracker (which is analogous to how fore’s observers work)

Default parameters for WorkMode, Logger and SystemTimeWrapper

A lot of fore classes take parameters for WorkMode, Logger and SystemTimeWrapper in their constructor. That’s done to make it very clear what needs to be swapped out when you want to inject different dehaviour (e.g. pass in a mock SystemTimeWrapper rather than a real one, when you want to test various time based behaviour). It’s simple and clear but potentially annoying to see these parameters crop up all the time.

From 1.2.0 the kotlin APIs will set default values for these parameters if you don’t specify them. If you chose to do that, you’ll probably want to use ForeDelegateHolder.setDelegate() to setup your tests with. You will usually want TestDelegateDefault() for that, but you can create your own if you have some specific mocking requirements.

By default, a SilentLogger will be used so if you do nothing, your release build will have nothing logged by fore. During development you may wish to turn on fore logs by calling: ForeDelegateHolder.setDelegate(DebugDelegateDefault("mytagprefix_")) Setting TestDelegateDefault() will redirect any android logs to println() so you can easily see them during unit tests

All the defaults used are specified here and here.

Kotlin Coroutines

We don’t really want to be putting asynchronous code in the View layer unless we’re very careful about it. So in this section we are mostly talking about Model code which often needs to do asynchronous operations, and also needs to be easily testable.

fore offers some extension functions that enable you to use coroutines in a way that makes them easily testable in common usage scenarios.

As mentioned above, passing TestDelegateDefault() to the Fore.setDelegate() function during setup will ensure your coroutine code runs synchronously during tests

AsyncTasks with Lambdas [for Java clients]

For legacy Java based clients, fore has some wrappers that make AsyncTask much easier to use and to test


new AsyncBuilder<Void, Integer>(workMode)
    .doInBackground(MyModel.this.doStuffInBackground())
    .onPostExecute(result -> MyModel.this.doThingsWithTheResult(result))
    .execute((Void) null);
 

AsyncBuilder<Unit, Int>(workMode)
    .doInBackground { this@MyModel.doStuffInBackground() }
    .onPostExecute { result -> this@MyModel.doThingsWithTheResult(result) }
    .execute()
 

Android’s AsyncTask suffers from a few problems - the main one being that it can’t be tested and is difficult to mock because it needs to be instanciated each time it’s used.

The quickest fore solution to all that is to use AsyncBuilder

Asynchronous Example App Source Code is the simplest way to see this all in action by the way.

AsyncBuilder

This class uses the builder pattern and has a cut down API to take advantage of lambda expressions. For reference here’s the source code

One restriction with AsyncBuilder is there is no way to publish progress as you can with android’s AsyncTask. If you want to use that feature during your asynchronous operation, see the Async class below.


new AsyncBuilder<String, Integer>(workMode)
    .doInBackground(input -> MyModel.this.doStuffInBackground(input))
    .onPostExecute(result -> MyModel.this.doThingsWithTheResult(result))
    .execute("input string");
 

AsyncBuilder<String, Int>(workMode)
    .doInBackground { input -> this@MyModel.doLongRunningStuff(input) }
    .onPostExecute { result -> this@MyModel.doThingsWithTheResult(result) }
    .execute("input string")
 

AsyncBuilder (and Async) use a AsyncTask.THREAD_POOL_EXECUTOR in all versions of Android.

WorkMode Parameter

AsyncBuilder takes a constructor argument: WorkMode (in the same way that fore Observable does). The WorkMode parameter tells AsyncBuilder to operate in one of two modes (Asynchronous or Synchronous).

Passing WorkMode.ASYNCHRONOUS in the constructor makes the AsyncBuilder operate with the same behaviour as a normal AsyncTask.

Passing WorkMode.SYNCHRONOUS here on the other hand makes the whole AsyncBuilder run in one thread, blocking until it’s complete. This makes testing it very easy as you remove the need to use any CountdownLatches or similar.

Async

Async (which is basically a wrapper over AsyncTask that makes it testable) looks and behaves very similarly to android’s AsyncTask and is an (almost) drop in replacement for it.

You can take a quick look at the source code for Async to get an idea of what it’s doing, don’t worry it’s <100 lines.

Here’s how you use Async:


new Async<Void, Integer, Integer>(workMode) {

    @Override
    protected Integer doInBackground(Void... voids) {

        //do some stuff in the background
        ...

        //publish progress if you want
        publishProgressTask(progress);

        //return results
        return result;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        progress = values[0];
        //do something with the progress
        ...
    }

    @Override
    protected void onPostExecute(Integer result) {
        //do something with the results once back on the UI thread
        ...
    }

}.executeTask((Void) null);
 

object : Async<Unit, Int, Int>(workMode) {
    override fun doInBackground(vararg empty: Unit?): Int {

      //do some stuff in the background
      ...

      //publish progress if you want
      publishProgressTask(progress);

      //return results
      return result;
    }

    override fun onProgressUpdate(vararg values: Int?) {

      progress = values[0]!!;

      //do something with the progress
      ...
    }

    override fun onPostExecute(result: Int?) {
      //do something with the results once back on the UI thread
      ...
    }

}.executeTask()
 

ExecuteTask

One difference with Async is that to run it, you need to call executeTask() instead of execute(). (AsyncTask.execute() is marked final).

Testing

For both Async and AsyncBuilder testing is done in the same way that it is done for the fore coroutine extensions, i.e. by passing WorkMode.SYNCHRONOUS in via the constructor.

The easiest way to do that is to set a delegate like this:

Fore.setDelegate(TestDelegateDefault())

Check the sample apps that come with the fore repo for complete test examples