In this codelab you'll learn how to use Agera to eliminate lag and jank from your UIs and think in a reactive and functional way. You will also learn how to use lambda expressions and method references to keep your code compact and clean.

What you'll learn

What you'll need

How will you use this tutorial?

Read it through only Read it and complete the exercises

How would rate your experience with building Android apps?

Novice Intermediate Proficient

You can clone the GitHub repository from the command line using this command:

$ git clone https://github.com/googlecodelabs/android-agera

To run a specific step of the codelab, choose it in the run configurations selector.

Every step of this codelab has a "final" version that runs the solution. Likewise, there is a "final" class with the suggested solution in every step.

Frequently Asked Questions

Reactive programming is a paradigm related to how changes are propagated in a program. A good example is a spreadsheet: cells contain formulas that depend on other cells' values. When one of these values are changed, there's no need to manually update the resulting values; the changes are propagated for you.

Agera brings a reactive flavor to Android using an observer pattern.

The main class in Agera is the Repository. To understand it, let's go over the basics first.

The foundation of Agera is a set of very simple interfaces including: Observable, Updatable, Supplier and Receiver:

Observable.java

public interface Observable {

  void addUpdatable(Updatable updatable);

  void removeUpdatable(Updatable updatable);
}

Updatable.java

public interface Updatable {

  void update();
}

Supplier.java

public interface Supplier<T> {

  T get();
}

Receiver.java

public interface Receiver<T> {

  void accept(T value);
}

As you can see, these four interfaces are very simple. This is what they're responsible for:

Interface

Description

Observable

Something that can be observed for changes and stores a list of updatable objects. When a change occurs, the updatables are poked.

Updatable

Called when an event has occurred. Its update() method will be called when the class it observes changes, but can also be called manually to force an update.

Supplier

Something that supplies data when the get() method is called. In this case, "Data" can be anything.

Receiver

Something that can receive (and normally store) a value send to it via accept().

Let's create objects implementing these interfaces to get used to the terminology.

In this chapter, we'll create a simple implementation of the observer pattern to become familiar with Agera's interfaces. Skip to the next chapter if you already understand these concepts.

Many times the Observable is also a Supplier and a Receiver, since the thing that is observed is probably the data we're interested in and you need a way to receive it. Create a class that is an Observable, a Supplier and a Receiver:

Open step1/Step1Activity.java.

Create a inner class in the Step1Activity class that implements these three interfaces:

private static class MyDataSupplier implements Observable, Supplier<String>, Receiver<String> {

}

It will show an error, so let Android Studio tell us what we need to fill in:

Implement methods, create a list to store the Updatables and a way to set the data. It should look something like this:

private static class MyDataSupplier implements Observable, Supplier<String>, Receiver<String> {

   List<Updatable> mUpdatables = new ArrayList<>();

   private String mValue;

   @Override
   public void addUpdatable(@NonNull Updatable updatable) {
       mUpdatables.add(updatable);
   }

   @Override
   public void removeUpdatable(@NonNull Updatable updatable) {
       mUpdatables.remove(updatable);
   }

   @NonNull
   @Override
   public String get() {
       return mValue;
   }

   @Override
   public void accept(@NonNull String value) {
       mValue = value;
        // Notify the updatables that we have new data
       for(Updatable updatable : mUpdatables) {
           updatable.update();
       }
   }
}

Now, in Step1Activity.onCreate(), create an Updatable and an instance of our Observable-Supplier class.

// Create a Supplier-Observable-Receiver
MyDataSupplier myDataSupplier = new MyDataSupplier();

// Create an Updatable
Updatable updatable = new Updatable() {
    @Override
    public void update() {
        Log.d("AGERA", myDataSupplier.get());
    }
};

// Connect the dots:
myDataSupplier.addUpdatable(updatable);

Convert that "new Updatable()..." to a lambda expression.

 updatable = () -> Log.d("AGERA", myDataSupplier.get());

Feel free to run the activity, but you won't see a thing in the logcat. We're missing a line in onCreate():

myDataSupplier.accept("Hello Agera!");

To understand how this works, let's follow the dots:

  1. This last line sets the data in the Supplier
  2. The Supplier pokes the Updatable, because it has received a change
  3. The Updatable gets the data from the Supplier and prints the String in Logcat.

Seriously? All this effort for a hello world?

