Keynote: Hotwire Native - A Rails Developer's Secret Tool for Building Mobile Apps


Summarized using AI

Keynote: Hotwire Native - A Rails Developer's Secret Tool for Building Mobile Apps

Joe Masilotti • September 04, 2025 • Amsterdam, Netherlands • Keynote

Introduction

The keynote, delivered by Joe Masilotti at Rails World 2025, introduces Hotwire Native—a tool designed to empower Rails developers to build mobile apps for iOS and Android using their existing Rails stack. The talk highlights how Hotwire Native lowers the barriers to mobile development, allowing developers to maintain productivity in Rails without extensive mobile development expertise, and delivers the advantages of both web and native mobile environments.

Key Points

  • The Challenge for Rails Developers: Rails provides fast, easy web development, but building mobile apps traditionally demands learning new languages (Swift for iOS, Kotlin for Android), managing different UI toolkits, and duplicating business logic, significantly increasing maintenance for small teams.
  • Mobile Apps vs. the Web: Mobile apps offer enhanced discoverability (via the App Store/Google Play) and native capabilities that browsers lack, such as push notifications, Bluetooth/NFC integrations, access to device sensors (calendar, contacts), and platform-specific features like deep integration with Google Fit/Apple Health.
  • Limitations of Alternatives:
    • PWAs: Limited native access, unreliable push notifications, lack of app store visibility, and user-unfriendly installation processes.
    • React Native: Reduces duplication across iOS/Android but still requires building and maintaining separate web and mobile codebases, often forcing teams out of their preferred Rails ecosystem.
  • Hotwire Native Approach: Hotwire Native bridges this gap by embedding a web view inside a minimal native shell, allowing developers to reuse their HTML, CSS, and much of their existing server-side business logic. Key advantages:
    • Deploy web and mobile changes instantly, sometimes skipping app store reviews for server-side changes.
    • Use platform-specific native chrome (navigation/tab bars), ensuring that apps feel truly native.
  • Technical Details and Features:
    • Webview-based Rendering: The same HTML/CSS is delivered to both the mobile web and mobile app via an embedded web view, with minimal adjustments for screen-specific navigation bars.
    • Bridge Components: Extend web views with native interactions (e.g., native buttons, dropdowns, biometric authentication, barcode scanners) via a combination of Stimulus controllers and platform-specific native callbacks, promoting code reuse and rapid feature delivery.
    • Native Screens: For high-performance or deeply integrated features, full native screens can coexist with web-based content and are dynamically controlled through server-side configuration.
    • Simplified Setup: The latest Hotwire Native API reduces boilerplate setup to a few lines for both iOS and Android, making native app development accessible to Rails developers.
    • Offline Caching (Upcoming): New features such as offline mode (enabled by service workers) are under development, further enhancing app reliability.
  • Examples and Case Studies:
    • Multiple real-world apps (e.g., Ruby Friends, Basecamp, Hey Email, Hey Calendar, The Story Graph) use Hotwire Native for parity across web and mobile, successful app store deployment, and scaling from solo developers to millions of users.
    • The speaker illustrates how integrated bridge components and native screens serve specialized use cases without sacrificing maintainability.
  • Ecosystem and Community:
    • Hotwire Native is now a first-class entity in the Hotwire documentation ecosystem, with detailed guides, demo apps, and a component library (bridgecomponents.dev) for rapid prototyping and advanced use cases.

Conclusion

Hotwire Native delivers a powerful solution for Rails developers, enabling them to create, deploy, and maintain native-feeling mobile apps without duplicating business logic or deep native expertise. This approach facilitates rapid iteration, app store presence, and scalable maintenance for teams of all sizes, bridging the gap between the productivity of Rails for the web and the demands of modern mobile app development.

Keynote: Hotwire Native - A Rails Developer's Secret Tool for Building Mobile Apps
Joe Masilotti • Amsterdam, Netherlands • Keynote

Date: September 04, 2025
Published: Mon, 15 Sep 2025 00:00:00 +0000
Announced: Tue, 20 May 2025 00:00:00 +0000

Hotwire Native lowers the barrier to launching native apps on the App Store and Google Play. Rails developers can now ship to iOS and Android without rewriting their entire stack. By reusing your existing HTML and CSS, you can keep your business logic on the server and stay productive in Rails - without needing to become a mobile expert.

Rails World 2025

