The Fundamentals of Reactive Programming

Reactive programming is a programming paradigm oriented around data streams and the propagation of change. Unlike traditional programming methods, which execute a set of commands sequentially, reactive programming listens for changes and reacts by updating automatically in real time. This approach is particularly beneficial in modern software development, where apps are often required to handle a lot of dynamic information, such as user interactions, network requests, and sensor data, efficiently and in real time.

The significance of reactive programming lies in its ability to create more responsive, scalable, and easily maintainable apps. It allows developers to construct systems that are better adapted to the asynchronous nature of today’s app environments. By leveraging observable data streams, developers can write code that’s more declarative and focuses on the what rather than the how, leading to clearer and more concise codebases.

Reactive vs. Imperative Programming: A Comparison

To appreciate the advantages of reactive programming, it’s essential to contrast it with imperative programming, the more traditional approach in software development.

Imperative Programming

Imperative programming is a programming paradigm that describes computation in terms of statements that change a program state. In an imperative programming approach, developers write code that specifies the exact steps needed to achieve a desired outcome. This method is similar to a recipe, in which the programmer gives the computer a set of ordered instructions to follow.

The usual characteristics of imperative programming are:

  • Sequential execution: You usually write instructions in the exact order you want to execute.
  • Explicit state modifications: You change the state of your program or app explicitly by assignments and other control structures.
  • Procedural approach: Programs are often structured into procedures or functions, each performing a specific task.

Let’s imagine a simple cooking app. Usually, there’s a feature that needs to fetch some data from some data source and render that data on the screen. In a modern Android app, you could find code like the following. Please keep in mind that this example will omit some parts of the code that might be present in the real app.

class ViewModel {

  // ...

  private fun renderMeal() {

    // Step 1: Show loading
    view.showLoading()

    // Step 2: Fetch carrots
    val carrots = carrotStore.fetchCarrots()

    // Step 3: Filter carrots
    val filteredCarrots = carrotFilter.filter(carrots)

    // Step 4: Cut carrots
    val cutCarrots = carrotCutter.cutCarrots(filteredCarrots)

    // Step 5: Cook carrots
    val cookedCarrots = carrotCooker.cookCarrots(cutCarrots)

    // Step 6: Hide loading
    view.hideLoading()

    // Step 7: Render view
    view.renderMeal(cookedCarrots)
  }
}

In this example, you can see how steps are clearly defined, and they directly control the flow of operations. This approach is very straightforward. It’s also important to notice that someone needs to explicitly call renderMeal().

Reactive Programming

Reactive programming is a programming paradigm oriented around data streams and the propagation of changes. In reactive programming, developers define how the app should respond to changes in data, creating a dynamic data flow. This approach is similar to setting up a series of dominoes; you design how they fall in response to changes rather than manually tipping each one.

Key characteristics of reactive programming include:

  • Data streams and changes: Data, whether user input, server responses, or other, is treated as streams. These streams emit data items or events over time.
  • Reactive updates: Instead of explicitly updating the app’s state, you define how the app should react to changes in these data streams. When new data arrives or an event occurs, the app automatically reacts to update accordingly.
  • Functional and declarative approach: Reactive programming often employs functional programming concepts, allowing you to transform and combine streams with different operators. You describe what should happen when data changes rather than detailing how to perform each operation.
  • Observables and observers: In many reactive systems, observables represent data streams, and observers subscribe to these streams. When a stream emits a new item, it notifies all its subscribers.

The previous example could look like this in the more reactive world:

class ViewModel {

  // ...

  private fun subscribeToOrders() {
    carrotOrders()
      // Step 1: Show loading
      .onEach { showLoading() }

      // Step 2: Fetch carrots
      .flatMap { order -> carrotStore.fetchCarrots() }

      // Step 3: Filter carrots
      .filter { carrots -> matchesQuality(carrots) }

      // Step 4: Cut carrots
      .map { carrots -> carrotCutter.cutCarrots(carrots) }

      // Step 5: Cook carrots
      .map { cutCarrots -> carrotCooker.cookCarrots(cutCarrots) }
      .collect { cookedCarrots ->

        // Step 6: Hide loading
        hideLoading()

        // Step 7: Emit data for the view
        preparedMeals.emit(cookedCarrots)
      }
  }
}

Just by looking at this, you can immediately see the difference in the code’s structure. You can still see the same steps, but they all kind of react to each other. What triggers the whole operation is also noticeable. For this example, you also need to assume that you have a View that listens for your data.

Now that you’ve seen the gist of it, you’ll learn this by coding it!

See forum comments
Download course materials from Github
Previous: Introduction Next: Reactive Demo