User Experience (UX)

Summarized using AI

Just enough Turbo Native to be dangerous

Joe Masilotti • October 18, 2023 • Amsterdam, Netherlands

In the video titled "Just Enough Turbo Native to Be Dangerous," Joe Masilotti discusses the capabilities of Turbo Native for Rails developers, significantly reducing the complexity of developing hybrid mobile applications. Turbo Native allows developers to transform their Rails codebases into iOS and Android apps by rendering server-side HTML within native mobile environments.

Key Points discussed in the presentation include:
- Turbo Native Overview: It enables developers to quickly create hybrid apps that can access native features like push notifications and geo-fencing without building separate native codebases for iOS and Android.
- Hybrid Development Advantages: Masilotti shares his experience transitioning a large Rails application to mobile, highlighting how much faster and efficient Turbo Native can be compared to traditional native development.
- Development Process: The demonstration includes building a simple hiking journal app from scratch using Turbo Native, showcasing the ease of integrating a Rails server with an iOS client through Xcode.
- User Experience Enhancements: The presentation emphasizes strategies to improve the native feel of applications, such as changing navigation styles and titles dynamically through Rails code, without extensive overhauls.
- Turbo Native Features: Discussion on specific features, like the ability to upgrade certain screens to native functionality when needed, avoiding the constraints of a rigid all-or-nothing approach.
- Advanced Integration: Masilotti illustrates how to incorporate native components for specific use cases, such as adding a fully interactive map by routing to Swift UI when necessary.
- Future Developments: The talk concludes with insights into upcoming tools, including Strata and Turbo Navigator, which aim to enhance the Turbo Native experience further by reducing boilerplate code and streamlining the integration of native components.

Key Takeaways:
- Developers can leverage Turbo Native to validate ideas quickly and launch apps in app stores without extensive delays.
- Writing Rails code remains the primary focus even when developing for mobile, reducing the need for deep native experience.
- Upcoming tools and features like Turbo Navigator will simplify the mobile app development process even further, positioning Turbo Native as an excellent option for Rails developers looking to build mobile applications.

Just enough Turbo Native to be dangerous
Joe Masilotti • October 18, 2023 • Amsterdam, Netherlands

Turbo Native gives Rails developers superpowers, enabling us to launch low maintenance but high-fidelity hybrid apps across multiple platforms. All while keeping the core business logic where it matters - in the Ruby code running on the server.

@joemasilotti, ‘The Turbo Native guy’ will walk you through how to build a Turbo Native iOS app from scratch along with the benefits and pain points that come with it. You’ll dive into ways to make the app feel more native, and how to integrate with native Swift SDKs, and more.

Slides available at: https://masilotti.com/slides/rails-world-2023/

Links:
https://rubyonrails.org/
https://masilotti.com/

#RailsWorld #RubyonRails #rails #turbo #turbonative #ios #swift #xcode

Rails World 2023

