Present forms as modals in Hotwire Native

In native iOS and Android apps we can present screens as modals, having them slide up from the bottom instead of from the side. Modals provide focused, interruption-style experiences that are ideal for short, self-contained tasks.

We can take advantage of this UX pattern in Hotwire Native apps by presenting web forms as modals, improving:

  1. Task isolation - Helps focus attention on the specific task.
  2. Validation errors - Easier to handle errors with standard Rails patterns.
  3. Consistency - iOS and Android users expect certain UX when using apps.
Default screen presentation vs. modal presentation on iOS
Default screen presentation vs. modal presentation on iOS

To present forms as modals we need to set up a path configuration rule. This will match a URL when a user taps a link in the app and apply presentation behavior.

Set up remote path configuration #

Start by adding add a new route to config/routes.rb.

# config/routes.rb
resource :configuration, only: :show

Then create a controller with a #show method that renders an empty JSON object.

# app/controllers/configurations_controller.rb
class ConfigurationsController < ApplicationController
  def show
    render json: {
    }
  end
end

Inside the JSON object add the two required keys that path configuration requires: rules and settings. The rules array is where we map URL paths to behavior. We’ll cover settings in a future article.

# app/controllers/configurations_controller.rb
class ConfigurationsController < ApplicationController
  def show
    render json: {
      settings: {},
      rules: [
      ]
    }
  end
end

Rails forms can usually be identified by their URL path ending in /new or /edit. So we’ll add a rule to match those, setting the context property to modal.

# app/controllers/configurations_controller.rb
class ConfigurationsController < ApplicationController
  def show
    render json: {
      settings: {},
      rules: [
        {
          patterns: [
            "/new$",
            "/edit$"
          ],
          properties: {
            context: "modal"
          }
        }
      ]
    }
  end
end

Note that the patterns key matches via regex, hence the trailing $ to match the end of the string.

By hosting the configuration on the Rails server then we can dynamically change behavior. Add a new entry to the patterns array to present a new screen as a modal. The next launch will configure the app with the new configuration - all without a new release to the app stores!

We now have our remote path configuration available at /configuration.json. Up next is telling the Hotwire Native apps where to find it.

Use the remote path configuration #

Load the path configuration in the iOS app from AppDelegate.swift. This ensures the configuration is ready before the app makes it first request.

import HotwireNative
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        let url = URL(string: "http://localhost:3000/configuration.json")!
        Hotwire.loadPathConfiguration(from: [
            .server(url)
        ])
        return true
    }
}

Same for Android, load the path configuration in your Application() subclass or in MainActivity.kt at the end of onCreate(savedInstanceState:).

import dev.hotwire.core.config.Hotwire
import dev.hotwire.core.turbo.config.PathConfiguration

class MainActivity : HotwireActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // ...

        Hotwire.loadPathConfiguration(
            context = this,
            location = PathConfiguration.Location(
                remoteFileUrl = "http://localhost:3000/configuration.json"
            )
        )
    }
}

Forms will now be presented as modals on both platforms, helping users focus on the task and providing consistency with native UX patterns. And context isn’t the only property available, check out the docs for more available options.

Bonus tips #

Here are a few ideas on how you could enhance your path configuration.

  1. Remove the hardcoded localhost URL in the apps with dynamic resolution based on the environment. (Tutorial coming soon!)
  2. Version the path configuration for forward and backward compatibility with new app versions.
  3. Set Hotwire.config.showDoneButtonOnModals = true on iOS to automatically add a Done button to screens presented as modals.