Slide 1

Slide 1 text

Hotwire Native Tony Messias Hotwire Native fo L ve D s

Slide 2

Slide 2 text

Agenda ➔ What's new in Turbo 8 ➔ Hotwire Starter Kit for Laravel ➔ Intro to Hotwire Native ◆ Getting Started ◆ Navigation ◆ Web Forms ◆ Our First Bridge Component ◆ Native Screens

Slide 3

Slide 3 text

but first…

Slide 4

Slide 4 text

Hotwire in 5 mins-is

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Turbo

Slide 7

Slide 7 text

Turbo Drive

Slide 8

Slide 8 text

Turbo Frames (...)

Slide 9

Slide 9 text

Turbo Streams (...)

Slide 10

Slide 10 text

What's new in Turbo 8

Slide 11

Slide 11 text

Page Refreshes

Slide 12

Slide 12 text

View Transitions

Slide 13

Slide 13 text

Prefetching Links on Hover

Slide 14

Slide 14 text

Stimulus

Slide 15

Slide 15 text

Stimulus 101

Slide 16

Slide 16 text

Stimulus 101
PIN: Copy to Clipboard

Slide 17

Slide 17 text

Stimulus 101 import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = [ "source" ] copy() { navigator.clipboard.writeText(this.sourceTarget.value) } }

Slide 18

Slide 18 text

Stimulus 101
PIN: Copy to Clipboard
PIN: Copy to Clipboard

Slide 19

Slide 19 text

Stimulus 101

Slide 20

Slide 20 text

Stimulus 101

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

it was well received… but there were some folks confused…

Slide 23

Slide 23 text

Hotwire Native

Slide 24

Slide 24 text

Getting Started 1. Create Project 2. Add Dependency 3. Set Permissions 4. Replace Activity

Slide 25

Slide 25 text

Getting Started 1. Create Project 2. Add Dependency 3. Set Permissions 4. Replace Activity class MainActivity : HotwireActivity() { override fun onCreate(savedInstanceState : Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState ) setContentView(R.layout.activity_main) findViewById (R.id.main_nav_host) .applyDefaultImeWindowInsets() } override fun navigatorConfigurations() = listOf( NavigatorConfiguration( name = "main", startLocation = "http://10.0.2.2:8000" , navigatorHostId = R.id.main_nav_host ) ) }

Slide 26

Slide 26 text

Getting Started 1. Create Project 2. Add Dependency 3. Add Scene Delegate

Slide 27

Slide 27 text

Getting Started 1. Create Project 2. Add Dependency 3. Add Scene Delegate let rootURL = URL(string: "http://localhost:8000")! class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? private let navigator = Navigator() func scene(_ scene: UIScene, ...) { window?.rootViewController = navigator.rootVi... navigator.route(rootURL) } }

Slide 28

Slide 28 text

Navigation Web Native

Slide 29

Slide 29 text

Path Configuration { "settings": {}, "rules": [ { "patterns": [".*"], "properties": { "context": "default", "uri": "hotwire://fragment/web", "pull_to_refresh_enabled": true } }, { "patterns": ["/create$", "/edit$", "/delete$"], "properties": { "context": "modal", "uri": "hotwire://fragment/web/modal/sheet", "pull_to_refresh_enabled": false }

Slide 30

Slide 30 text

Native Integrations ✨

Slide 31

Slide 31 text

Accessing Camera

Slide 32

Slide 32 text

Date Picker

Slide 33

Slide 33 text

Time Picker

Slide 34

Slide 34 text

Web Forms

Slide 35

Slide 35 text

Bridge Components ✨

Slide 36

Slide 36 text

Bridge Components ✨ ➔ Component-based framework ➔ All about messaging: The Native App doesn't know anything about the markup on the page ➔ Native app should opt-in for native components

Slide 37

Slide 37 text

Bridge Components

Slide 38

Slide 38 text

Bridge Components

Slide 39

Slide 39 text

1) php artisan stimulus:make \ bridge/form --bridge=form Bridge Components

Slide 40

Slide 40 text

2) import { BridgeComponent, BridgeElement } from "@hotwired/hotwire-native-bridge" export default class extends BridgeComponent { static component = "form" static targets = ["submit"] connect() { super.connect() const element = new BridgeElement(this.submitTarget) const title = element.bridgeAttribute("title") this.send("connect", { title }, () => { this.submitTarget.click() }) } } Bridge Components

Slide 41

Slide 41 text

3)

