jzbrooks

React, Compose, & SwiftUI

The Android UI framework has been around since Android’s birth and available publically since 20091. While there have been some notable changes, like the advent of fragments in the honeycomb release, the fundamental substrate has been largely the same. These days View.java is a good text editor performance benchmark, lists in the UI require carefully arranging several components, and buttons can be configured to make their text label selectable. It’s time to begin again.

React captivated experienced an inexperienced web developers alike with its component-based modularity, reactive paradigm, and declarative UI programming model. Apple and Google took note. The reactive paradigm is the future for user interfaces in iOS and Android SDKs.

Fertile Ground for Reactive UI

Reactive programming has been around since the early 2000s2 and has particularly captivated the Android world in the form of RxJava—probably, though I’m unsure, by way of the Java server programming world. RxJava’s Github repository boasts ~44.4k stars—the most among ReactiveX repositories.

For years Rx was the dominant paradigm for wrangling data in Android applications, probably because AsyncTask<T> left a lot to be desired). Google provided its own endorsed observable machinery, LiveData, that boasts Android-specific features like lifecycle awareness. More recently kotlinx.coroutines’ Flow is giving Rx a run for its money.

Google also provided a library in the Android Gradle Plugin that would facilitate binding view fields to (commonly LiveData) viewmodel properties. The idea was pretty similar to .NET’s WPF/UWP databinding system. However, being laregly a retrofit on a system that wasn’t designed initially for reactivity, there were several rough edges that were tough to tolerate. There’s value in reactivity though, and Google knew it.

In some ways, the transition to this sort of reactive programming model is more natural on Apple platforms. Delegation has been a commonly employed design pattern on the platform basically since its inception. Apple’s Combine Framework is the asynchronus event handling system that enables management of data through operators on reactive streams.

Reactive UI

Google’s adoption of Kotlin as an officialy endorsed programming language in the Android community was certainly the first gale-force gust of the winds of change. Jetpack Compose is the hurricane that follows. Compose is Android’s next-generation UI toolkit. It’s reactive, elegant, and leverages Kotlin features for a particularly integrated experience. There are even some efforts to port the framework to desktop and web contexts.

Compose is built around the idea of functional composition. @Composable annotated functions participate in the Compose machinery by way of a compiler plugin that appends parameters to the parameter list at compile time. These new parameters help manage state, but this is nearly invisible to the programmer. Because of this, Compose closely resemble’s React’s functional components with hooks.

@Composable elegance is manifest in its application to Compose’s UI components, state, and side-effect machinery—all built on the same fundamental mechanism.

SwiftUI is Apple’s take on the reactive UI model. SwiftUI leverages Swift’s particular strengths, resulting in a slightly different programming model than Compose. Combine integrates with SwiftUI and will often be found in the same codebase.

The SwiftUI team has leaned into Swift’s strong suits also, namely what they call ’protocol orientation’. Swift structs that implement the SwiftUI.View protocol specify a body computed property that is analagous to the React.Component render method. The system still involves type hierarchies due to protocol orientation, but props & state are neatly modeled as struct properties.

Reactive UI is not the same thing as reactive programming models for managing data, but they compliment each other well. High cohesion with low coupling is the game.

Let’s compare some code! To keep it simple, we’ll compare a simple todo list item component in React, Compose, and SwiftUI.

A React function component & Compose UI component

function TodoItem(props) {
  const [completed, setCompleted] = useState(props.isCompleted);
  return (
    <li>
      <input type="checkbox"
       defaultChecked={completed}
       onClick={() => setCompleted(!isCompleted)}
       required
      />
      <input type="text"
       placeholder='description'
       value={props.description}
       required
      />
    </li>
  );
}
@Component
fun TodoItem(todo: Todo, onCompletedChanged: (Todo) -> Unit) {
    val (completed, setCompleted) by remember { mutableStateOf(todo.isCompleted) }

    Row {
        Text(todo.task)
        Checkbox(
            completed,
            onCheckChanged = {
                onCompletedChanged(todo.copy(isCompleted = completed)
                setCompleted(!completed)
            }
        )
    }
}

A React class component & SwiftUI view

class TodoItem extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      completed: this.props.isCompleted,
    };
    this.onCompleted = this.onCompleted.bind(this);
  }

  onCompleted() {
    this.setState({
      completed: !this.state.completed
    });
  }

  render() {
    return (
      <li>
        <input type="checkbox"
         defaultChecked={this.state.completed}
         onClick={this.onCompleted}
         required
        />
        <input type="text"
         placeholder='description'
         value={this.props.description}
         required
        />
      </li>
    );
  }
}
struct TodoItem : View {
    var item: Todo

    @State var isChecked = todo.isChecked

    var body: some View {
        HStack {
            Text(todo.description)
            Checkbox(checked: $isChecked, onCheckChanged {
              isChecked.toggle()
              item.isChecked = isChecked
            });
        }
    }
}