Original link: medium.com/airbnb-engi…

We are inFive articles in this seriesIn, it tells the history of Airbnb’s mobile terminal development with React Native and its plan after giving up React Native. This is the fifth article in this series.

This is the fifth in a series of blog posts in which we outline our experience with React Native and what is next for mobile at Airbnb.

Exciting times

Exciting Times Ahead

While conducting experiments with React Native, we also accelerated the development of our Native end. Today, we have many exciting projects in production or in the pipeline. Some of these projects were inspired by the best parts of React Native and our experience with it.

Even while experimenting with React Native, we continued to accelerate our efforts on native as well. Today, we have a number of exciting projects in production or in the pipeline. Some of these projects were inspired by the best parts and learnings from our experience with React Native.

Server-side driven rendering

Server-Driven Rendering

Even though we stopped using React Native, we learned the value of only writing code once. We still rely heavily on our Unified Design Language system (DLS), and a lot of the interface looks pretty much the same on Android and iOS.

Even though we’re not using React Native, we still see the value in writing product code once. We still heavily rely on our universal design language system (DLS) and many screens look nearly identical on Android and iOS.

Several teams have begun to experiment and unify powerful server-side driven rendering frameworks. Through these frameworks, the server sends data to the client device describing the components that need to be rendered, the interface configuration, and the actions that can be performed. Each mobile platform then parses this data and renders the native interface, or even the entire production flow, using DLS components.

Several teams have experimented with and started to unify around powerful server-driven rendering frameworks. With these frameworks, the server sends data to the device describing the components to render, the screen configuration, and the actions that can occur. Each mobile platform then interprets this data and renders native screens or even entire flows using DLS components.

Large-scale server-side driven rendering brings its own challenges. Here’s part of what we’re working on:

  • Securely update component definitions while maintaining backward compatibility.
  • The definition of cross-platform sharing components.
  • In response to run-time events, such as button clicks or user input.
  • Switch between several JSON-driven interfaces while maintaining internal state.
  • Renders the entire custom component that was not implemented at build time. We are experimenting with this in Lona format.

Server-driven rendering at scale comes with its own set of challenges. Here is a handful we’re solving:

  • Safely updating our component definitions while maintaining backward compatibility.
  • Sharing type definitions for our components across platforms.
  • Responding to events at runtime like button taps or user input.
  • Transitioning between multiple JSON-driven screens while preserving internal state.
  • Prototype entirely custom components that don’t have Existing Implementations at build-time. We’re Asking with the Lona format for this.

The serviceability driven rendering framework was of great value, allowing us to experiment and update features in real time without a manual download.

Server-driven rendering frameworks have already provided huge value by allowing us to experiment with and update functionality instantly over-the-air.

Epoxy components

Epoxy Components

In 2016, we opened source project Epoxy on Android. Epoxy is a framework that enables easy implementation of heterogeneous RecyclerView, UICollectionView and UITableView. Today, Epoxy is used on most of our new interfaces. This allows us to split an interface into separate components and implement lazy rendering. Today, we have Epoxy on Android and iOS.

In 2016, we open sourced Epoxy for Android. Epoxy is a framework that enables easy heterogeneous RecyclerViews, UICollectionViews, and UITableViews. Today, most new screens use Epoxy. Doing so allows us to break up each screen into isolated components and achieve lazy-rendering. Today, we have Epoxy on Android and iOS.

Epoxy’s code for iOS looks like this:

This is what it looks like on iOS:

BasicRow.epoxyModel( content: BasicRow.Content( titleText: "Settings", subtitleText: "Optional subtitle"), style: .standard, dataID: "settings", selectionHandler: { [weak self] _, _, _ in self? .navigate(to: .settings) })Copy the code

On Android, we take advantage of DSLs in Kotlin’s capabilities to make component code both easy to write and type-safe:

On Android, we have leveraged the ability to write DSLs in Kotlin to make implementing components easy to write and type-safe:

basicRow {
 id("settings")
 title(R.string.settings)
 subtitleText(R.string.settings_subtitle)
 onClickListener { navigateTo(SETTINGS) }
}
Copy the code