Slide 42

Slide 42 text

4) [data-bridge-components~="form"] [data-controller~="bridge--form"] [type="submit"] { display: none; } Bridge Components

Slide 43

Slide 43 text

5) class FormComponent( name: String, private val delegate: BridgeDelegate ) : BridgeComponent(name, delegate) { override fun onReceive(message: Message) { // Handle incoming messages based on the message `event`. when (message.event) { "connect" -> handleConnectEvent(message) else -> Log.w("FormComponent", "Unknown event for: $message") } } // ... } Bridge Components

Slide 44

Slide 44 text

5) class FormComponent( name: String, private val delegate: BridgeDelegate ) : BridgeComponent(name, delegate) { private fun handleConnectEvent(message: Message) { val data = message.data() ?: return // Write native code to add create button and add to toolbar… btn.formSubmit.apply { text = data.title setOnClickListener { replyTo("connect") } } } } Bridge Components

Slide 45

Slide 45 text

6) Hotwire.registerBridgeComponents( BridgeComponentFactory("form", ::FormComponent), ); Bridge Components

Slide 46

Slide 46 text

Bridge Components

Slide 47

Slide 47 text

Bridge Components

Slide 48

Slide 48 text

Bridge Components

Slide 49

Slide 49 text

Native Screens ✨

Slide 50

Slide 50 text

{ "settings": {}, "rules": [ { "patterns": ["/numbers$"], "properties": { "uri": "hotwire://fragment/numbers", "title": "Numbers" } } ] } @HotwireDestinationDeepLink (uri = "hotwire://fragment/numbers" ) class NumbersFragment : HotwireFragment() { // ... } 1) 2) Bridge Components 3) Hotwire.registerFragmentDestinations( WebFragment:: class, NumbersFragment:: class, )

Slide 51

Slide 51 text

Demo ➔ Navigation ➔ Web Forms ➔ Lifting Form Submits ➔ Native Toast Messages ➔ Native Screens

Slide 52

Slide 52 text

Advantages ➔ Small teams ➔ Very little native code needed (at least initially) ➔ Low maintenance ➔ Web controls how/when native components or screens are accessed

Slide 53

Slide 53 text

Disadvantages ➔ No offline story (you gotta go native for offline support) ➔ We must know a little bit about native development to build the native app (Kotlin on Android and Swift on iOS)

Slide 54

Slide 54 text

Links ➔ The Hotwire Starter Kit: https://github.com/hotwired-laravel/hotwire-starter-kit ➔ The Hotwire Native Android Example for the Hotwire Starter Kit: https://github.com/hotwired-laravel/hotwire-starter-kit-android-example ➔ Joe Masilotti's Bridge Components library: https://github.com/joemasilotti/bridge-components ➔ The Hotwire Native Demo Web App: https://github.com/hotwired/hotwire-native-demo ➔ The Hotwire Native Demo Android App: https://github.com/hotwired/hotwire-native-android/tree/main/demo ➔ The Hotwire Native Demo iOS App: https://github.com/hotwired/hotwire-native-ios/tree/main/Demo ➔ Jay Ohms introduction to Strada (now Bridge Components, still applies): https://youtu.be/LKBMXqc43Q8?si=ohA7n7-jJtpJJwxV ➔ Joe Masilotti's introduction to Turbo Native (still applies): https://youtu.be/hAq05KSra2g?si=sOA3etvFImrpk3sP ➔ Sam Stephenson's "Turbolinks 5: I can't believe it's not native" talk: https://youtu.be/SWEts0rlezA?si=TU8AfgXQPoS-51mq

Slide 55

Slide 55 text

Fim.