wireapp / wire-signals   1.0.0

GNU General Public License v3.0 only GitHub

A small and effective event-handling library for Scala

Scala versions: 3.x 2.13 2.12 2.11

Wire Signals

or Yet Another Reactive Library for Scala

Scala CI

About two thirds of Wire Android code is written in Scala making it unique among Android apps - most of them being implemented in Java and/or Kotlin. Wire is a messenger and as such it must be very responsive: it has to quickly react to any events coming from the backend, as well as from the user, and from the Android OS itself. The Wire Android team developed its own implementation of event streams and "signals" - special event streams with caches holding the last propagated value. They proved to be a very flexible and concise way of handling events all over the Scala code in Wire.

Wire Signals are used extensively in our application, from fetching and decoding data from another device to updating the list of messages displayed in a conversation.

How to use

To include wire-signals in your project, add this to your library dependencies in sbt:

libraryDependencies += "com.wire" %% "wire-signals" % "1.0.0"

Currently wire-signals work with Scala 2.11 (because Android), 2.12, 2.13, and it's ready for Scala 3.

Similarly, you can add wire-signals-extensions to get some additional functionality. The reason for splitting the library into two is that the core library is enough to perform the main task of event streams and signals, which is to transport events across the app. For some users this might be all they need and so they will be interested more in lack of additional dependencies, small codebase, and performance. The core library is dependent in production only on the standard Scala library.

In short, you can create a SourceSignal somewhere in the code:

val intSignal = Signal(1) // SourceSignal[Int] with the initial value 1
val strSignal = Signal[String]() // initially empty SourceSignal[String]

and subscribe it in another place:

intSignal.foreach { number => println("number: $number") }
strSignal.foreach { str => println("str: $str") }

Now every time you publish something to the signals, the functions you provided above will be executed, just as in case of a regular event stream...

scala> intSignal ! 2
number: 2

... but if you happen to subscribe to a signal after an event was published, the subscriber will still have access to that event. On the moment of subscription the provided function will be executed with the last event in the signal if there is one. So at this point in the example subscribing to intSignal will result in the number being displayed:

> intSignal.foreach { number => println("number: $number") }
number: 2

but subscribing to strSignal will not display anything, because strSignal is still empty. Or, if you simply don't need that functionality, you can use a standard EventStream instead.

You can also of course map and flatMap signals, zip them, throttle, fold, or make any future or an event stream into one. With a bit of Scala magic you can even do for-comprehensions:

val fooSignal = for {
 number <- intSignal
 str    <- if (number % 3 == 0) Signal.const("Foo") else strSignal
} yield str

If you want to know more about how we use it: