Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Just enough Turbo Native to be dangerous

Just enough Turbo Native to be dangerous

Turbo Native gives Rails developers superpowers. It enables 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.

The framework renders your existing mobile web HTML inside of a native “shell.” But getting started isn’t easy. You still have to write some native Swift code in Xcode. And there isn’t much documentation available about the right way to do things.

During this talk I’ll walk you through all of that. You’ll learn how to build a Turbo Native iOS app from scratch along with the benefits and pain points that come with it. We’ll dive into ways to make the app feel more native, how to integrate with native Swift SDKs, and more.

Joe Masilotti

October 10, 2023
Tweet

Other Decks in Programming

Transcript

  1. I was a hybrid skeptic Launched iOS and Android apps

    as the only developer. What should have taken years 5.3
  2. I was a hybrid skeptic Launched iOS and Android apps

    as the only developer. What should have taken years took months. 5.4
  3. I was a hybrid skeptic Launched iOS and Android apps

    as the only developer. What should have taken years took months. My world changed that day. 5.5
  4. 7

  5. Why Turbo Native? Write once, deploy everywhere Maximize Rails skills

    Skip expensive development cycles Upgrade screens when needed 9.4
  6. 10

  7. Today we will… 1. Build an app from scratch 2.

    Make it feel more native 11.2
  8. Today we will… 1. Build an app from scratch 2.

    Make it feel more native 3. Integrate native screens 11.3
  9. Today we will… 1. Build an app from scratch 2.

    Make it feel more native 3. Integrate native screens 4. Preview new features 11.4
  10. 13

  11. 16

  12. 17

  13. 18

  14. 19

  15. Set the user agent private lazy var session { let

    session Session() session.delegate self return session }() 1 = 2 = 3 = 4 5 21.1
  16. Set the user agent let config WKWebViewConfiguration() config.applicationNameForUserAgent "Turbo Native

    iOS" private lazy var session { 1 = 2 = 3 = 4 let session Session(webViewConfiguration: config) 5 = session.delegate self 6 = return session 7 }() 8 22
  17. Set the user agent let config WKWebViewConfiguration() config.applicationNameForUserAgent "Turbo Native

    iOS" private lazy var session { 1 = 2 = 3 = 4 let session Session(webViewConfiguration: config) 5 = session.delegate self 6 = return session 7 }() 8 let session Session(webViewConfiguration: config) private lazy var session { 1 = let config WKWebViewConfiguration() 2 = config.applicationNameForUserAgent "Turbo Native iOS" 3 = 4 5 = session.delegate self 6 = return session 7 }() 8 22.1
  18. Hide the nav bar <%# app/views/shared/_navbar.html.erb %> <nav class="navbar"> <%=

    link_to "Hiking journal", root_path %> </nav> 1 2 3 4 5 24.1
  19. Hide the nav bar <% unless turbo_native_app? %> <% end

    %> <%# app/views/shared/_navbar.html.erb %> 1 2 3 <nav class="navbar"> 4 <%= link_to "Hiking journal", root_path %> 5 </nav> 6 7 25
  20. Turbo Native styles <nav class="navbar turbo-native-hidden"> <%# app/views/shared/_navbar.html.erb %> 1

    2 3 <%= link_to "Home", root_path %> 4 </nav> 5 /* app/assets/stylesheets/native.css */ .turbo-native-hidden { display: hidden !important; } 1 2 3 4 5 29
  21. Turbo Native styles <nav class="navbar turbo-native-hidden"> <%# app/views/shared/_navbar.html.erb %> 1

    2 3 <%= link_to "Home", root_path %> 4 </nav> 5 /* app/assets/stylesheets/native.css */ .turbo-native-hidden { display: hidden !important; } 1 2 3 4 5 <%# app/views/layouts/application.html.erb %> <head> <%= stylesheet_include_tag "application" %> </head> 1 2 3 4 5 30
  22. Turbo Native styles <nav class="navbar turbo-native-hidden"> <%# app/views/shared/_navbar.html.erb %> 1

    2 3 <%= link_to "Home", root_path %> 4 </nav> 5 /* app/assets/stylesheets/native.css */ .turbo-native-hidden { display: hidden !important; } 1 2 3 4 5 <%= stylesheet_include_tag "native" if turbo_native_app? %> <%# app/views/layouts/application.html.erb %> 1 2 <head> 3 <%= stylesheet_include_tag "application" %> 4 5 </head> 6 31
  23. 32

  24. 34

  25. 35

  26. Native titles <title><%= content_for(:title) || "Hiking Journal" %></title> <%# app/views/layouts/application.html.erb

    %> 1 2 <head> 3 4 </head> 5 <%# app/views/hikes/show.html.erb %> <h1><%= @hike.name %></h1> 1 2 3 38
  27. Native titles <title><%= content_for(:title) || "Hiking Journal" %></title> <%# app/views/layouts/application.html.erb

    %> 1 2 <head> 3 4 </head> 5 <% content_for :title, @hike.name %> <%# app/views/hikes/show.html.erb %> 1 2 3 4 <h1><%= @hike.name %></h1> 5 39
  28. Native titles <title><%= content_for(:title) || "Hiking Journal" %></title> <%# app/views/layouts/application.html.erb

    %> 1 2 <head> 3 4 </head> 5 <h1 class="turbo-native-hidden"><%= @hike.name %></h1> <%# app/views/hikes/show.html.erb %> 1 2 <% content_for :title, @hike.name %> 3 4 5 40
  29. 41

  30. 44

  31. 45

  32. 48

  33. 49

  34. 52

  35. 53

  36. 54

  37. 55

  38. Path configuration Server-hosted JSON file to remotely configure app {

    "patterns": ["/hikes/[0-9]+/map"], "properties": { "controller": "map" } } { 1 "settings": {}, 2 "rules": [ 3 4 5 6 7 8 9 ] 10 } 11 58
  39. Path configuration Server-hosted JSON file to remotely configure app {

    "patterns": ["/hikes/[0-9]+/map"], "properties": { "controller": "map" } } { 1 "settings": {}, 2 "rules": [ 3 4 5 6 7 8 9 ] 10 } 11 "patterns": ["/hikes/[0-9]+/map"], { 1 "settings": {}, 2 "rules": [ 3 { 4 5 "properties": { 6 "controller": "map" 7 } 8 } 9 ] 10 } 11 58.1
  40. Path configuration Server-hosted JSON file to remotely configure app {

    "patterns": ["/hikes/[0-9]+/map"], "properties": { "controller": "map" } } { 1 "settings": {}, 2 "rules": [ 3 4 5 6 7 8 9 ] 10 } 11 "patterns": ["/hikes/[0-9]+/map"], { 1 "settings": {}, 2 "rules": [ 3 { 4 5 "properties": { 6 "controller": "map" 7 } 8 } 9 ] 10 } 11 "properties": { "controller": "map" } { 1 "settings": {}, 2 "rules": [ 3 { 4 "patterns": ["/hikes/[0-9]+/map"], 5 6 7 8 } 9 ] 10 } 11 58.2
  41. Path configuration Server-hosted JSON file to remotely configure app "properties":

    { "controller": "map" } { 1 "settings": {}, 2 "rules": [ 3 { 4 "patterns": ["/hikes/[0-9]+/map"], 5 6 7 8 } 9 ] 10 } 11 struct VisitProposal { let url: URL let options: VisitOptions let properties: PathProperties } 1 2 3 4 5 59
  42. Path configuration Server-hosted JSON file to remotely configure app "properties":

    { "controller": "map" } { 1 "settings": {}, 2 "rules": [ 3 { 4 "patterns": ["/hikes/[0-9]+/map"], 5 6 7 8 } 9 ] 10 } 11 let properties: [String: AnyHashable] struct VisitProposal { 1 let url: URL 2 let options: VisitOptions 3 4 } 5 60
  43. Path configuration # app/controllers/configurations/ios_controller.rb class ConfigurationsController < ApplicationController def ios_v1

    render json: { settings: {}, rules: [ { patterns: ["/hikes/[0-9]+/map"], properties: { controller: "map" } } ] } end end 62
  44. Path configuration .server(rootURL.appending(path: "configurations/ios-v1.json")) let rootURL URL(string: "http://localhost:3000") 1 =

    ! 2 let session Session() 3 = session.pathConfiguration PathConfiguration(sources: [ 4 = 5 ]) 6 65
  45. Path configuration .server(rootURL.appending(path: "configurations/ios-v1.json")) let rootURL URL(string: "http://localhost:3000") 1 =

    ! 2 let session Session() 3 = session.pathConfiguration PathConfiguration(sources: [ 4 = 5 ]) 6 Rails.application.routes.draw do get "/configurations/ios-v1", to: "configurations#ios_v1" end 66
  46. Route the screen func visit(proposal: VisitProposal) { let visitable VisitableViewController(url:

    proposal.url) navigationController.pushViewController(visitable) session.visit(visitable, options: proposal.options) } 1 2 = 3 4 5 67
  47. Route the screen if /* ... */ { } else

    { } func visit(proposal: VisitProposal) { 1 2 3 4 let visitable VisitableViewController(url: proposal.url) 5 = navigationController.pushViewController(visitable) 6 session.visit(visitable, options: proposal.options) 7 8 } 9 68
  48. Route the screen if proposal.properties["controller"] as? String "map" { func

    visit(proposal: VisitProposal) { 1 2 == 3 } else { 4 let visitable VisitableViewController(url: proposal.url) 5 = navigationController.pushViewController(visitable) 6 session.visit(visitable, options: proposal.options) 7 } 8 } 9 69
  49. Route the screen if proposal.properties["controller"] as? String "map" { func

    visit(proposal: VisitProposal) { 1 2 == 3 } else { 4 let visitable VisitableViewController(url: proposal.url) 5 = navigationController.pushViewController(visitable) 6 session.visit(visitable, options: proposal.options) 7 } 8 } 9 "properties": { "controller": "map" } { 1 "patterns": ["/hikes/[0-9]+/map"], 2 3 4 5 } 6 70
  50. Route the screen let controller MapViewController(url: proposal.url) navigationController.pushViewController(controller) func visit(proposal:

    VisitProposal) { 1 if proposal.properties["controller"] as? String "map" { 2 == 3 = 4 } else { 5 let visitable VisitableViewController(url: proposal.url) 6 = navigationController.pushViewController(visitable) 7 session.visit(visitable, options: proposal.options) 8 } 9 } 10 71
  51. Path configuration Keeps logic on the server Disconnects from App

    Store releases Grants backwards compatibility 72.3
  52. 75

  53. 76

  54. 77

  55. 79

  56. Turbo Navigator Handles 15+ navigation flows Removes 100+ lines of

    boilerplate Upstreaming into turbo-ios soon! 80.2
  57. Turbo Navigator Handles 15+ navigation flows Removes 100+ lines of

    boilerplate Upstreaming into turbo-ios soon! github.com/joemasilotti/TurboNavigator 80.3
  58. Your Turbo Native app I help folks launch Turbo Native

    apps. And I’d love to do the same for you. 81.2
  59. Your Turbo Native app I help folks launch Turbo Native

    apps. And I’d love to do the same for you. Say hi and see if Turbo Native is right for you. 81.3
  60. Joe Masilotti Email me a question – [email protected] Learn more

    Turbo Native – Masilotti.com Thank you! 82.3