This is a naive implementation of the Observer Pattern, but most of this boilerplate is already in Agera. Let's remove some code in the next chapter.

Read more:

The most important concept in Agera is the Repository. Repositories receive, supply, and store data and emit updates. The interfaces Observable, Supplier and Receiver are combined into two types of repositories:

Interface

Observable

Supplier

Receiver

Repository

MutableRepository

Simple repositories

A simple repository can be created using one of the utility methods in the class Repositories.

In our code, you could replace

implements Observable, Supplier<String>, Receiver<String>

with:

implements MutableRepository<String>

but Agera provides a Repository factory, so remove the MyDataSupplier class altogether and replace

MyDataSupplier myDataSupplier = new MyDataSupplier();

with

MutableRepository<String> mStringRepo = Repositories.mutableRepository("Initial value");

mutableRepository() creates a repository similar to our previous implementation, but that is thread-safe and has a more sophisticated update dispatcher, so let's use that from now on.

Also, remove the updatables (with removeUpdatable()) when you know you're done with them. In our example this is not needed but it is a good practice: it avoids potential leaks and prevents updating destroyed views. The class is now much shorter:

public class Step1ActivityFinal extends AppCompatActivity {

    private MutableRepository<String> mStringRepo;
    private Updatable mLoggerUpdatable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.step1);

        // Create a MutableRepository
        mStringRepo = Repositories.mutableRepository("Initial value");

        // Create an Updatable
        mLoggerUpdatable = () -> Log.d("AGERA", mStringRepo.get());

    }

    @Override
    protected void onStart() {
        super.onStart();
        mStringRepo.addUpdatable(mLoggerUpdatable);

        // Change the repository's value
        mStringRepo.accept("Hello world.");
    }

    @Override
    protected void onStop() {
        mStringRepo.removeUpdatable(mLoggerUpdatable);
        super.onStop();
    }
}

However, there's still a lot of boilerplate. The next step will introduce complex repositories, which is where the power of Agera lies.

In summary:

Read more:

A complex repository in Agera can react to other repositories (or observables, in general) and produce values by transforming data obtained from other data sources, synchronously or asynchronously. The data supplier by this repository is kept up to date in reaction to the events from the sources it's observing.

The description provides a declaration of:

The declaration is defined compiling a repository. Example:

topAlbumsRepository = repositoryWithInitialValue(emptyList())
  .observe(accountManager,      // When the account changes...
           networkStatus)       // or when we get online...
  .onUpdatesPer(10000)          // but at most every 10 seconds...
  .goTo(networkExecutor)        // on the network thread...
  .getFrom(albumsFetcher)       // fetch albums from API...
  .thenTransform(                   
    functionFrom(String.class)
    .unpack(jsonUnpacker)       // unpack JSON items...
    .map(jsonToAlbum)           // for each album...
    .filter(fiveStarRating)     // filter the best...
    .thenLimit(5))              // and give us the first five of those!
  .compile();                   // Create the repository!

Operators

Data transformation is done with operators.

Operator

Description

getFrom(Supplier)

Ignores input and returns a value from a supplier

transform(Function)

Applies a function to an input and returns the result

mergeIn(Supplier, Merger)

A function that takes the input from the previous directive and the input from an external supplier to produce a result

sendTo(Receiver)

The input is not modified, but it's sent to an external receiver

bindWith(Supplier, Binder)

Similar to sendTo, but the input is sent to a binder that takes a second parameter from a supplier.

check(Predicate).or()

Evaluates a condition for "early exit"

The Result wrapper class

The functional interfaces Supplier, Function and Merger are defined not to throw any exceptions, but realistically, many operations may fail. To help capture the failures, Agera provides a wrapper class Result, which encapsulates the (either successful or failed) result of a fallible operation, which we call an attempt. The attempt can be implemented as a Supplier, Function or Merger that returns a Result.

The data processing flow provides failure-aware directives that allow terminating the flow in case of failure:

The Result wrapper class can also store absent (or present) values.

Enough theory. In the next chapter, you'll see this in action.

Read more:

In this step, you're going to create a simple UI that uses a complex repository.

It has a TextView and a Button. The Button increments the value shown by the text view.

Open step2/Step2Activity.java, and in onCreate():

1. Set an OnClickListener on the button that increments the valueRepository which is a MutableRepository<Integer> that will be the source of truth for the value.

