ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

Actor based peaceful state management

There are already so many app-level architecture and presentation layer patterns (MVC/MVP/MVVM/MVI/MVwhatever) that exist to help us manage the complexity and state of our application without going insane. Each pattern has its pros and cons.

Here, the state could be anything, it could represent the whole app, a screen, a view component, a shopping cart, a user’s authentication state, or anything. It is up to you to define the scope of the state.

How you define the state is an art in itself.

Everything in our application happens concurrently. We have network requests, database operations, system broadcasts, push notifications, lifecycle events, and user inputs all going on concurrently.

Flowchart about concurrent nature of Android app
Concurrent nature of the app

The concept of threading is far more complex than it looks. Let’s take a simple counter-example that simulates a concurrent environment where multiple threads are trying to update a mutable state without using any synchronization mechanism or concurrent utilities.

A simple counter example without synchronization

The final value of the counter should be 100000, but it hardly ever is, unless you get very lucky.

What if there is a way to access and update the state safely across threads without explicitly defining any synchronization mechanism or using concurrent utilities?

But why?

Handling the state in concurrent systems is hard. We all know and might have learned the hard way that concurrent read and write to a shared mutable state will always put our app in a non-deterministic (unexpected) state if not set up properly to deal with the concurrent nature of the environment our application lives in. We can’t avoid it, so we usually end up using some sort of synchronization mechanism as a solution, most of the time as an afterthought.

The significance of having proper state management becomes much more apparent when working on a Kotlin-MultiPlatform (KMP) project since KMP doesn’t provide any synchronization mechanisms to deal with concurrency out of the box.

"Synchronization on any object is not supported on every platform and will be removed from the common standard library soon." - Kotlin

Additionally, Kotlin has two rules regarding state sharing in native platforms (non JVM).

  • Rule 1: Mutable state == 1 thread. If your state is mutable, only one thread can see it at a time.
  • Rule 2: Immutable state == many threads. If a state can’t be changed, multiple threads can safely access it.

These two rules can be considered as a good practice for all platforms .

So to handle concurrency that also satisfies the above rules, either we need to use some libraries providing concurrent utilities or provide our own platform-specific synchronization mechanism.

Enter Actor(s)

The Actor model is a conceptual model to deal with concurrent computation. It defines a set of guidelines on how system components should interact in a concurrent computational environment.

In a typical concurrency model, we use synchronization primitives to prevent starvation, deadlocks, and race conditions when multiple threads try to access or mutate a shared state. So, basically, we are sharing the memory (state) between multiple threads which leads to all sorts of problems when we scale.

A flowchart on a typical concurrency approach
A typical concurrency approach

Actor(s) on the other hand is based on the idea of

Do not communicate by sharing memory; instead share memory by communicating.

What is Actor?

An Actor in an Actor model is the fundamental unit of computation. They encapsulate state, and a message queue (like a mailbox). Messages are sent asynchronously to an Actor and it will process the received messages sequentially in FIFO order.

Flowchart explaining the structure of a Actor
Structure of an Actor

The most famous implementations of the Actor Model are Akka and Erlang. Even though Actors are predominantly used in the backend systems, we can adapt some of its traits for the frontend applications to help us with managing the state efficiently to achieve a deterministic state behaviour.

How does Actor help?

An Actor runs in its own thread and its private state can be accessed or modified only by processing the received messages from its queue (mailbox). To even access the state of an Actor, you send a message requesting the current state and the Actor will provide a copy of its state.

Messages are simple, immutable data structures that can be sent to an Actor from any thread.

A flowchart explaining how Actors work and interact.
Actor based concurrency

This removes the need for lock-based synchronization, because:

The Actor model is designed to be message-driven and non-blocking. It has synchronization built into it by its design.

This also satisfies the above-mentioned rules of concurrency in Kotlin Native and allows us to define the state management implementation in the common module itself, thus removing the need to provide platform-specific implementations.

Actor test drive

Jeep wrangler rubicon cover image for Actor test drive section
Photo by cody lannom on Unsplash

Kotlin along with coroutines helps us build an Actor based system quite easily. In fact, Kotlin coroutines come with experimental actor implementation.

To begin with, we will use the experimental actor coroutine builder that comes with a mailbox Channel to receive messages from outside.

A simple counter state machine using an Actor

You can execute the above code and witness the power of the Actor model yourself!

Building a simple counter app

Excavator image for Building a counter app section.
Photo by Luke Besley on Unsplash

Let’s see how we can use the Actor model concept to manage our counter app state.

This example is based on MVI/Flux/Redux, simply put, uni-directional flow concept.

First, define the state:

data class CounterState(val count: Int = 0)

Define messages that our Actor based state machine can process:

Our Actor based state machine and Store:

For the sake of simplicity, think of StateMachine like a container holding the state and the logic to update the state.

The CounterStateStore provides the inputs to run the counterStateMachine.

In the context of Android or any frontend application where the view layer needs to react to state changes, we slightly modified the Actor to broadcast the state changes as a StateFlow . Others can get the current state using GetCounterState message. The “others” here refer to side-effects and more.

SideEffects are the place where we do IO operations, navigation, logging, etc… Think of it as the use-case in the domain layer of clean architecture. We defined SideEffect as a separate class just for example, but anything can act as a SideEffect by listening to messages or state changes from the CounterStateStore . The SideEffect can also send messages to the CounterStateStore .

We can also define the SideEffect as an Actor, kind of a worker Actor for our main Actor: counterStateMachine.

The CounterSideEffect listens for IncrementCounter message and write the counter value to our imaginary database.

The CounterViewModel acts as a lifecycle aware container for our CounterStateStore.

Finally, the view:

That’s it, we have an Actor model based state management without using any synchronization primitives.

Flowchart explaining the architecture of a simple counter app
A simple counter app architecture

Wrapping up

There’s a caveat in using Kotlin’s experimental actor . Kotlin has recently marked the actor with ObsoleteCoroutinesApi . Since the experimental actor implementation cannot be extended to suit complex use cases, Kotlin will be deprecating it to make way for more powerful Actors. It doesn’t mean, end of life. The good news is, we can build our own Actor.

As we mentioned earlier, an Actor for state management contains an internal state, a mailbox (Channel) to receive messages, and the business logic to decide how to change its state based on the received message. With that we can roll out our own Actor based state machine:

Custom actor based state machine

For a complete Actor based setup, you can check out the Flywheel library, which implements the Actor model concept based on a uni-directional flow concept covering all practical use cases.

Generally, they say: one Actor is no Actor. Actors come in systems, an Actor can create more Actors (think of it as a worker Actor). It is true in the backend world, where generally multiple Actors will be running and communicating with each other through messages. You can relate it to (sort of) microservices architecture. Actors can be distributed across multiple systems (servers). As long as we know the address of the Actor, we can establish communication.

In the context of Android, to achieve better separation of concerns and modularize our codebase, we can design our features, components, repositories, use-cases, modules, dynamic modules as Actors and provide a common communication channel for all Actors to communicate with each other at the app level or any feature level. This might sound similar to an EventBus pattern. Yes, it is, albeit a much better and a powerful one.

Flowchart representing complete app designed around Actors
Full Actor based app architecture

Actors are much more capable and powerful, we just scratched the surface of the Actor model concepts on how it can help us manage the state peacefully.

To learn more about Actors:

Thank you for reading. I hope this article will sparkle discussion about using the Actor model in Android development. Please share your feedback and let me know your thoughts.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Written by Abhi Muktheeswarar

I was born designer, but education made me an engineer.