Epoxy Diffing

Epoxy Diffing

In React, you return a list of components from the renderer. The key to React performance is that those components are data models that describe the views or HTML that you want to render. The component tree is then compared, and only the parts that have changed are distributed. We have a similar system in Epoxy. In Epoxy, we declare the entire interface model in the buildModels function. This, along with the elegant Kotlin DSL, makes it very similar in concept to React, and the code looks like this:

In React, You return a list of components from render. The key to React’s performance is that those components are just a data model representation of the actual views/HTML you want to render. The component tree is then diffed and only the changes are dispatched. We built a similar concept for Epoxy. In Epoxy, you declare the models for your entire screen in buildModels. That, paired with the elegant Kotlin DSL makes it conceptually very similar to React and looks like this:

override fun EpoxyController.buildModels() {
  header {
    id("marquee")
    title(R.string.edit_profile)
  }
  inputRow {
    id("first name")
    title(R.string.first_name)
    text(firstName)
    onChange { 
      firstName = it 
      requestModelBuild()
    }
  }
  // Put the rest of your models here...
}
Copy the code

Every time the data changes, you just call requestModelBuild() and it will rerender your interface using the best RecyclerView distribution call.

Any time your data changes, you call requestModelBuild() and it will re-render your screen with the optimal RecyclerView calls dispatched.

On iOS, the code looks like this:

On iOS, it would look like this:

override func itemModel(forDataID dataID: DemoDataID) -> EpoxyableModel? { switch dataID { case .header: return DocumentMarquee.epoxyModel( content: DocumentMarquee.Content(titleText: "Edit Profile"), style: .standard, dataID: DemoDataID.header) case .inputRow: return InputRow.epoxyModel( content: InputRow.Content( titleText: "First name", inputText: firstName) style: .standard, dataID: DemoDataID.inputRow, behaviorSetter: { [weak self] view, content, dataID in view.textDidChangeBlock = { _, inputText in self? .firstName = inputText self? .rebuildItemModel(forDataID: .inputRow) } }) } }Copy the code

A New Android Product Framework (MvRx)

A New Android Product Framework (MvRx)

One of the most exciting recent developments is a new framework we are working on, internally we call it MvRx. MvRx combines the strengths of Epoxy, Jetpack, RxJava and Kotlin, as well as many React principles, to make building the New Interface easier and more seamless than ever. It’s a bit self-righteous, but it’s a very flexible framework, and we used a lot of common development patterns and React benefits to develop it. It’s also thread-safe, it basically runs everything in child threads, which makes scrolling and animation very smooth.

One of the most exciting recent developments is a new Framework we’re developing that we internally call MvRx. MvRx combines the best of Epoxy, Jetpack, RxJava, and Kotlin with many principles from React to make building new screens easier and more seamless than ever before. It is an opinionated yet flexible framework that was developed by taking common development patterns that we observed as well as the best parts of React. It is also thread-safe and nearly everything runs off of the main thread which makes scrolling and animations feel fluid and smooth.

It already works on a number of interfaces and basically eliminates the need to deal with life cycles. We’re currently testing it on some Android products, and if it continues to be successful, we’ll open source it. Here is the complete code needed to build a usable interface with network requests:

So far, it has worked on a variety of screens and nearly eliminated the need to deal with lifecycles. We are currently trialing it across a range of Android products and are planning on open sourcing it if it continues to be successful. This is the complete code required to create a functional screen that makes a network request:

data class SimpleDemoState(val listing: Async<Listing> = Uninitialized) class SimpleDemoViewModel(override val initialState: SimpleDemoState) : MvRxViewModel<SimpleDemoState>() { init { fetchListing() } private fun fetchListing() { // This automatically fires off a request and maps its response to Async<Listing> // which is a sealed class and can be: Unitialized, Loading, Success, and Fail. // No need for separate success and failure handlers! // This request is also lifecycle-aware. It will survive configuration changes and // will never be delivered after onStop. ListingRequest.forListingId(12345L).execute { copy(listing = it) } } } class SimpleDemoFragment : MvRxFragment() { // This will automatically subscribe to the ViewModel state and rebuild the epoxy models // any time anything changes. Similar to how React's render method runs for every change of // props or state. private val viewModel  by fragmentViewModel(SimpleDemoViewModel::class) override fun EpoxyController.buildModels() { val (state) = withState(viewModel) if (state.listing is Loading) { loader() return } // These Epoxy models are not the views themself so calling buildModels is cheap. RecyclerView // diffing will be automaticaly done and only the models that changed will  re-render. documentMarquee { title(state.listing().name) } // Put the rest of your Epoxy models here... } override fun EpoxyController.buildFooter() = fixedActionFooter { val (state) = withState(viewModel) buttonLoading(state is Loading) buttonText(state.listing().price) buttonOnClickListener { _ -> } } }Copy the code

MvRx has a wrapper for handling Fragment parameters, savedInstanceState persistence on process restart, TTI monitoring, and many other features that are easy to call.

MvRx has simple constructs for handling Fragment args, savedInstanceState persistence across process restarts, TTI tracking, and a number of other features.

We are also working on a similar framework for iOS, which is currently in early beta.

We’re also working on a similar framework for iOS that is in early testing.

We expect to hear more about this this morning, but we’re very excited about the progress we’ve made so far.

Expect to hear more about this soon but we’re excited about the progress we’ve made so far.

The iteration speed

Iteration Speed

One obvious point when switching from React Native to Native is iteration speed. Instead of waiting a second or two to reliably test your code changes, you now have to wait 15 minutes at most, which is unacceptable. Fortunately, we will also provide some much-needed improvements.

One thing that was immediately obvious when switching from React Native back to native was the iteration speed. Going from a world where you can reliably test your changes in a second or two to one where may have to wait up to 15 minutes was unacceptable. Luckily, we were able to provide some much-needed relief there as well.

We built an infrastructure on Android and iOS that allows you to compile only one part of your App, which contains a launcher, and can also rely on specific feature modules.

We built infrastructure on Android and iOS to enable you to compile only part of the app that includes a launcher and can depend on specific feature modules.

On Android, we use Gradle Product Flavors. Our Gradle module looks like this:

On Android, this uses gradle product flavors. Our gradle modules look like this:

This new indirect reference allows engineers to build and develop a small part of the entire App. Coupled with IntelliJ’s Module uploading, builds and IDE performance on MacBook Pro were significantly improved.

This new level of indirection enables engineers to build and develop on a thin slice of the app. That paired with IntelliJ module unloading dramatically improves build and IDE performance on a MacBook Pro.

We built scripts to create new test flavors, and in just a few months, we’ve created more than 20 of them. Builds in development mode with these new Flavors were 2.5 times faster than average, and the percentage of builds that took more than five minutes was 15 times lower.

We have built scripts to create a new testing flavor and in the span of just a few months, we have already created over 20. Development builds that use these new flavors are 2.5x faster on average and the percentage of builds that take longer than five minutes is down 15x.

For reference, this code snippet is used to dynamically generate flavor, a product with root-dependent modules.

For reference, this is the gradle snippet used to dynamically generate product flavors that have a root dependency module.

Similarly, on iOS, our module looks like this:

Similarly, on iOS, our modules look like this:

The same system delivers compilation speeds three to eight times faster than before.

The same system results in builds that are 3 — 8x faster.

conclusion

Conclusion

It is exciting to work for a company that is not afraid to experiment with new technologies while maintaining high standards of quality, speed and development experience. At the end of the day, React Native is an important tool we use to implement features and gives us a lot of new ways to think about mobile development. If this sounds like a journey you’d like to be part of, let us know!

Batelco: It is exciting to be at a company that isn’t afraid to try new technologies yet to maintain an incredibly high bar for quality, speed, and developer experience. At the end of the day, React Native was an essential tool in shipping features and giving us new ways of thinking about mobile development. If this sounds like a journey you would like to be a part of, let us know!


This is the fifth installment in a series that focuses on React Native’s journey with Airbnb and its plans for the future.

This is part five in a series of blog posts highlighting our experiences with React Native and what’s next for mobile at Airbnb.