00:00:15.400 Hello everyone! My name is Joe Masilotti, and this is just enough Turbo Native to be dangerous. Let's get started.
00:00:20.960 I'm here to tell you that Turbo Native gives Rails developers superpowers. It transforms our Rails codebases into iOS and Android apps by rendering our server-side HTML inside of native mobile apps—ones you can download from the app store and have full access to native SDKs like push notifications and geo-fencing.
00:00:32.520 But I get it; I was a hybrid skeptic too. I once worked for a company that had about a hundred Rails screens, and I was tasked to port that app to both iOS and Android. I was able to do so. I launched iOS and Android apps to both app stores as the only developer. This would have taken years if I had built out both platforms fully native. Instead, it took months. I had just picked up Turbo Native at the time. Imagine what you could do if you already know the framework.
00:00:54.560 Before this, I was an iOS developer. I built real apps with native SDKs. Thank you very much! But my world changed that day, and since then, I have been all in on Turbo Native. I write tutorials, create YouTube videos on the subject, and I'm even writing a book. I'm one of the two maintainers of the Turbo iOS library, and I've launched dozens of apps to the App Store and Google Play stores.
00:01:04.760 I live and breathe Turbo Native. But you’re probably saying that hybrid will never be as good as native, and you know what? You're right; it won't. Nothing will beat the absolute fluidity of gliding your finger across the screen with GPU-accelerated animations that would make Craig Federighi proud.
00:01:18.920 However, today I'm here to tell you that your web UI is good enough. Most screens today render data, and native controls won't help much. Turbo Native gives you a shortcut. It lets you launch into the App Store today with the resources you have instead of waiting months or years to deploy something and test your hypothesis. You can validate those resources now, and Turbo Native gets you there today.
00:01:41.480 And for those times when Turbo Native isn't good enough—when you want to go to higher fidelity—we have answers for that, and we'll talk about those a little bit later. But why choose Turbo Native over the myriad of other hybrid frameworks? Well, for Rails developers, it offers true write once, deploy anywhere.
00:01:54.680 You push your code with new changes to a mobile web view on your mobile screens, and your iOS and Android apps get those changes for free—no app store approval, no resubmitting, no rebuilding, no rebundling. If it's on the server, it's in your app. This allows you to maximize what you do best—writing Rails code. We’re not native developers; we don’t want to be native developers. We want to write Rails code.
00:02:09.240 Turbo Native enables this. It allows you to skip expensive development cycles of building out an API and then building it out on iOS and Android by writing it once and deploying it everywhere. When you really need to, you can upgrade specific screens to native, when you need access to native SDKs like the accelerometer, GPS, or push notifications.
00:02:14.040 But it lets you do it on a case-by-case basis when you're ready—it's not all or nothing. You can go web now, feature complete, and upgrade individual screens. These are just some of the apps that are in the App Store right now built with Turbo Native. Some of these might look familiar, as I've worked on about half of these.
00:02:42.440 Today, we're going to do four things: we're going to build an app from scratch, learn how to make it feel a little bit more native, learn how to integrate Swift UI screens to enhance fidelity, and finally, we'll preview some upcoming features for Turbo Native that I am very excited to share.
00:03:02.440 Today's talk is going to focus on iOS for the most part. I only have 30 minutes, but everything I talk about is applicable to both iOS and Android platforms. We're going to build the demo app, an iOS app that keeps track of your hiking, essentially functioning as a hiking journal app. We have an index page, a show page, and some structural elements in the output that we'll discuss later.
00:03:27.600 Now, this is the Rails server that's running locally. It’s a Rails app that I can click through. I can get a show page, I can edit; it’s a CRUD app. It's nothing too exciting, but we're going to build this into a Turbo Native iOS app. So, we'll open up Xcode and create a brand new project.
00:03:44.840 It's going to be an iOS app, and we're going to call it 'Demo.' We’re going to throw it on our desktop. This opens to the project explorer; we won't worry about this right now. We will focus entirely on the SceneDelegate.
00:04:00.440 This function happens when the app is launched. This is called 'sceneWillConnectTo.' We’re going to delete everything else in this file and only focus on this for now.
00:04:20.560 The first thing we’re going to do is create a navigation controller. A navigation controller is a basic building block for Turbo Native and on UIKit or what you use to build apps with on iOS. You’re probably familiar with it in the Contacts app, for example. This is a navigation controller; it’s a stack of screens where I can navigate forward and back, going deeper into multiple layers.
00:04:43.440 What we're going to do with that is set it as our window's root view controller. This means that when the app launches, we'll launch right into this navigation controller. Let's command-R to run it, and we’ll see an empty white screen, but that means it's working.
00:05:10.360 Next, we’re going to add the Turbo Native file package. Dependencies are going to hook us into the world of Swift Package Manager. It’s like gems for iOS. I'm going to add Turbo iOS from github.com/hotwired/turbo-ios. Then, we’ll add it to our demo target. If I pull open the explorer on the left—sorry this is a little small—but here is the code.
00:05:34.080 As if we were to do a bundle open, we can start logging into all of Turbo iOS right there. Once we have it, we’ll import it, giving us access to the great features of Turbo Native. One of the most critical ones is something called a session. A session manages the complex stuff; it manages HTML rendering, taking care of the screenshotting and snapshotting.
00:06:02.880 We'll create one and return it here using a lazy variable to only create it when we access it for the first time. Remember, this is when the app launches. When the app fires up, we want to visit our localhost.
00:06:32.760 Up at the top here, we’re going to create a global variable called rootURL; we’re going to point that to our localhost. Notice how it's outside of the class definition; this means we can access it across our app.
00:06:52.360 Down here, we're going to create a private function called 'visit.' This function is going to be called every time you want to make a visit to a new page in our app. The parameter it takes will be called 'proposal.' A visit proposal is how Turbo Native wraps every single visit to your screens.
00:07:18.760 We should command-click into this to see its definition. It has three properties: a URL, options, and properties. A URL is self-explanatory; it's the URL of the page you're visiting. Options are more advanced stuff for navigating, which is somewhat out of scope for today, and properties is a hash.
00:07:44.760 This hash will be important later when we start routing URLs. When we want to visit a page, we have to do three things: we want to create the screen, push the screen or display it onto our navigation stack, and finally, we want to visit the page or render the HTML.
00:08:06.520 So let's do those three things in three lines of code. First, we’ll create a visitable view controller. A visitable view controller is the core of Turbo Native rendering; this expects a URL that we can pull from the proposal. It manages our web view session that gets passed across the screen as we navigate deeper into our Turbo Native app.
00:08:31.440 It also manages snapshotting and caching. We can now push this visitable, passing in 'true' to animate it on every plus one screen to ensure we get that nice animation that makes it feel native. Finally, we take our session and visit the visitable. We’ll pass in the options from the visit proposal, ensuring that future visits will use all of the good stuff that that proposal already had.
00:09:04.040 Finally, up here, back in where our app launches, we’re going to create a proposal by passing in the URL of our root URL, which we defined earlier.
00:09:21.960 We’ll pass this off to 'visit,' and then command-R to run our app. If all goes well, we should see a spinner for a quick second and load our local host index page. We now have a Turbo Native app displaying our content with just 33 lines of code.
00:09:34.960 Now we have a problem: we can't click links. What's happening here is that the session is trying to tell our app to do something, but we haven't instructed it where to send those messages.
00:09:46.840 We're going to set up a delegate that says when a new visit is clicked, route it to me as the scene delegate. However, the scene delegate doesn’t conform; we need to tell Xcode we're strongly typed. Remember, we’re in Swift, not Ruby; we must tell Xcode that we listen to those methods.
00:10:09.680 Xcode will complain again, saying it understands, but you don't implement the three methods you're required to implement, so let's implement those now. 'Did propose visit' essentially happens when a link is clicked; we get that visit proposal as expected.
00:10:28.840 ‘Did fail to visit' is what happens when an error occurs, and finally, 'Web view process did terminate' is an edge case when the web view dies outside of the context of our iOS app because it runs in a separate process. Let's start with that; it's easy as we can just reload the session.
00:10:56.560 This will ensure that our session reload can have a brand new context to work with and our page reloads. For 'did fail,' we will just print it out for now with the localized description of the error.
00:11:20.720 But this is the important one: when a link is clicked, we want to visit a page. Luckily, we have that function already right there; we can pass in 'visit' and pass in the proposal. We run this, and now we’re listening to that callback through the delegate, allowing navigation across our app.
00:11:45.440 We now have about 50 lines of code here for what would be the absolute bare minimum Turbo Native app that you could build off of a Rails app that’s running all the standards. Let's go back to the slides to discuss ways we can make this feel a little more native.
00:12:08.520 This is what we're working with, right? Zooming in on the top—we have a hiking journal app. The top middle is the page title; the top left is the back button. The title of that page is already 'Hiking Journal,' so we get it again, and then we have a nav bar rendered on the web. Let's deal with that one first.
00:12:31.440 We already have a native nav bar, so there's no need to have the web one as well. To do that, we’re going to render Turbo Native-specific content. However, we first have to identify the app as a Turbo Native app. To do that, we’ll set a user agent.
00:12:53.920 Here’s the session that we had before where we set the delegate. We’re going to create a web view configuration and pass in an application name for the user agent, calling it Turbo Native iOS. We’ll pass that into the session and configure it; with every request, we’ll get Turbo Native iOS appended to the user agent.
00:13:18.680 Then on Rails, if you’re using Turbo Rails, you get a helper for free. It’s buried deep in app controllers, and I’d recommend checking out that class. It checks for Turbo Native in the string and returns true.
00:13:39.440 To hide the nav bar in our app, we have our nav bar partial. Let's just render it unless it’s done hidden. Right? No problem, but that kind of sits wrong with me; maintaining a conditional every time you want to render Turbo Native content is going to complicate things.
00:13:50.960 We’re now sending different HTML over to different user agents. This means we need to include our user agent somehow in our cache key, which might double our cache size—no thanks.
00:14:12.280 Instead, we’re going to render Turbo Native-specific styles, ones that only apply to the Turbo Native app. We're going to create a new class called 'Turbo Native Hidden,' which is simply display: hidden; and important here.
00:14:38.800 This is a new stylesheet—native.css. This isn’t our application CSS; it's a new one, and we’ll include that in our application layout. Now, if you're rendering a Turbo Native app, this is the only conditional we need in our view layer. We’re sending down additional CSS with the same HTML, which overrides and customizes our base styles.
00:15:01.720 Fun things can happen, like changing how it looks on iOS or Android. We went from three hiking journals to two: one down and one to go. The key takeaway is that we rendered Turbo Native-specific content with only our Rails code. We deploy that change once—where we set the user agent—then we can change styles at will without doing App Store updates.
00:15:24.960 Let’s get rid of this one; the title is automatically set from the title HTML tag, buried somewhere in Turbo Native's visitable view controller. Our application layout now looks like this: we set the title to 'Hiking Journal.' We’re going to use a Rails feature called content_for.
00:15:46.760 It pulls the value from the title, and if not, defaults back to the static string in our show view for hikes. Here’s where we render out the content for; we’ve now set the title dynamically for every single page for each individual hike. This works great for your HTML as well, now you have an actual useful title displayed in the nav bar.
00:16:16.000 We can go one step further by hiding the H1 on Turbo Native apps, as we're already rendering it at the top. We don't need to render it again. Just like that, we now have a single 'Hiking Journal' displayed at the top left. While I understand these elements are very Bootstrap-like and might look ugly, we’ll clean them up later.
00:16:39.760 We’ve rendered native titles now with only Ruby code. We've deployed that change—the change that sets the user agent—and we can now do these native titles and change native elements using only our Ruby code.
00:17:05.040 Traditionally, a difficult part of native development is image uploads, which normally requires a lot of code. Here’s what we're going to do: step one, we’re going to add a file field backed by Active Storage in my app. You could back it by whatever you want in yours.
00:17:26.440 The file field is the important thing, and step two… that’s it! Turbon Native handles this for us. We're leveraging the fact that we’re a web view, which already has a ton of this functionality built-in. Image uploading is just one of those aspects.
00:17:51.760 We get date pickers, time pickers, and decimal inputs—this is common on iOS and Android. There are no new code changes needed, just the correct HTML markup and the right input fields. We should take advantage of the fact that Apple and Google have spent years perfecting these UIs.
00:18:11.960 Don't reinvent the wheel unless you absolutely need to. Your logic stays on the server, and you keep writing Ruby code—doing what you do best.
00:18:29.520 Now, let’s take a step further and discuss advanced Turbo Native screens. This is a local sandwich place in Portland that I recommend checking out if you're ever in town. As I was scrolling through their page, I noticed an embedded Google map.
00:18:46.640 You know how it goes: your finger gets stuck, you start scrolling the map, and accidentally click it. Then Google prompts you to use their app. This is a perfect example to upgrade to native on Turbo Native. Instead of rendering a screenshot of a map, we can use Swift UI to get a fully scrollable, zoomable Native interface.
00:19:12.920 So how do we do that? First, we need to determine when to route to this screen. This is the URL we will use; anything that says 'hikes/id/map' will want to trigger rendering the native map.
00:19:39.720 However, we need to remain flexible—we don’t want to hardcode this into our iOS app. If we ever wanted to render that map for, say, SL Maps ID, we’d need to redeploy to the App Store. The path configuration can help us keep this configuration on our server.
00:20:03.360 This is a server-hosted JSON file that looks like this. It has two keys: settings and rules. Today, we’ll discuss rules. These rules match when you click a URL, and you get this pattern that applies properties.
00:20:31.040 Here is the rule we're building, which is like a routes.rb for your iOS app. Every time we match this pattern, where 'hikes' is a number, we will apply these properties. Properties are then applied to the visit proposal.
00:20:59.960 This is a hash, and for those in the back who can’t see, I’m showing a visit proposal with properties rendered out, which has string keys. We will route this file in our Rails routing.
00:21:22.240 The configurations for iOS V1 will route to a controller. I like to version these to ensure we don't lose backward compatibility. If we ever add a new native feature, we want to ensure we don’t break old apps.
00:21:42.560 Here’s what that looks like copied over to an iOS controller. We have our session initialized with our root URL. Our session takes in a path configuration, and we can give it an array of sources, pointing to our server URL, appending our path.
00:22:00.920 As soon as the session is initialized lazily, Turbo Native will fetch, parse, and apply all these properties for us. It will also cache the path configuration for future launches, so you’re free to update this without worrying about doing any networking requests in the app—Turbo handles it all.
00:22:24.560 Now we need to actually show the screen. This is what we had before in our one-two-three method for visiting. We can throw all that in an else statement; that’s our standard web view process.
00:22:43.840 Our if statement will check whether the properties on the proposal have a string and whether the controller is named 'map.' This will be abstracted.
00:23:04.160 Every time we match this condition, we’ll render our map view controller. This is just one example of how configurable this is; you can imagine this if statement growing large, but each can be a different native feature routed from your server.
00:23:29.600 The path configuration allows us to keep our logic on the server, disconnecting us from App Store and Google Play releases. It also ensures backward compatibility by versioning, so old apps aren’t broken.
00:23:48.640 Now let’s talk about what's next for Turbo Native. I bet everyone knows what’s on the next slide: Strata! I’m very excited about this. Yesterday, Jay did an amazing job discussing it.
00:24:04.960 I won't dive too deeply, but at a high level, you can build native components with Strata—not full screens, but native components. It bridges the gap between web and native, ensuring that you keep your HTML powering your native components.
00:24:18.960 It's not some fancy JavaScript thing or a JSON endpoint; you write your HTML, markup it, and get native components from it. What's important for this presentation is that we can discard those ugly components.
00:24:35.920 With Strata, we can throw a button in the top right, an overflow button, and when you click that, we get the map and edit buttons with our little icons from SF Symbols.
00:24:59.760 This makes it so easy that it would take a lot of code to do before Strata, and the best part is that when I click “edit,” it triggers that action under the hood. Whatever is happening behind that button press, be it a new GET, a new POST request—it just works.
00:25:21.120 I'm also super excited about Turbo Navigator, a package I’ve been working on that simplifies getting started with Turbo iOS. As you saw, Turbo iOS is quick to get started, but anything beyond that requires a lot of boilerplate.
00:25:42.480 There are countless flows you have to manage. Turbo Navigator handles 15 of those for you. It can manage modal navigation, presenting something from the bottom of the screen and dismissing it.
00:26:05.680 It supports deep navigation into the stack, such as going one, two screens deep, and clearing everything, like when you log someone in or out. It also manages basic navigation of pushing and popping view controllers off the stack.
00:26:27.520 There are numerous other features, and I recommend checking out the GitHub for it. Turbo Navigator handles 15 different flows and minimizes about 100 lines of boilerplate. I use this for all my client apps, and the good news is it's being upstreamed into Turbo iOS very soon.
00:26:48.480 We finished about 90% of the work, and I promise it's coming soon. If you want an early look, check it out on GitHub, give it a watch, give it a star, and we’ll announce the upstream merge when it happens.
00:27:09.960 We talked a lot about building Turbo Native apps, but let’s discuss your Turbo Native apps. I help folks launch Turbo Native apps through coding, consulting, workshops, training, and advisory services.
00:27:23.840 I’d love to help you get yours into the App Store or Google Play Store. If you're interested in Turbo Native or want to discuss if it's right for your company, come say hi; I’d love to chat!
00:27:35.360 Again, I'm Joe Masilotti, and if you have any questions about this or Turbo Native, here’s my email. For more insights, I have a weekly newsletter and blog about Turbo Native regularly at masilotti.com.
00:27:37.430 Thank you!
Explore all talks recorded at Rails World 2023
+25