In this article, we will learn how to make a hybrid mobile application using Rails and Hotwire Native.
The Rails application will be a simple CRUD directory app with authentication, while the mobile app will integrate the app with some changes and navigation customization.
The end result will be a modest but functional mobile application built in a fraction of the time it takes to build one using any of the hybrid alternatives like React Native or Flutter.
What is Hotwire Native
It's the mobile part of the Hotwire suite of products.
Announced on September 25, 2024, it's the consolidation of Turbo Native and Strada into a single framework that allows us to build web-first hybrid mobile applications using the tools we already know.
With HN, we can transform our web apps into mobile apps with native characteristics with minimal configuration.
We can release our app to the app stores without writing almost any native code, and changes to our web app won't have to go through the submission process, which can be exhausting.
But, the good thing about HN is that our apps will feel like native apps, and we can control the in-app experience by progressively adding native functionality.
The first step is to add Bridge Components which are a way to add communication between our web and mobile applications.
With them, we can start customizing the way our application behaves and deviating from strictly displaying web views.
Lastly, adding fully native screens is possible with Hotwire Native. These are usually needed when we need high-fidelity/performance screens or access to APIs that are not available in the browser.
How does it work?
Hotwire Native is, basically a native wrapper around our web application.
For Android, it uses a WebView
, which is an object that allows to display content from the web inside the activity layout, instead of opening or directing users to a browser.
For iOS, it uses the WKWebView
class that behaves similarly to the WebView
class in Android.
But it's no
Navigation
When it comes to navigation, HN intercepts link taps and applies some behind-the-scenes work to make our application behave more like a native app: it screenshots the current page and then pushes the new screen to the stack with an animation.
Unlike regular web-view mobile apps that replace the whole content with a refresh, the default navigation that Hotwire Native gives us feels native.
HN adds this navigation pattern by default, but it also allows us to personalize the way navigation happens when we tap on links.
We can have the navigation be of type:
- Replace: the entire page is replaced with the content coming from the tapped link. This behavior is similar to how a web-view behaves by default without Hotwire Native.
- Refresh: it involves updating the content of the current view without changing the navigation stack.
- Clear all: remove everything from the navigation stack except for the root ### Path Configuration It's essentially a JSON file that we use to configure some things outside the default HN behavior. ### Bridge Components They are the way in which our web application can communicate with the native app.
As our native app encapsulates a web-view it actually knows very little about it out of the box beyond the link interception and navigation we previously mentioned.
Bridge components are actually a re-brand of Strada components with the difference that they're included in Hotwire Native without the need to install further dependencies.
They
The web application
On the web side of things, our application is a simple book directory application with authentication.
It has a User
and a Book
model with basic book attributes like: title, excerpt, isbn, description, author_name & pages_count
and an attached cover using Active Storage. We will also have a Review
model in order to showcase forms using HN.
The home view has a hero section and a list of books.
We also have authentication views and a very basic book show
page to display its details separately.
There's also a typical nav bar with the logo to the left, a search bar and a dropdown to the right. On mobile, the nav bar has the logo, the search bar and the hamburger menu icon which toggles a slide over menu.
All in all, a very simple Rails app with some details that are meant to showcase how to integrate an app with Hotwire Native.
Important: as the time of this writing, the latest turbo-rails
version which is 2.0.10
doesn't have the latest Hotwire Native updates and is still referring the previous Turbo Native. So, in order for everything to work correctly, we will need to run from the main branch:
# Gemfile
gem "turbo-rails", github: "hotwired/turbo-rails", branch: "main"
Now that we know what our app will look like, let's start by creating the iOS application:
iOS Application
The first thing we need is to install Xcode version 15 or higher (the latest stable release is 16.0 at the time of writing this). I will be using Xcode 15.1.
Next we need to create a new project using File → New → Project or pressing ⇧ + ⌘ + N
(Shift + Command + N).
The template we need to choose is the iOS App one. Next up is picking a name for our application:
Be careful to pick Storyboard for our interface and Swift as a language. The other alternatives which are SwiftUI and Objective-C won't work with Hotwire Native.
The next step on the wizard is selecting a place to save your project. You can leave the default location or personalize that.
Once that's done we now have an empty iOS application, we need to add the HN dependency to make it work.
To do that we right-click on the root of our project on the left sidebar, which is the item with the default application icon at the top. Then we select the Add package dependencies item.
Next we have to search for the Hotwire Native iOS repository which is located at https://github.com/hotwired/hotwire-native-ios
you should see the following:
Once we find the dependency and click on Add package we will have our dependency available to import it in our application.
Now, we will meet an “empty” iOS application. What we need to do is tell our iOS application to load our website using Hotwire Native. To do so, we have to edit the SceneDelegate.swift
file which is in charge of managing the UI lifecycle.
We will be defining a rootURL
variable which will point to our website
import HotwireNative
import UIKit
let rootURL = URL(string: "http://localhost:3000")!
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
private let navigator = Navigator()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
window?.rootViewController = navigator.rootViewController
navigator.route(rootURL)
}
}
Now, we just have to run our application by clicking Product → Run or the ⌘ + R
keyboard shortcut.
Xcode will open an iPhone simulator and display the contents of our app:
As you can see, it displays basically the same content as I have on the responsive views on the browser but running inside a mobile iOS app.
To make the app feel more native we should get rid of the footer
because it's not a common pattern for mobile apps, and it also includes a lot of potentially distracting information and links.
Luckily, turbo-rails
provides us with a method to check if the current request comes from a Hotwire Native app which is hotwire_native_app?
so we can add the following to our application.html.erb
layout:
<body>
<%= yield %>
<%= render "shared/footer" unless hotwire_native_app? %>
</body>
The footer will render normally for every request except for those that come from Hotwire Native.