• What’s Next for Mobile at Airbnb:: Bringing the best back to Native
  • By Gabriel Peal
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: ALVINYEH
  • Proofreader: DateBro

Where is Airbnb’s mobile end?

Unleash the full potential of the original

This is aSeries of blog postsIn the fifth part, this article will outline the experience of using React Native and what’s next for Airbnb mobile.

Exciting times are at hand

Even when we tried React Native, we also accelerated Native development. Today, we have a lot of exciting plans in terms of production environments or ongoing projects. Some of these projects were inspired by our best parts and experiences with React Native.

Server driven rendering

Even though we no longer use React Native, we see the value in writing production code only once. We still rely heavily on the Common Design Language system (DLS) because many pages are almost identical on Android and iOS.

Several teams have tried to begin to agree on a powerful server-driven rendering framework. Using these frameworks, the server sends data to the device describing the components that need to be rendered, the page configuration, and the actions that might take place. Each mobile platform then parses this data and renders native pages, or even entire processes, using DLS components.

Server-driven large-scale rendering has many challenges. Here are a few of the issues we’re working on:

  • Component definitions need to be securely updated while maintaining downward compatibility.
  • Type definitions for cross-platform shared components.
  • Respond to events at run time, such as button clicks or user input.
  • Transition between multiple JSON-driven screens while preserving internal state.
  • Render custom components that have no existing implementation at all at build time. We are experimenting with Lona format.

The server-driven rendering framework has provided great value, allowing us to experiment and update functionality in real time.

Epoxy components

In 2016, we open-source Android Epoxidation. Epoxy is a framework that implements simple, heterogeneous RecyclerView, UICollectionView and UITableView. Today, most new pages are Epoxy. This allows us to split each page into separate components, enabling lazy rendering. Today, we have Epoxy on Android and iOS.

On iOS it looks something like this:

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

On Android, we took advantage of using Kotlin to write DSLS to make writing components easier and type-safe:

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

Epoxy Diffing

In React, render returns a list of components. The key to React performance is that these components only represent the actual view /HTML data model that you want to render. The component tree is then extended to render only the parts that have changed. We have developed a similar concept for Epoxy. In Epoxy, you can declare the model for the entire page in buildModel. Used in conjunction with elegant Kotlin and DSL, it is very similar in concept to React and looks like this:

override fun EpoxyController.buildModels() {
  header {
    id("marquee")
    title(R.string.edit_profile)
  }
  inputRow {
    id("first name"First_name) text(firstName) onChange {firstName = it requestModelBuild()}} }Copy the code

Every time the data changes, you call requestModelBuild(), which will re-render your page and invoke the best RecyclerView.

On iOS it looks something 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 inself? .firstName = inputText self? .rebuildItemModel(forDataID: .inputRow)
        }
      })
  }
}
Copy the code

A New Android Product Architecture (MvRx)

One of the most exciting recent developments is that we are developing a new architecture, internally called MvRx. MvRx combines the strengths of Epoxy, Jetpack, RxJava, and many of the principles of Kotlin and React to build new pages that are easier and smoother than ever before. It’s an opinionated and flexible framework that was developed by adopting the co-development patterns we observed and the best parts of React. It’s also thread-safe, with almost everything running from the main thread, which makes scrolling and animation very smooth.

So far, it has worked on a variety of pages and has barely had to deal with the life cycle. We are currently experimenting with a number of Android products, and if it continues to be successful, we plan to open source. Here is the complete code needed to create the functional page that makes the 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 triggers the request and maps its response to Async <Listing> // This is a sealed class, which can be: Unitialized, Loading, Success, and Fail. // There is no need to handle successful and failed callbacks separately! // This request also has a life cycle. It will survive configuration changes // it will not be passed after onStop. ListingRequest.forListingId(12345L).execute { copy(listing = it) } } } class SimpleDemoFragment :MvRxFragment() {// This automatically synchronizes ViewModel state and reconstructs the Epoxy model at any time changes occur. React like rendering method: how to run // parameters or states for every change. 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 views themselves, so calling buildModels costs very little. // RecyclerView Diffing will be done automatically, only model changes will be re-rendered. DocumentMarquee {title(state.listing().name)} } override fun EpoxyController.buildFooter() = fixedActionFooter { val (state) = withState(viewModel) buttonLoading(state is Loading) buttonText(state.listing().price) buttonOnClickListener { _ -> } } }Copy the code

MvRx has a relatively simple architecture for handling Fragment parameters, savedInstanceState persistence for cross-process restarts, TTI tracking, and other features.

We are also working on a similar iOS framework, which is in early testing.

Expect to hear more on this soon, and we are excited about the progress made so far.

The iteration speed

When switching from React Native to Native, the immediate problem is iteration speed. Going from a platform where you can reliably test changed parts in a second or two to a platform where you might have to wait 15 minutes is simply unacceptable. Fortunately, some remedies have been found.

We built the infrastructure on Android and iOS to build only a portion of the application that contains the launcher, and to rely on specific functional modules.

On Android, gradle Product Flavors is available. Our Gradle module looks like this:

This new layer of indirection allows engineers to build and develop on a small part of an application. Used in conjunction with IntelliJ’s uninstall module, it greatly improves build times and IDE performance on the MacBook Pro.

We wrote scripts to create new test Flavors, and in just a few months, we’ve created more than 20. Development with these new Flavors was, on average, 2.5 times faster, and the percentage of build times that took more than five minutes fell 15-fold.

For reference, this is the Gradle code snippet that can be used to dynamically generate the Product Flavor with the root dependency module.

Again, on iOS, our module looks like this:

The same system can be built 3-8 times faster

conclusion

It’s great to be a company that’s not afraid to try new technologies, but still strives to maintain high quality, high speed and a great development experience. Finally, React Native is an important tool for releasing new features, and it gives us new ideas for mobile development. Let us know if you want to get involved!


This is the fifth in a series of blog posts focusing on our experience with React Native and what’s next for Airbnb mobile.

  • Part I: React Native in Airbnb
  • Part TWO: Technical details
  • Part three: Building a cross-platform mobile team
  • Part 4: Decisions made on React Native
  • Part five: What’s next on mobile

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.