mIncrementBt.setOnClickListener(view -> valueRepository.accept(valueRepository.get() + 1));

3. Create our first complex repository. It observes the valueRepository and transforms its value to a String.

textValueRepository = Repositories.repositoryWithInitialValue("N/A")
       .observe(valueRepository)
       .onUpdatesPerLoop()
       .getFrom(valueRepository)
       .thenTransform(input -> String.format("%d", input))
       .compile();

4. Create an Updatable that will react to a change in the textValueRepository and set the text view.

mTextValueUpdatable = new Updatable() {
   @Override
   public void update() {
       mValueTv.setText(textValueRepository.get());
   }
};

Now, let Android Studio convert that to a lambda expression:

mTextValueUpdatable = () -> mValueTv.setText(textValueRepository.get());

5. Add and remove updatables in onStart and onStop.

@Override
protected void onStart() {
   super.onStart();
   textValueRepository.addUpdatable(mTextValueUpdatable);
}
...

6. Optional: Handle rotation changes with onSaveInstanceState and onRestoreInstanceState.

Now the TextView should be updated on each click. Let's take it up a notch in the next chapter.

By now you should understand the difference between a simple repository and a complex repository and how to wire them to UI elements.

This peculiar calculator uses RadioButtons to choose an operation and SeekBars to set the two operands. This way we can generate multiple operations per second while sliding one or both bars at the same time quickly. (You'll see why in the next chapter.)

Run the Step 3 - Calculator configuration and play with it.

Error handling - division by zero

The valid range for the inputs go from 0 to 100 and there's a division operation, so there's a potential problem in this calculator. We'll show "DIV#0" when the divisor (second input) is zero.

A very convenient way to handle errors in Agera is to wrap repository values in Result. Its values belong to an attempt, which is a call that may fail. A Result will be absent or present, and failed or succeeded.

Open step3/CalculatorActivity.java.

Our complex repository will hold a result of a String:

Repository<Result<String>> mResultRepository;

Therefore, the Updatable will also get a result of an String, which is very convenient as we can react to present, failed, or absent values:

mResultUpdatable = () -> mResultRepository
        .get()
        .ifFailedSendTo(t -> Toast.makeText(this, t.getLocalizedMessage(),
                Toast.LENGTH_SHORT).show())
        .ifFailedSendTo(t -> {
            if (t instanceof ArithmeticException) {
                resultTextView.setText("DIV#0");
            } else {
                resultTextView.setText("N/A");
            }
        })
        .ifSucceededSendTo(resultTextView::setText);

You can add multiple ifFailedSendTo methods. They give access to the throwable that caused the failure, so we can react to different exceptions.

Fix an imperative approach

There are many ways to compile the complex repository. In step3.CalculatorActivity you'll find (a bad) one:

mResultRepository = Repositories.repositoryWithInitialValue(Result.<String>absent())
       .observe(mValue1Repo, mValue2Repo, mOperationSelector)
       .onUpdatesPerLoop()
       .getFrom(() -> "Lambda all the things!")
       .thenTransform(input -> {
           Result<Integer> operation = mOperationSelector.get();
           if (operation.isPresent()) {
               Integer result1;
               int a = mValue1Repo.get();
               int b = mValue2Repo.get();
               switch (operation.get()) {
                   case R.id.radioButtonAdd:
                       result1 = a + b;
                       break;
                   case R.id.radioButtonSub:
                       result1 = a - b;
                       break;
                   case R.id.radioButtonMult:
                       result1 = a * b;
                       break;
                   case R.id.radioButtonDiv:
                       try {
                           result1 = (a / b);
                       } catch (ArithmeticException e) {
                           return Result.failure(e);
                       }
                       break;
                   default:
                       return Result.failure(
                               new RuntimeException("Invalid operation"));
               }
               return Result.present(result1.toString());
           } else {
               return Result.absent();
           }
       })
       .onConcurrentUpdate(RepositoryConfig.SEND_INTERRUPT)
       .compile();

The problem with this approach is that the last function has too much responsibility and it's hard to maintain, reuse and test.

Let's break it into pieces.

In this chapter, we are going to replace the operator thenTransform() with a set of directives that:

But first, let's talk about lambdas:

Using lambda functions with Android Studio

If you use Java 8 and the Jack compiler (or retrolambda), you may use lambdas to keep your code clean of types and annotations.

Agera lets you use concepts from functional programming. If you're not familiar with lambdas, let Android Studio suggest to you what classes to create implementing interfaces and then convert to lambdas automatically. For example:

We know getFrom takes a Supplier. Create a new Supplier anonymous class:

Android Studio will ask for a type.

Set it to String, for now...

Android Studio will ask you to implement the get() method.

Now the IDE will suggest lambdas:

Types? Where we're going we don't need types.

The expression is replaced with a lambda function that takes no arguments and returns a string. The next directive must receive a String in this case or the compiler will complain.

Cleaning up

Once the directives are in place, you can move pieces of logic out of the Activity.

1. Replace the last function in the repository with the flow above, after onUpdatesPerLoop:

You should end up with something similar to this:

mResultRepository = Repositories.repositoryWithInitialValue(Result.<String>absent())
       .observe(mValue1Repo, mValue2Repo, mOperationSelector)
       .onUpdatesPerLoop()
       .getFrom(mValue1Repo)
       .mergeIn(mValue2Repo, Pair::create)
       .attemptMergeIn(mOperationSelector,  CalculatorOperations::attemptOperation)
       .orEnd(Result::failure)
       .thenTransform(input -> Result.present(input.toString()))
       .compile();

Note that we moved the performOperation to a different class, to keep the Activity short.

You might be wondering why there's a Androidified person moving around the calculator. It's our visual indicator of jankiness. Jankiness is produced when our main thread is so busy that it has to drop frames, producing a bad user experience.

We are going to make our calculation really slow, and we'll use a lot of CPU cycles and allocate unnecessary memory to demonstrate how to keep the UI smooth, no matter how busy our device is.

After

.onUpdatesPerLoop()

add these directives to your result repository:

.transform(input -> {
 String stringCounter = "0";
 for (int i = 0; i < 200_000; i++) {
   // Show no love for our CPU and GC.
   Integer intCounter = Integer.valueOf(stringCounter);
   intCounter++;
   stringCounter = intCounter.toString();
 }

 return input;
})

This simulates a very slow operation that uses a lot of CPU and memory. It counts from 0 to 200000 using strings and allocating memory for it. Then it returns the unmodified input parameter.

Open the activity and play around with it, you'll immediately find jankiness which can be confirmed with the Android Monitor in Android Studio:

The green saw wave in the GPU graph means the device had to drop frames because the UI thread was busy.

We need to move this calculation off the main thread. Before the new transform directive, add:

.goTo(mExecutor)

mExecutor is a single thread executor in this case, but you can choose the type and size of the thread pools for intensive and parallel calculations. This directive moves the execution to a different thread from there on. It can be used down the line. For example, you might have a networking thread and a general purpose pool of threads for other operations.

Run the Activity. The CPU usage is still high, but the jankiness is gone and it's not dropping frames any more.

That's it. Moving work to a different thread in Agera is so easy, you should almost always do it. You don't need to create AsyncTasks or worry about callbacks.

Interruptions and onConcurrentUpdate()

Our app is super responsive now but we want it to finish as quickly as possible. The data flow is executed, including the slow function, every time any of the three observables change. What if we change one of the inputs before the previous operation has finished? We want to interrupt this operation to start the new calculation right away, because in this case, we don't care about the previous result.

Add this directive to the repository before .compile().

.onConcurrentUpdate(RepositoryConfig.SEND_INTERRUPT)

This will send an interrupt signal to the operation that is taking place when something changes. This is something you have to implement manually.

Normally, you will only implement an interruption mechanism in slow functions so let's modify ours:

.attemptTransform(input -> {
 String stringCounter = "0";
 for (int i = 0; i < 200000; i++) {
     if (Thread.currentThread().isInterrupted()) {
         return Result.failure();
     }
   // Show no love for our CPU and GC.
   Integer intCounter = Integer.valueOf(stringCounter);
   intCounter++;
   stringCounter = intCounter.toString();
 }
 return Result.present(input);
})
.orEnd(i -> Result.absent())

Now transform is an attemptTransform because we want to fail when an interruption is received.

The final repository looks something like:

mResultRepository = Repositories.repositoryWithInitialValue(Result.<String>absent())
       .observe(mValue1Repo, mValue2Repo, mOperationSelector)
       .onUpdatesPerLoop()
       .goTo(mExecutor)
       .attemptTransform(CalculatorUtils::keepCpuBusy)
       .orEnd(Result::failure)
       .getFrom(mValue1Repo)
       .mergeIn(mValue2Repo, Pair::create)
       .attemptMergeIn(mOperationSelector,  CalculatorUtils::performOperation)
       .orEnd(Result::failure)
       .thenTransform(input -> Result.present(input.toString()))
       .compile();

Congratulations! You have a very performant, jank-free activity. Now let's talk about testing with Agera.

This final chapter requires you to be familiar with advanced Espresso topics, like Idling Resources.

Unit tests

Functions, Binders, Receivers, Suppliers, etc. are very simple interfaces and their implementations are easy to unit test. In the CalculatorOperationsTest class, we added some unit tests using the Result wrapper to check for errors.

@Test
public void attemptOperationDivZero_fails() throws Exception {
   Result<Integer> result = CalculatorOperations.attemptOperation(OPERANDS_1_0, OPERATION_DIV);
   assertFalse(result.isPresent());
   assertTrue(result.failed());
}

UI tests with Espresso

Animations

Animations will sometimes send messages to the main thread's queue, making Espresso wait until the app is idle, indefinitely in this case: we have an animation that needs to be disabled in the Calculator activity.

1. Open Step4CalculatorActivityTest and finish getActivityIntent so that it sends a boolean in the Intent to tell the activity that animations should be disabled.

2. In step4.CalculatorActivity#onCreate() add something like:

// For testing, the animation can be disabled via an Intent.
if (getIntent().hasExtra(ANIMATIONS_ENABLED_KEY)) {
   mAnimationEnabled = getIntent().getBooleanExtra(ANIMATIONS_ENABLED_KEY, true);
}

3. Intercept the start of the animation:

    @Override
    protected void onResume() {
        super.onResume();
        if (mAnimationEnabled) {
            UiUtils.startAnimation(findViewById(R.id.imageView));
        }
    }

If you run the Step4CalculatorActivityTest in step4, you'll notice that the animation is no longer moving. However, this is not enough to prepare our app for UI testing.

Idling Resources

Agera lets you get off the main thread very easily but the moment you do that, Espresso is unaware of messages that are outside the main thread or the AsyncTasks thread(s).

We use Idling Resources to tell Espresso that our app is busy. There are many ways to implement this. The most common is using a CountingIdlingResource to count (increment and decrement) the number of "tasks" that are being processed and if there are zero, the app is idle.

But in this codelab, we're going to go deeper. Since we're creating our own Executor, we can query it to check if there are active tasks.

Check out the ThreadPoolIdlingResource class. You'll find that isIdleNow() is checking if the queue is empty and if there are any active tasks.

    @Override
    public synchronized boolean isIdleNow() {
        if (threadPoolExecutor.getQueue().isEmpty() && threadPoolExecutor.getActiveCount() <= 0) {
            if (callback != null) {
                callback.onTransitionToIdle();
            }
            return true;
        }
        return false;
    }

1. In the class under test, change the Executor in the result repository to use our new CalculatorExecutor.EXECUTOR.

2. In the test class, complete registerIdlingResource so that it creates a new ThreadPoolIdlingResource and passes our executor to it. Also, register the new idling resource with Espresso.registerIdlingResource().

Run the tests, they should all pass, slowly but surely, since Espresso is letting the repository compute the calculations.

You are ready to explore the more advanced topics in Agera. Network calls, SQLite interaction, RecyclerViews and lists, BroadcastReceivers, etc.

For more information:

https://github.com/google/agera

What we've covered

How would you rate your understanding of Agera after the codelab?

Good - I understand the concepts and completed the exercises Good - I read through it and have a good understanding of the concepts Poor - I completed the exercises but I still don't understand it Poor - I read through it but don't understand the concepts at all

How interesting does Agera look after the codelab?

Very interesting - I'm excited about it Interesting - I'll play with it and follow its development It's OK but I don't think I'll use it Not interesting - I'm not interested in reactive patterns Not interesting - I use other reactive frameworks/libraries

Would you use Agera now?

I can't wait! Even for production apps. Yes, but only for personal experiments. I won't use it unless the community recommends it. No, I don't like it.

Comparing Agera with RxJava:

I think Agera solves a problem that RxJava already solves but in a simpler way I think Agera solves a problem that RxJava already solves but with less features I don't know enough about RxJava to answer I don't know enough about Agera to answer

Thank you!

The Agera team