00:00:09.440 talk about hot wire native. But first
00:00:14.639 I got to say something. We're freaking spoiled.
00:00:19.680 Rails developers are spoiled. It has never been easier to build,
00:00:27.359 iterate, and launch with Rails than it has been today.
00:00:32.480 Even complex UX interactions are possible without writing any JavaScript
00:00:37.520 thanks to Hot Wire on the web and Stimulus and Turbo.js.
00:00:43.200 Our feedback loops are lightning fast. Sometimes we can go just minutes from
00:00:50.160 deploying to seeing our code live in production, sometimes seconds.
00:00:56.399 And the best part is that we can ship whatever we want. There's no
00:01:01.760 gatekeepers. There's no permission needed. It truly is the open web.
00:01:13.439 But when it comes to mobile, specifically mobile apps,
00:01:20.400 suddenly the rules change. We have new languages to learn, new toolkits to master, app store rules to
00:01:28.720 abide by. All of a sudden, we've lost control, and
00:01:34.000 we're playing by someone else's rules, Apple or Google or both.
00:01:39.920 What felt effortless on the web suddenly feels impossible
00:01:46.320 on mobile. But we can't avoid mobile apps anymore.
00:01:58.159 Almost all time spent on mobile devices is spent in apps.
00:02:04.399 92% versus the 8% in a browser.
00:02:11.520 Mobile apps aren't a nice to have anymore. For many products and industries, it's the expectation.
00:02:20.480 Said another way, users live in apps.
00:02:27.520 And getting there, getting to try it in the first place. The app store and Google Play
00:02:40.319 on your laptop. Someone walks up and says, "Hey, how do I app?" And you roll
00:02:45.840 your eyes and say, "Oh, you visit my website. It's easy."
00:02:51.840 And they pull out their phone and they search for your app in the app store.
00:02:59.200 You just lost a moment there. When someone hears about your product, their first instinct is not to type a URL into
00:03:06.640 the browser. They go to the app store or they go go to Google Play and they search. And if you're not there, you've
00:03:14.159 just lost a potential customer. Outside of discoverability,
00:03:20.800 there are a bunch of things that mobile apps can do that web cannot.
00:03:27.760 reliable and customizable push notifications like interactive buttons
00:03:33.519 or dynamic content that updates after the push notification has been sent.
00:03:41.200 interactions with Bluetooth and NFC, pairing with a smart scale or a blood
00:03:48.080 pressure cuff, a heart rate monitor, controlling nearby IoT devices like
00:03:54.239 lights or speakers, an NFC, nearfield communication, tapping
00:04:00.959 a badge at a conference to check into a session, or scanning a product tag and
00:04:07.120 getting instant details on it on your phone, or rewards. integrations
00:04:15.519 with Google Fit and Apple. Log,
00:04:21.440 syncing steps, uh, tracking heart rate, logging calories, reading calories.
00:04:27.840 There's no API accessible for the web for these things.
00:04:33.280 And not to mention the real data on the device, the user's calendar, contacts,
00:04:39.199 and wallet. Deep integrations with these are possible with mobile apps.
00:04:45.040 Automatically blocking off time in someone's calendar when they register for a conference session. Inviting all
00:04:51.360 your friends from your address book by the ones you select and not dealing with
00:04:56.479 a clunky URL to share. or loyalty cards that show up on your
00:05:02.160 lock screen when you physically enter a store.
00:05:07.759 Mobile has a lot of advantages, mobile apps. So, let's say that at this point,
00:05:14.000 6 minutes in, I've convinced you. You're ready to build a mobile app, right?
00:05:20.880 Let's go. So, first we have to learn Swift, the programming language for iOS
00:05:26.720 apps. Then we have to learn Cotlin of course for Android. Then we want to get something on the screen. So we have to
00:05:32.639 learn Swift UI for iOS, Jetack Compose on Android. Once we have, you know,
00:05:38.479 stuff on the screen, we want to make sure we know it works with iOS 18 and one step back with iOS 17, iOS 26 in a
00:05:45.840 few days, like seven versions of Android because of the fragmentation.
00:05:51.360 And then we have it built. We're ready to go. We want to share it with our users. Well, now we need to get it into
00:05:56.960 the App Store and Google Play. And this is just the front end. We
00:06:02.960 haven't even talked about getting the data from our server into those apps.
00:06:09.600 So on the web, we know this, right? Getting data to the
00:06:15.440 device, the browser. We have a model that sends some data to the controller,
00:06:21.120 maybe through an IVAR. um sorry some data to the view through an IVAR maybe you're using ERB and that
00:06:28.880 renders out some HTML easy but for mobile apps we need this in
00:06:35.280 place and then we also need another layer we need an API specific controller
00:06:42.960 to massage our data from the model to get it to work in our JSON view
00:06:50.080 from there we send the JSON over the wire to iOS where we render out our screen in Swift UI and then to Android
00:06:57.039 where we render out our screen in Jetack Compose and I've skipped all the networking.
00:07:02.720 We've duplicated our work and we've left the Rails ecosystem. We now have twice as much if not three
00:07:10.000 times as much business logic to maintain and every new feature requires building
00:07:15.520 it for the web, iOS and Android. three times. When something breaks, three
00:07:22.080 times the debugging for small teams, let alone solo developers. This is a
00:07:28.240 maintenance nightmare. It's not possible. It makes native apps feel impossible
00:07:37.919 for Rails developers. Now, I know there's someone out there
00:07:44.639 going, "Joe Joe, Joe Joe, PWAs Of course,
00:07:51.759 sadly, PWA fall short in a number of categories. Outside of our circle and our community
00:07:58.800 and the developer community at large, very few folks actually install PWAs.
00:08:05.599 And the native features that are given to those that are blessed by Apple and Google are often hamstrung or
00:08:13.360 unreliable. push notifications that just stop working or getting signed out of your uh PWA for no apparent reason. It's
00:08:21.759 not unheard of for Apple to clear cookies under the hood and give you no warning from a PWA. Are they doing it on
00:08:27.599 purpose? Do they get 30% from the app store? Maybe.
00:08:34.719 And of course, PWA's lack app store discoverability, but also create an even bigger burden.
00:08:40.959 Not only do you have to get them to your website, you have to tell them how to hit that share button at the bottom and then scroll down to add to home screen,
00:08:48.240 which doesn't sound anything like add app. It sounds like adding shortcut.
00:08:54.160 Have you ever watched someone over their shoulder as they tried to do that? Someone who isn't techsavvy. Don't.
00:09:02.399 Okay. So, if fully native is over here and a PWA is over here, what's in the
00:09:09.519 middle? React Native.
00:09:17.200 React Native has some benefits. You get to share your native code
00:09:23.519 between iOS and Android. So no longer are you building things three times. You're building things twice. Once for
00:09:29.760 web, once for native. You still need all of the, you know, JSON endpoints and
00:09:35.279 stuff. So you're duplicating logic there. Sure. If you have ReactJS on the front end,
00:09:43.519 React Native is a good choice. Do not use Hotwire Native if you're already using React on the front end. It won't
00:09:49.040 give you much benefit. But if you're using HTML over the server and even Hotwire on the web,
00:09:56.480 React Native is just forcing you out of the Rails ecosystem and into the JavaScript one.
00:10:05.920 We want to we want to build mobile apps the same way we build Rails apps. Is
00:10:12.320 that so much to ask? We want to ship instantly. We want to
00:10:19.200 deploy the same way we deploy on the web and have our changes go live across all of our platforms and devices,
00:10:26.560 iOS, Android, and web, desktop and mobile. We want to own our release cycle and not
00:10:34.000 be delayed by the black box that is App Store review.
00:10:39.920 We want to keep building with Rails. We already found the tools we know and
00:10:46.000 love. Let's keep doing it. We don't want to become mobile experts. We don't want to hire teams of mobile experts.
00:10:55.120 We want to build mobile apps the Rails way. But how?
00:11:03.200 Anyone know? Of course. Hot wire native. Hotwire
00:11:08.320 native gives us access to all of this and makes it accessible to Rails developers for the first time.
00:11:14.320 Hotwire native changes the way we think about mobile development. It means that mobile apps are no longer offlimits to
00:11:20.959 Rails developers. It means that even solo team, solo
00:11:26.480 developers or small tiny teams can achieve feature par across web, iOS and
00:11:32.160 Android. So what is it?
00:11:37.519 It's just a web view. That's it. It's just a web view. It's a
00:11:42.800 web view with a little bit of magic around the edges, but that magic does a lot.
00:11:52.800 Because it's rendering the same HTML and CSS that we're using on our server, we don't have to duplicate business logic.
00:12:00.320 We can just send that same stuff over the wire to our web apps that we're sending to the mobile apps
00:12:07.440 since we're deploying from our server. We get the same stack that we're used to and the same infrastructure where we
00:12:14.000 push our code and it goes live in production. Now, just not on the web, but also in our apps.
00:12:22.560 And if we isolate our changes to Rails, we actually can skip App Store
00:12:29.360 review. Yes, we'll have to go through an initial round to get our app in the app stores,
00:12:34.560 but after that, as long as we don't touch the native code, we can continue to ship features and bug fixes that go
00:12:40.639 live immediately without Apple or Google knowing or caring and without breaking the rules.
00:12:48.720 But most importantly, we get to keep building with Rails.
00:12:54.480 We're not splitting our attention. We're not hiring new mobile experts. And we're doing it in a way that makes
00:13:00.320 it maintainable for small teams.
00:13:05.600 So, why am I up on stage today? Some of you may know me as the hotwire
00:13:11.600 native guy. Uh, I've been building hybrid apps for 10 years now with var
00:13:18.160 many variations of hotwire native which we'll talk about in a sec. And I'm here to help you take your Rails app from the
00:13:25.040 browser to the app store. I've shipped 25 of these to production
00:13:30.560 and Google Play and and uh the app store. I'm also one of the maintainers of the Hotwire native library.
00:13:37.760 This QR code is uh to my blog where I talk about Hotwire Native Weekly and have a weekly
00:13:43.360 newsletter to level up your skills with free tutorials and such. Also, I wrote the book which we'll talk
00:13:49.600 about later. So, let's see how Hotwire Native actually works. We know it's a web view.
00:13:55.600 We know we have magic, but let's pull back the covers and see what's going on under the hood.
00:14:04.240 We have our Rails server and we have our browser, our mobile web browser. Think
00:14:09.920 of this as Safari or Chrome on your iOS or Android device. And we send well HTML
00:14:15.600 over the wire hotwire. On a mobile app,
00:14:21.279 we have our navigation bar up at the top and we have our tab bar down at the bottom.
00:14:27.440 But what's in the middle? Well, it's the same thing. Instead of a
00:14:33.279 full web browser, it's an embedded web view, which means that we can send the exact same HTML that we're sending to
00:14:39.519 the server, sorry, to the uh mobile web clients to our apps.
00:14:46.480 If you take away one thing from this talk, it's this. That HTML doesn't need
00:14:51.760 to change. This is the magic of Hotwire Native. So, let's take a look at a real world
00:14:57.600 example here. I have a screenshot of the Ruby
00:15:02.639 Friends app, an app that I built to help keep track of who I meet at conferences.
00:15:07.760 And this is looking at it on uh Safari in responsive mode. So this is what the mobile web version would look like.
00:15:14.639 Here's what that app looks like on iOS with Hotwire Native and Android.
00:15:20.639 You can check this out at rubyfriend.app or download them from Google Play or the App Store.
00:15:27.040 For the native apps, we have what we talked about earlier, the native navigation bar. Up at the top, we have
00:15:34.079 the native title, the native uh three dots overflow button to show a drop-
00:15:39.199 down menu. And down at the bottom, we have a native tab bar to switch between tabs and context. Uh for those in the
00:15:45.680 back, we have my profile, my friends, and notifications down at the bottom.
00:15:50.959 But that's the Chrome. That's lower case. That's the Chrome around it. The
00:15:56.480 magic is inside the web content.
00:16:01.519 Oh, it's the same web content that we're rendering on the web.
00:16:08.720 We have some differences here where we're hiding that uh navigation bar on the web because we already have a native one.
00:16:15.199 But for the most part, we're sending the same exact we can do that with CSS. So, same HTML.
00:16:22.000 This tiny native shell offers a lot of magic for both the developer experience and the end user.
00:16:29.680 Here we have the second tab of the Ruby friends app, a listing of friends.
00:16:35.360 When I tap on a link, we get native transitions built by the
00:16:42.399 OS. Under the hood, Hotwire Native is creating a Turbo.js JS custom adapter
00:16:48.480 that hijacks that link click. And instead of doing an XHR like we do on the web, we're actually pushing a brand
00:16:54.320 new view controller onto the stack using a native navigation controller, which means we get these beautiful animations
00:16:59.920 on iOS. And not to mention these same animations, but platform specific on
00:17:06.240 Android. Your users have come to expect these transitions and they'll feel right at
00:17:12.000 home on hotwire native apps because it's the same thing. The only difference is
00:17:17.199 that the content is fully web. Along the bottom, we have our native tab
00:17:24.079 bar. iOS and Android. These are first party uh controls.
00:17:31.360 They're not it's not some JavaScript Frankenstein of
00:17:36.400 uh React and rendering out custom things. No, these are UI tab bar controller on iOS and bottom navigation
00:17:42.720 bar on Android, which means that they come with all the same features that your users are
00:17:48.799 accustomed to, like having separate stacks for each one. And when you tap on one, it pushes the whole stack back to
00:17:54.400 the beginning. They look like native controls and they feel like native controls because they are.
00:18:01.039 And it also means a hidden benefit is that when a new version of iOS comes out
00:18:06.080 or Android and the design paradigm changes, when would that ever happen? When would we get things like liquid
00:18:11.440 glass? In like a couple days. So this is iOS
00:18:16.559 26. This is what it looked like on iOS 18. The code changes to make it look like
00:18:22.320 this, nothing. All we had to do was rebuild an Xcode 26
00:18:27.679 to get the new features. Because we're just using first party from Apple or Google controls
00:18:35.039 with React Native or other hybrid frameworks where they're building their own controls, it might take weeks or
00:18:40.080 months or years to actually get something that looks and feels like like liquid glass.
00:18:48.240 Okay, so we've built our MVP. have our essentially like the the smallest amount
00:18:53.679 of work we could do to get something that looks good and submitted to the app stores. But you want to go deeper. You
00:18:59.360 want to actually use the native integrations that are built in or that you want to build yourself. You want to level up with custom native
00:19:06.400 integrations. Let's talk about two ways where we can integrate native code into our app. The
00:19:12.000 first is with fully native screens. Here we have two screens. one from the
00:19:18.160 Hey email app and one from that shows a full screen map from the app that you'll
00:19:23.200 actually build in my uh in my book. These offer two different benefits. This
00:19:28.559 home screen on the left means because it's native, the next time the app is
00:19:33.840 launched, it will load instantly. No network request required because we can cache that data. We also get native look
00:19:39.440 and feel and controls platform specific. If you slide across one of these, it'll slide off and go to the left just like
00:19:45.760 you would with any old iOS app. With the map, you avoid that awkward
00:19:52.480 two-finger scroll thing that you get on mobile web, and you get access to a more robust APIs like turnbyturn directions
00:20:00.000 or 3D rendering. Only possible on mobile apps.
00:20:08.240 Native screens are perfect for your highest fidelity and performance screens. If you're building a game that
00:20:14.559 you want to actually render out something complex, use this. You're dealing with maps or native SDKs or your
00:20:20.559 showstopper features, reserve these for the one or two things that set your app apart from the rest of the stuff in the
00:20:27.280 app store or on the web because they incur the highest maintenance cost.
00:20:33.679 Remember, you're stepping out of Hotwire native world now. You're no longer rendering a web view. You're rendering native content and with that comes all
00:20:40.960 of the rigomearroll of JSON endpoints and duplicated business logic and networking and all
00:20:46.559 that's required. But let's see how it works. Let's take a
00:20:52.000 high level approach at building that map but for Android. So we'll start off with a Google map.
00:20:58.480 This is cotlin. This is a jetack compose composable function that renders out well a Google map.
00:21:05.280 We'll wrap that in a map fragment that extends or inherits from hotwire fragment. And it gets our Chrome. We now
00:21:12.960 have our native back button, our native title up at the top, and our status bar at the time and uh networking stuff.
00:21:22.159 From there, we'll decorate this with a hotwire destination deep link URI. A mouthful
00:21:29.360 for saying an ID for this thing. This ID
00:21:36.000 we'll use on the server. So this is a JSON response on our server
00:21:42.960 that sets the path configuration for our apps. It's a way to remotely configure the behavior of our iOS and Android apps
00:21:50.000 dynamically. So it takes an array of rules where we match URIs or sorry URL patterns, URL
00:21:57.360 paths to properties. What this says is anytime a user clicks
00:22:03.360 a link that ends in /map dollar sign for the reg x apply the hotwire fragment map
00:22:09.120 URI. Hotwire native under the hood automatically sees that and doesn't display your web content. It displays
00:22:15.760 this fragment for you and now you're in native world to do whatever you want.
00:22:21.600 The benefit of keeping this on your server is that one day you decide that you don't like the slashmap and you want
00:22:27.280 to call it slashdirection. Well, you just change it on your server and the next time the user launches the app, they'll get that change and then they'll
00:22:33.760 route anything ending in directions to the map fragment. Hotwire Native is responsible for
00:22:39.360 downloading, caching, and applying all of these changes out of the box with one line of configuration.
00:22:47.600 But like I said before, native screens offer the highest
00:22:53.200 fidelity at the highest trade-off. So when the burden of a native screen is too much and you still want to take
00:22:59.039 advantage of your web content but add some native stuff around it, we can
00:23:04.480 reach for bridge components. We can add buttons to the top right of the screen in text or images or maybe a
00:23:11.840 full drop down on Android.
00:23:16.960 These bridge components are the or bridge components are the hotwire native feature that I am most excited about.
00:23:23.919 They're native sprinkles for your app. I like to think about them as stimulus
00:23:30.080 but for native code. The benefit being that they have
00:23:43.600 We'll start off with a plain old stimulus controller. Nothing new here. And when that data controller appears in
00:23:49.360 the DOM, the connect method fires.
00:23:55.039 Now, bridge components offer a third piece of this puzzle, the native side.
00:24:00.400 This would be written in Swift for iOS or Cotlin for Android. When the stimulus controller wants to
00:24:06.320 communicate to the native component, we fire this send in JavaScript. Then the native component replies back with a
00:24:12.799 primitive. The stimulus controller can say, "Hey, what is the user's heart rate right
00:24:19.919 now?" And the native component can do all of the authentication and authorization and API integr uh um
00:24:27.360 fetching to send the heart rate over as a number back to stimulus where you can write it to the DOM or make a fetch
00:24:33.279 request to pop it on your server and persist it. You're back in control on the web.
00:24:40.880 Let's build one of these live. So, we're going to build this add friend button, which you can find in the Ruby
00:24:47.200 friends app, but I think it's a plus sign now. And this button is a link to a
00:24:53.279 new friend path. We'll start with the HTML. We have a add
00:25:00.799 friend button that links to the new friend path link to. We'll add a data controller to that
00:25:08.320 bridge dash dash button. The double dash is to namespace the controller under the
00:25:13.760 bridge directory. We'll generate that controller through the stimulus generator. Bin rails
00:25:19.679 generate stimulus bridge/button which pops this button controller in the bridge subdirectory. Like I mentioned
00:25:25.600 before, I like to keep all of my bridge components separate from my web components, my web controllers, because
00:25:31.679 they serve a different purpose and it's easy to see them all in one spot.
00:25:36.880 Opening up that file, we have our stimulus controller. Nothing about this is Hotwire native or bridge component
00:25:42.880 related yet. So let's replace this import statement and import it now from hotwire native
00:25:49.440 bridge and replace controller with bridge component. This extends or inherits the controller
00:25:56.320 we just had which means we have access to all the underlying APIs of stimulus uh data values classes we can use all of
00:26:03.440 that here but we also have some new stuff like identifying this as the button
00:26:08.960 component for native apps and like I said earlier we can pass
00:26:14.320 messages along to the client by calling this send here we're saying send the
00:26:19.520 connect message to the client pass along a little context. Here we're giving the
00:26:24.799 title of the link that we just uh attached to the DOM and finally a callback.
00:26:31.840 This call back is triggered when the client calls reply to this code will be executed. Which means that when we
00:26:39.120 trigger this call back, the app will just click the add friend link and visit the new friend path. This could be a
00:26:46.240 link, it could be a button, it could execute JavaScript. It's just clicking it under the hood.
00:26:51.760 That wraps up part two. Let's move on to the JavaScript or the um stimulus, excuse me. Let's move on to the iOS side
00:26:57.919 in Swift where we create a button component that inherits from bridge component. We'll identify that with the same button
00:27:05.279 string that we had on stimulus and we'll override the only function required on receive.
00:27:12.559 This gets called every time this send is fired from the web.
00:27:17.600 Here we'll extract that title that we passed along as context as a string and
00:27:22.960 throw it into a UI bar button item. It's a button, but Apple doesn't call it
00:27:28.960 a button. It calls it an item. Doesn't inherit from button. It's a whole another thing. But these things we can put on the top of the screen.
00:27:36.159 We'll attach a call back to that just like we did on the web. This gets fired when the user taps on the button to
00:27:42.720 reply to the connect function to the connect message which will in turn click the HTML link. Finally, we'll throw it
00:27:50.480 on the screen in the upper right via the navigation item.
00:27:57.200 Once we build that, we get our add friend button up in the upright of the
00:28:02.559 my friends screen or page. But the magic of Bridge components isn't actually
00:28:08.320 building them. It's their reusability. Once we have those three pieces built,
00:28:14.480 we can start adding HTML markup to other screens like this
00:28:21.520 and have that button appear on new screens. No native code changes would be required
00:28:28.159 to add this button to a new screen. It means if you deploy your app with the native code included, you could add this
00:28:34.880 button a week, two weeks, month later, and it will just start to appear for all of your users the next time they hit this page. No app store view required
00:28:41.279 either. Bridge components are truly build once,
00:28:47.200 use everywhere over and over again.
00:28:56.000 But they aren't limited to little buttons in the upper right. No, not at all. Bridge components have immense
00:29:02.159 power. We can create native dropdowns powered by UI menu on iOS.
00:29:10.480 We can scan physical paper and use ondevice ML to turn that into a PDF
00:29:17.279 powered by Android and then get that PDF in one file in our stimulus controller
00:29:22.480 to then write to the DOM or send to the server.
00:29:28.240 We can grab the user's notification token on iOS, write that to the server, and then start
00:29:34.320 sending them push notifications through APNS.
00:29:40.080 We can use the builtin barcode scanner on Android that automatically zooms in
00:29:45.600 and adds that colored box around QR codes to show the user what they're scanning and then dismisses and sends
00:29:50.880 the URL to stimulus. or on iOS, we can create a secure app
00:29:57.279 that when you put it in the background, it locks it and puts a white screen over
00:30:02.720 all of the content until you unlock it with biometrics like Face ID or Touch ID.
00:30:11.120 You could also get to the user's location, even in the background with only one prompt instead of three.
00:30:17.679 All of these things are not only only possible with mobile apps and not on the web, but they're abstracted with bridge
00:30:24.240 components that you build it once at the bare minimum level at the most abstracted level and then you can use it
00:30:30.640 anywhere else in your on your app. To get a better feel and to see what
00:30:36.799 these really look like under the hood, you can check out bridgecomponents.dev.
00:30:42.559 I've put together 16 of these that you can copy and paste into your iOS and Android apps along with the stimulus
00:30:48.720 controllers to go with it along with full documentation for HTML about how to
00:30:54.559 add those to your app. This is pretty powerful for Hotwire
00:31:01.520 Native because this is the first time a component-like library has been released and I'm really excited to see this move
00:31:07.600 forward. So, uh, if you have any suggestions or want to submit a pull request, that's all welcome.
00:31:19.120 Okay, we've seen how it works. We've seen what it's good for. We even seen kind of the underlying pinnings of it.
00:31:24.320 Let's take a step back in time and talk about how we got here today. How did we
00:31:30.240 end up at Hotwire Native?
00:31:36.080 Back in 2016, Turbolinks Native was officially launched
00:31:41.519 by Sam Stevenson at his rather famous I can't believe it's not native talk at
00:31:47.440 Railscom Kansas City. He showed this diagram about how complex
00:31:53.360 building for mobile uh with a JS front end with a Rails API backend could be
00:31:59.919 and then blew it all away by building probably the first ever live demo of
00:32:05.919 Turbolinks Native turning Base Camp into an iOS app live on stage.
00:32:13.600 From there we've gotten two major jumps. one in 2020 with the rebrand to Turbo
00:32:20.480 Native which was a rebrand. Uh no new
00:32:25.519 features except that TurbolinksJS became TurboJS. So Turbo Native wanted to
00:32:30.559 follow along but it was still the same clunky uh very verbose API. You had to do a lot
00:32:37.919 of boilerplate to get everything working. It scared a lot of Rails developers away and rightfully so of
00:32:43.440 doing native development. But in 2024, we saw the first release of hotwire
00:32:49.919 native. Jay from 37 signals announced this right
00:32:55.760 before Rails World last year. And this brought web develop mobile app development to Rails developer in an
00:33:02.240 accessible way for the first time ever. What took hundreds of lines of code to
00:33:07.679 build a real working iOS or Android app was now down to like 12 or 15.
00:33:14.159 As an example, here's what it used to take to build a turbo native app on iOS.
00:33:19.840 We'd have our scene delegate, assign a window, pop it out with a custom turbo navigation controller, which subclass
00:33:25.200 navigation controller. On the load, we would hit the local host, make sure to visit the visit options, have two sessions, make sure we popped it on
00:33:31.360 visit, and then make sure that we handled errors, and of course, if the session crashed, we'd reload it. Easy,
00:33:40.880 but not attainable. Also, all of this is boilerplate. If
00:33:47.200 I've built 50 hot turbo native apps, 48 of them had this exact same code in it. There's no need for everyone to be
00:33:53.200 writing this over and over again. With Hotwire Native, that turned into
00:33:58.399 this. This is now all you need to get started on iOS to build a Hotwire Native app. 11
00:34:06.080 lines of code, which you could even shorten if you put the whole private navigator thing on one line. But this
00:34:12.560 means that we can now expect Rails developers to build mobile apps because the Swift required and the Android
00:34:18.800 required, the Cotlin is is no longer overwhelming. Hell, it even looks pretty.
00:34:27.359 Hotwire Native also gave us new documentation. For the first time, Hotwire Native is a first class property
00:34:35.919 or entity in the Hotwire namespace. We now have turbo and stimulus and now native up at the top.
00:34:42.000 these documentation. This doc site is uh brand new custom branding, open source,
00:34:47.200 and also includes guides specific to iOS and Android on how to get started.
00:34:52.720 If you leave today and you're like, where do I go next? native.hotwire.dev.
00:34:58.320 Check out the how the um iOS and Android getting started guide. And that will walk you through taking your Rails
00:35:04.320 server to iOS and Android and really seeing what Hotwire Native can do in 12
00:35:09.599 lines of code.
00:35:15.040 We also introduced new demo apps more powerful than ever. These demo apps
00:35:20.880 include uh native tab bars along the bottom which are now included in hotwire
00:35:26.079 native 1.2. Uh they handle three different bridge components that you could copy paste
00:35:32.240 into your app if you wanted. They also show an example of how to go from native screens and back again to the web
00:35:37.359 context, giving you the full picture of how hotwire native works.
00:35:43.839 These hit a demo server that lives forever at a URL. So when you launch
00:35:49.599 this, you can hit that demo server and not have to worry about running anything locally. But before this demo server was written
00:35:56.320 in ExpressJS, which created some confusion. How do we redirect? Where's
00:36:01.359 the Rails code? What about authentication? Well, with our latest release, we've now
00:36:06.560 migrated that demo server to be a Rails server, which means that you can build out complex interactions like this.
00:36:20.160 And all you need on your server is redirect to. So, what's going on here is that we're
00:36:25.680 presenting a form. I'll wait till it starts. We click our link.
00:36:32.880 It presents from the bottom as a modal. We fill in our information. We have a bridge component in the upper right showing a native submit button that
00:36:39.599 dismisses the screen and pushes a new screen on the stack and we get our flash message for free.
00:36:44.720 Hotwire native is now integrated into Rails in a way where you don't need to do custom routing on the server just to
00:36:50.240 get it to work. Simple redirect twos, flash messages, modals, they all work
00:36:55.280 out of the box. and all of the different scenarios that are now handled.
00:37:06.400 All 15 and more cuz I couldn't fit one slide um are now handled out of the box
00:37:11.760 on hotwire native which means that you don't have to worry about if you're going from a modal to a non-modal to a
00:37:16.880 stack to a pop to a replace. You just pass along the context and hot native will take care of it for you.
00:37:25.920 We're also getting a bunch of new features in Hotwire Native 1.3.
00:37:31.119 Included in that are uh custom animation to fade when the screen replaces itself.
00:37:37.839 Tab bars that load lazily instead of loading them all the time, potentially reloading uh overloading your server.
00:37:43.200 Customizable error handling with native views with just one call back.
00:37:48.480 There's about six PRs open on the Hotwire native iOS repo. Take a look if you want to see more.
00:37:55.280 It might not make it to 1.3, but what I am most excited about is offline caching.
00:38:02.480 This is easily the single most biggest blocker I have when working with
00:38:07.680 clients. They are all in on hot wire native. They cannot wait to get started and then we get to like signing the
00:38:13.599 contract and they're like, "Great. How do we do offline mode?" And I have nothing. Offline caching will give us access on
00:38:20.480 Turbo.js JS where when we visit a page it will store in our cache using a PWA
00:38:26.000 like manifest and then when we visit that page it will load the cached copy if we lose internet access we'll be able
00:38:32.640 to configure it with this taking advantage of a service worker
00:38:39.520 and to get it working for hotware native apps native true
00:38:47.440 so excited here uh Rosa is working on this in PR 1427,
00:38:53.119 but more importantly, she's speaking tomorrow at her talk, bringing offline mode to hotwire with
00:38:59.359 service workers at 1:45 tomorrow. I'll definitely be there and I encourage you to as well.
00:39:10.640 So, we've seen how it works. We've seen all the fun stuff that's coming, all the
00:39:16.320 stuff that we've how we got here. But who's having success with it? Who is
00:39:21.520 taking advantage of Hotwire Native and seeing real business benefits from it?
00:39:26.960 Let's talk about a few folks in the community who are using Hotwire Native to their advantage.
00:39:40.320 optimizes CPAP therapy for folks who suffer with sleep apnea. This iOS app integrates with custom
00:39:48.240 Bluetooth medical devices and also has an integration for Apple Health and Apple Watch. He's combining all of this
00:39:56.240 data that he could only get from a native app and presenting it in beautiful charts and graphs for the
00:40:01.760 user. Charts and graphs that are rendered on the web where it's easier to render those things. Chart kick,
00:40:07.119 chart.js. JS only n only only native apps have access
00:40:13.599 to these integrations. And if he had to build the whole thing in React Native or uh fully native, he
00:40:22.400 would lose all of the feature parity of the you know hundreds of models and controllers that he would get when using
00:40:27.680 hotware native. Native sprinkles only when needed.
00:40:33.440 We have 37 signals, perhaps the poster child for Hotwire Native with their three apps, Base Camp, Hey Email, and
00:40:39.920 Hey Calendar. If you're looking for a really good example of what Hotwire Native is and can do, check out any one
00:40:45.839 of these three apps. They are pushing them to the limits with native home screens, native interactions, and
00:40:51.920 already using a bit of offline caching. But why I bring them up is that they are
00:40:56.960 using these to scale to millions of paying customers.
00:41:02.560 These are their flagship apps. They chose to use Hotwire Native again for Hey Email and Hey Calendar when it came
00:41:08.880 out. They didn't migrate off of it. It's not a pet project. Hot native is
00:41:14.640 scalable to millions of dollars of revenue and beyond.
00:41:25.520 Next up we have Nadia from the story graph. She's built a Goodreads competitor that
00:41:32.640 has access to a bunch of native features like push notifications, uh, native tab bars, and even inapp purchases, which
00:41:39.359 are notoriously the most difficult to build on iOS and Android apps. Hotwire native aside,
00:41:46.079 she's scaled to 4 million registered users. But the most exciting part is that she's
00:41:52.160 doing it as a onewoman dev team.
00:41:57.280 Hotwire Native unlocks mobile apps even for the smallest of dev teams all the
00:42:02.720 way down to a single solo developer.
00:42:10.560 I talked about Ruby a little bit earlier on the way home from Rails comp. I actually built the prototype for this uh
00:42:18.000 way to keep in touch with friends that you meet at conferences, you know, scanning badges and stuff like that. integrated a bunch of native features in
00:42:24.400 the iOS and Android apps like push notifications, QR code scanning, and NFC reading and writing. All with of course
00:42:30.720 Bridge Components all also available in that bridge components.dev library I was talking
00:42:36.400 about. But why I'm talking about this is that this went from idea to in the app store
00:42:43.920 in just two weeks.
00:42:49.760 Hotwire Native is finally bringing back the speed of web development that we're used to to mobile apps.
00:42:56.800 Apps that used to take weeks or months can now take days
00:43:02.240 if you're building with Hotwire Native and just reusing your mobile web screens and even going beyond MVPs and adding
00:43:08.960 native features with pre-made bridge components.
00:43:16.319 These are some other apps built with Hotwire Native by the community
00:43:21.440 available in either Google Play or the App Store. Some developers of these apps are in the audience. I see.
00:43:36.640 With Rails, the web was just the beginning.
00:43:43.520 With Hotwire Native, mobile apps are now
00:43:50.960 with Hotwire Native, we can launch instantly less than 20 lines of Swift or
00:43:56.720 Cotlin to take our Rails app from the browser to the app stores.
00:44:08.880 With Hot Native, we can keep writing Rails. We don't need to become expert
00:44:14.079 mobile app developers. We can ignore the minutia of iOS and Android app development and just stay in our comfort
00:44:20.319 zone. With Hot Native, we can deploy when we
00:44:26.720 want. Ship a feature and instantly see it accessible across all three platforms.
00:44:33.200 No one stopping us from iterating daily or hourly or every minute
00:44:40.400 because we can ship without gatekeepers. No waiting for review, no mystery
00:44:46.800 rejections, just our web content in mobile Chrome.
00:44:53.359 With Hotwire Native, we can finally own the full stack.
00:44:59.760 It's quick enough for a tiny team or even a solo developer to get started, but robust and extensible enough to
00:45:07.119 scale to apps with millions of dollars of revenue.
00:45:14.720 Now, get out there and ship. But before you do, if you need a little
00:45:20.079 bit of help, this is my book. And for everyone in the audience today, a tiny 50% off discount
00:45:28.640 with RailsWorld Joe. That cut off. That's supposed to
00:45:33.760 say Rails World Joe. Um, and yeah, come talk to me if you want to talk about Hotwire Native. Thank you so much. Thank
00:45:40.079 you to the Rails Foundation. It's been a pleasure.
Explore all talks recorded at Rails World 2025
+19