<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: William Kennedy</title>
    <description>The latest articles on DEV Community by William Kennedy (@williamkennedy).</description>
    <link>https://dev.to/williamkennedy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F459263%2Fc9379732-b740-47a3-99f7-ca81ee3371c6.png</url>
      <title>DEV Community: William Kennedy</title>
      <link>https://dev.to/williamkennedy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/williamkennedy"/>
    <language>en</language>
    <item>
      <title>Video: Up and Running with Turbo Android</title>
      <dc:creator>William Kennedy</dc:creator>
      <pubDate>Fri, 18 Aug 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/williamkennedy/video-up-and-running-with-turbo-android-503j</link>
      <guid>https://dev.to/williamkennedy/video-up-and-running-with-turbo-android-503j</guid>
      <description></description>
      <category>android</category>
      <category>turbo</category>
      <category>ios</category>
      <category>rails</category>
    </item>
    <item>
      <title>Video: Up and Running with Turbo Android - Part 2</title>
      <dc:creator>William Kennedy</dc:creator>
      <pubDate>Fri, 18 Aug 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/williamkennedy/video-up-and-running-with-turbo-android-part-2-3b78</link>
      <guid>https://dev.to/williamkennedy/video-up-and-running-with-turbo-android-part-2-3b78</guid>
      <description></description>
      <category>android</category>
      <category>turbo</category>
      <category>ios</category>
      <category>rails</category>
    </item>
    <item>
      <title>Tidy Up Your Views with Request Variants</title>
      <dc:creator>William Kennedy</dc:creator>
      <pubDate>Wed, 02 Aug 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/williamkennedy/tidy-up-your-views-with-request-variants-2236</link>
      <guid>https://dev.to/williamkennedy/tidy-up-your-views-with-request-variants-2236</guid>
      <description>&lt;p&gt;When I gave my recent &lt;a href="https://youtu.be/JvGC9DUZ-ck"&gt;Turbo Native talk&lt;/a&gt; for Ruby Ireland, one of the questions that came up repeatedly was about duplicating views for both native and Rails. I got many questions asking if I have “if-else” logic in my views to accommodate mobile.&lt;/p&gt;

&lt;p&gt;This was an understandable question because many teams use Rails to serve JSON over the network instead of HTML. Turbo Native is a whole new world because it accommodates HTML. I use request variants to make my views easier to understand for mobile-specific reasons.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Request Variant
&lt;/h2&gt;

&lt;p&gt;A variant tells Rails to look for template variations of the same format. For example, let’s say we have a file called &lt;code&gt;app/views/books/index.html.erb.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When we navigate to &lt;code&gt;/books&lt;/code&gt;, it will render the default file. However, let’s say we create the same file but with a variation at the end of the name called &lt;code&gt;app/views/books/index.html+phone.erb&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now if we do the following in our controller, it will render the &lt;code&gt;+phone&lt;/code&gt; template instead of the template with no variation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def index
  request.variant = :phone
end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  In the Real World
&lt;/h2&gt;

&lt;p&gt;For those lucky enough to be Jumpstart Pro users, this is &lt;a href="https://jumpstartrails.com/docs/existing_apps#mobile-views"&gt;built in&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I use variants to hide partials from the mobile app for my &lt;a href="https://smartstrengthapp.com/tools/free-workout-builder"&gt;workout builder app, Smart Strength&lt;/a&gt;. For example, the footer appears on the website but disappears on the mobile app.&lt;/p&gt;

&lt;p&gt;I have two files, &lt;code&gt;footer.html.erb&lt;/code&gt;, which you see on the Smart Strength website, and &lt;code&gt;footer.html+native.erb&lt;/code&gt;, which is a blank file.&lt;/p&gt;

&lt;p&gt;This allows me to avoid having lots of logic in my views.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Request Variants with Existing Apps
&lt;/h2&gt;

&lt;p&gt;Since Hotwire arrived, many companies with existing Rails apps have wanted to build a native app the Basecamp way but do not have the Turbo JS library installed.&lt;/p&gt;

&lt;p&gt;Existing apps may also have the following challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Existing JavaScript doesn’t work with Turbo(or requires a lot of work)&lt;/li&gt;
&lt;li&gt;Few tests(more common than I would like) 

&lt;ul&gt;
&lt;li&gt;Lack a budget for a full-native app&lt;/li&gt;
&lt;li&gt;committed to certain patterns&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Request variants can bypass many of these issues when we create a variant for our layouts.&lt;/p&gt;

&lt;p&gt;Simply creating an &lt;code&gt;application.layout.html+phone.erb&lt;/code&gt;allows us to add turbo.js to that layout only and build from there. This means we could have Turbo.js exist side-by-side without worrying about interfering with the existing Rails app.&lt;/p&gt;

&lt;p&gt;This approach is how I converted a recent application to use Turbo Native.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging Request Variants
&lt;/h2&gt;

&lt;p&gt;One of the problems that can arise with request variants is debugging. If your team has a designer and they would like to design the mobile views for your native app, then you will have to teach them how to trigger request variants. If you are using Chrome:&lt;/p&gt;

&lt;p&gt;Open Inspect Element &amp;gt; Three Dots &amp;gt; More Tools and Change User Agent to Turbo Native.&lt;/p&gt;

&lt;p&gt;When you refresh the browser, this will trigger the requested variant.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K-kwhJz6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://williamkennedy.ninja/assets/images/posts/developer_tools_network_conditions.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K-kwhJz6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://williamkennedy.ninja/assets/images/posts/developer_tools_network_conditions.gif" alt="alt text" title="Opening Up Network Tools" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is how I help teams get involved early on to show how they contribute to the mobile app. This helps break down barriers between mobile, design, and development teams.&lt;/p&gt;

</description>
      <category>android</category>
      <category>turbo</category>
      <category>ios</category>
      <category>rails</category>
    </item>
    <item>
      <title>Turbo Native - Native Authentication Part 3 - Android Client</title>
      <dc:creator>William Kennedy</dc:creator>
      <pubDate>Mon, 17 Jul 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/williamkennedy/turbo-native-native-authentication-part-3-android-client-3kn5</link>
      <guid>https://dev.to/williamkennedy/turbo-native-native-authentication-part-3-android-client-3kn5</guid>
      <description>&lt;p&gt;In this series’s previous two blog posts, we covered setting up a Rails app and an iOS app. In this article, we’re going to do the same for Android. The code can be seen &lt;a href="https://github.com/williamkennedy/turbo_auth_android/tree/main"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The approach for the Turbo Android app is the same as the Turbo iOS app.&lt;br&gt;&lt;br&gt;
When a user is unauthenticated, we return a 401 status code. We intercept that error and display a native sign-in flow in our iOS app.&lt;/p&gt;
&lt;h2&gt;
  
  
  Initial Setup
&lt;/h2&gt;

&lt;p&gt;First, we can set up our app as per this &lt;a href="https://williamkennedy.ninja/android/2023/05/10/up-and-running-with-turbo-android-part-1/"&gt;blog post&lt;/a&gt;. However, instead of setting up No Activity, set up an Empty Activity, which will install all the Jetpack compose libraries.&lt;/p&gt;

&lt;p&gt;We then add our Turbo library and the okhttp3 library in the Gradle file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// hotwire
implementation "dev.hotwire:turbo:7.0.0-rc18"

// okhttp3
implementation("com.squareup.okhttp3:okhttp:4.10.0")

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, press the sync button to install all our dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a SignInFragment
&lt;/h2&gt;

&lt;p&gt;With everything set up, we next need to trigger a native flow every time a user visits /sign_in or a page that returns a 401 unauthorised status.&lt;/p&gt;

&lt;p&gt;The first thing we will do is create our &lt;code&gt;SignInFragment&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private const val AUTH_TOKEN_KEY = "auth_token"
private const val SHARED_PREFS_NAME = "turbo_native_auth"

@TurboNavGraphDestination(uri = "turbo://fragment/sign_in")
class SignInFragment : TurboFragment(), TurboNavDestination {

  override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
    return ComposeView(requireContext()).apply {
      setContent {
        AppTheme {
          SignInForm()
        }
      }
    }
  }

  @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    fun SignInForm() {
      var email by remember { mutableStateOf("") }
      var password by remember { mutableStateOf("") }

      val context = LocalContext.current

        Column(
            modifier = Modifier
            .padding(16.dp)
            .fillMaxWidth(),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center
            ) {
          TextField(
              value = email,
              onValueChange = { newText -&amp;gt;
              email = newText.trimEnd()
              },
              label = { Text("Email") },
              modifier = Modifier.fillMaxWidth()
              )
            Spacer(modifier = Modifier.height(8.dp))
            TextField(
                value = password,
                onValueChange = { password = it },
                label = { Text("Password") },
                visualTransformation = PasswordVisualTransformation(),
                modifier = Modifier.fillMaxWidth()
                )
            Spacer(modifier = Modifier.height(16.dp))
            Button(
                onClick = { performSignIn(context, email, password) },
                modifier = Modifier.align(Alignment.End)
                ) {
              Text("Sign In")
            }
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then add this to our list of registered fragments in our &lt;code&gt;MainSessionNavHost&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val BASE_URL = "http://10.0.2.2:3005"
val SIGN_IN_URL = "${BASE_URL}/sign_in"
val PATH_CONFIGURATION_URL = "${BASE_URL}/turbo/android/path_configuration"
val API_SIGN_IN_URL = "${BASE_URL}/api/v1/sessions"

class MainSessionNavHost : TurboSessionNavHostFragment() {
  override var sessionName = "main"
    override var startLocation = BASE_URL

    override val registeredFragments: List&amp;lt;KClass&amp;lt;out Fragment&amp;gt;&amp;gt;
    get() = listOf(
        WebFragment::class,
        WebModalFragment::class,
        SignInFragment::class
        )

    override val registeredActivities: List&amp;lt;KClass&amp;lt;out AppCompatActivity&amp;gt;&amp;gt;
    get() = listOf()

    override val pathConfigurationLocation: TurboPathConfiguration.Location
    get() = TurboPathConfiguration.Location(
        assetFilePath = "json/path_configuration.json",
        remoteFileUrl = PATH_CONFIGURATION_URL
        )
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Update Path Configuration
&lt;/h2&gt;

&lt;p&gt;Now, we update our &lt;code&gt;path_configuration&lt;/code&gt; file so our native fragment gets displayed. Just like turbo-ios, turbo-android relies heavily on path configuration. We can get a file from the server and save one locally. For this project, our path configuration file looks like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "settings": {
    "screenshot_enabled": true
  },
    "rules": [
    {
      "patterns": [
        ".*"
      ],
      "properties": {
        "context": "default",
        "uri": "turbo://fragment/web",
        "pull_refresh_enabled": true
      }
    },
    {
      "patterns": [
        "/new$",
      "/edit$"
      ],
      "properties": {
        "context": "modal",
        "uri": "turbo://fragment/web/modal/sheet",
        "pull_to_refresh_enabled": false
      }
    },
    {
      "patterns": [
        "/sign_in$"
      ],
      "properties": {
        "context": "default",
        "uri": "turbo://fragment/sign_in",
        "pull_refresh_enabled": false
      }
    }
  ]
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just like that, every time we navigate to &lt;code&gt;sign_in&lt;/code&gt;, we display a native sign-in form. However, it doesn’t do much yet. We still need to be able to send requests and save the cookies and auth token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Native Sign in
&lt;/h2&gt;

&lt;p&gt;For our iOS application, we saved our authentication token to the keychain to be held securely and retrieved in other parts of the application via a getter method.&lt;/p&gt;

&lt;p&gt;For the Android application, we will use the &lt;a href="https://developer.android.com/training/data-storage/shared-preferences"&gt;Shared Preferences&lt;/a&gt; api.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private fun getAuthToken(context: Context): String? {
  val sharedPreferences = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE)
    return sharedPreferences.getString(AUTH_TOKEN_KEY, null)
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method retrieves the AuthToken if we need to make an API request.&lt;/p&gt;

&lt;p&gt;We also need to be able to write the token to our shared preferences.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private fun saveAuthToken(context: Context, authToken: String?) {
  val sharedPreferences = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE)
    sharedPreferences.edit().putString(AUTH_TOKEN_KEY, authToken).apply()
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we have to save our cookies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private fun saveCookies(cookies: List&amp;lt;String&amp;gt;) {
  val cookieManager = CookieManager.getInstance()

    for (cookie in cookies) {
      parse(API_SIGN_IN_URL.toHttpUrlOrNull()!!, cookie)?.let {
        cookieManager.setCookie(API_SIGN_IN_URL, it.toString())
      }
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can add all call all these methods from our &lt;code&gt;performSignIn&lt;/code&gt; method in the &lt;code&gt;SignInFragment&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private fun performSignIn(context: Context, email: String, password: String) {
  val client = OkHttpClient()

    val requestBody = FormBody.Builder()
    .add("email", email)
    .add("password", password)
    .build()

    val request = Request.Builder()
    .url(API_SIGN_IN_URL)
    .post(requestBody)
    .build()

    client.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
        // Handle network failure or API error
        }

        override fun onResponse(call: Call, response: Response) {
        if (response.isSuccessful) {
        val authToken = response.header("X-Session-Token")
        val cookies = response.headers("Set-Cookie")

        saveAuthToken(context, authToken)
        saveCookies(cookies)

        // execute on the main thread
        requireActivity().runOnUiThread {
        navigateUp()
        sessionNavHostFragment.reset()
        }

        } else {
        // Raise error
        }
        }
    })
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since this is an async request, you must ask Turbo Android to navigate back to the previous page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// execute on the main thread
requireActivity().runOnUiThread {
  navigateUp()
    sessionNavHostFragment.reset()
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handling a 401 error.
&lt;/h2&gt;

&lt;p&gt;So now we should be able to spin up our Android app and sign in and out.&lt;/p&gt;

&lt;p&gt;The next thing we need to do is handle the 401 unauthorised errors.&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;WebFragment&lt;/code&gt;, we can override the onVisitErrorReceived method and navigate to our sign-in fragment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;override fun onVisitErrorReceived(location: String, errorCode: Int) {
  when (errorCode) {
    401 -&amp;gt; navigate(SIGN_IN_URL, TurboVisitOptions(action = REPLACE))
      else -&amp;gt; super.onVisitErrorReceived(location, errorCode)
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Unauthorised modal
&lt;/h2&gt;

&lt;p&gt;In our example app, when we navigate to &lt;code&gt;/new&lt;/code&gt;, a WebModalFragment is displayed. I could not figure out how to dismiss it and then navigate to the &lt;code&gt;SignInFragment&lt;/code&gt;, so I handled the case where there was a 401 error.&lt;/p&gt;

&lt;p&gt;By overriding the &lt;code&gt;createErrorView&lt;/code&gt;, we can inject our own.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@TurboNavGraphDestination(uri = "turbo://fragment/web/modal/sheet")
class WebModalFragment: TurboWebBottomSheetDialogFragment(), TurboNavDestination {

  @SuppressLint("InflateParams")
    override fun createErrorView(statusCode: Int): View {
      when (statusCode) {
        401 -&amp;gt; return layoutInflater.inflate(R.layout.turbo_auth_error, null)
          else -&amp;gt; return super.createErrorView(statusCode)
      }
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The customer error layout informs the user that they are not logged in, so they can dismiss and log in. Not a perfect flow, but it works for now.&lt;/p&gt;

&lt;p&gt;Turbo Android is an excellent library for quickly building hybrid apps. As you can see, utilising native for specific scenarios is straightforward. It’s simply a matter of creating and adding the fragment to our list of registered fragments.&lt;/p&gt;

</description>
      <category>android</category>
      <category>turbo</category>
      <category>ios</category>
      <category>rails</category>
    </item>
    <item>
      <title>Conquering iOS, Android and Web with Hotwire</title>
      <dc:creator>William Kennedy</dc:creator>
      <pubDate>Wed, 12 Jul 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/williamkennedy/conquering-ios-android-and-web-with-hotwire-2a9f</link>
      <guid>https://dev.to/williamkennedy/conquering-ios-android-and-web-with-hotwire-2a9f</guid>
      <description></description>
      <category>android</category>
      <category>turbo</category>
      <category>ios</category>
      <category>rails</category>
    </item>
    <item>
      <title>Turbo Native Authentication Part 2 - IOS Client</title>
      <dc:creator>William Kennedy</dc:creator>
      <pubDate>Mon, 03 Jul 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/williamkennedy/turbo-native-authentication-part-2-ios-client-bjf</link>
      <guid>https://dev.to/williamkennedy/turbo-native-authentication-part-2-ios-client-bjf</guid>
      <description>&lt;p&gt;The source code can be found &lt;a href="https://github.com/williamkennedy/turbo_auth_ios"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now that we have our Rails backend, we can start working on our Turbo Native apps. First up is iOS. This post will touch on different parts of building a typical iOS app. We will use an established iOS design pattern called the Coordinator pattern to navigate between screens. First, we’ll get our App up and running, then implement native authentication, and finally, we’ll wrap up with some bonus content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;We are going to set up our App using the Coordinator pattern. This pattern encapsulates our navigation behaviour. It works by keeping a stack of child coordinators, and depending on the logic in the App, we push or pop a coordinator.&lt;/p&gt;

&lt;p&gt;It won’t be my favourite pattern, but doing everything using UIViewControllers quickly became a mess, and this is recommended way in iOS. I’ve written about setting coordinators up previously in this blog post about rendering a &lt;a href="https://williamkennedy.ninja/ios/2022/09/12/how-to-render-a-native-home-screen-with-turbo-ios/"&gt;native screen with turbo-ios&lt;/a&gt;, but it would be good to run through everything here as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up packages
&lt;/h2&gt;

&lt;p&gt;Open Xcode and create a new app.&lt;/p&gt;

&lt;p&gt;For the template, choose App; for the Interface, select Storyboard.&lt;/p&gt;

&lt;p&gt;Ensure you have the turbo-ios and KeychainAccess packages.&lt;/p&gt;

&lt;p&gt;These can be added via File &amp;gt; Add Package &amp;gt; Enter Package name.&lt;/p&gt;

&lt;p&gt;Keychain access makes encrypting sensitive information on the user’s keychain easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up our Parent Coordinator
&lt;/h2&gt;

&lt;p&gt;Create a new group called Coordinators, then create a new file named &lt;code&gt;Coordinator.swift&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import UIKit

class Coordinator: NSObject, UINavigationControllerDelegate {

  var didFinish: ((Coordinator) -&amp;gt; Void)?

  var childCoordinators: [Coordinator] = []

  // MARK: - Methods

  func start() {}

  func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {}

  func pushCoordinator(_ coordinator: Coordinator) {
    // Install Handler
    coordinator.didFinish = { [weak self] (Coordinator) in
      self?.popCoordinator(coordinator)
    }

    // Start Coordinator
    coordinator.start()

    // Append to Child Coordinators
    childCoordinators.append(coordinator)
  }

  func popCoordinator(_ coordinator: Coordinator) {
    // Remove Coordinator From Child Coordinators
    if let index = childCoordinators.firstIndex(where: { $0 === coordinator }) {
      childCoordinators.remove(at: index)
    }
  }

  func isPresentingModal(viewController: UIViewController) -&amp;gt; Bool {
      return viewController.presentedViewController != nil
  }

}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the parent Coordinator, and every subsequent Coordinator inherits from this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up our Turbo Coordinator
&lt;/h2&gt;

&lt;p&gt;Next, create a coordinator to handle our Sessions object. &lt;code&gt;Session&lt;/code&gt; are how turbo-ios handles navigation within the webview.&lt;/p&gt;

&lt;p&gt;If you are copying/pasting, note that my baseURL is localhost:3005, not 3000.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Foundation
import Turbo
import UIKit
import SafariServices
import WebKit

class TurboSessionCoordinator: Coordinator {
    var didAuthenticate: (() -&amp;gt; Void)?
    let baseURL = URL(string: "http://localhost:3005/")!
    var rootViewController: UIViewController {
        return navigationController
    }

    var resetApp: (() -&amp;gt; Void)?

    override func start() {
        visit(url: baseURL)
    }

    private let navigationController = UINavigationController()
    private lazy var session = makeSession()
    private lazy var modalSession = makeSession()

    private func makeSession() -&amp;gt; Session {
        let session = Session()
        session.webView.customUserAgent = "My App (Turbo Native) / 1.0"
        session.delegate = self
        let pathConfiguration = PathConfiguration(sources: [
            .file(Bundle.main.url(forResource: "path_configuration", withExtension: "json")!),
            .server(baseURL.appending(path: "/turbo/ios/path_configuration"))
        ])
        session.pathConfiguration = pathConfiguration
        return session
    }

    private func visit(url: URL, action: VisitAction = .advance, properties: PathProperties = [:]) {
        let viewController = makeViewController(for: url, from: properties)
        let modal = properties["presentation"] as? String == "modal"
        let action: VisitAction = url == session.topmostVisitable?.visitableURL ? .replace : action
        navigate(to: viewController, via: action, asModal: modal)
        visit(viewController, as: modal)
    }

    private func makeViewController(for url: URL, from properties: PathProperties) -&amp;gt; UIViewController {
        return VisitableViewController(url: url)
    }

    private func navigate(to viewController: UIViewController, via action: VisitAction, asModal modal: Bool) {
        if modal {
            navigationController.present(viewController, animated: true)
        } else if action == .advance {
            navigationController.pushViewController(viewController, animated: true)
        } else if action == .replace {
            navigationController.dismiss(animated: true)
            navigationController.viewControllers = Array(navigationController.viewControllers.dropLast()) + [viewController]
        } else {
            navigationController.viewControllers = Array(navigationController.viewControllers.dropLast()) + [viewController]
        }
    }

    private func visit(_ viewController: UIViewController, as modal: Bool) {
        guard let visitable = viewController as? Visitable else { return }
        let session = modal ? modalSession : self.session
        session.visit(visitable)
    }
}

extension TurboSessionCoordinator: SessionDelegate {
    func session(_ session: Session, didProposeVisit proposal: VisitProposal) {
        visit(url: proposal.url, action: proposal.options.action, properties: proposal.properties)
    }

    func session(_ session: Session, didFailRequestForVisitable visitable: Visitable, error: Error) {
    // tackle this later
    }

    func sessionWebViewProcessDidTerminate(_ session: Session) {
        session.reload()
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Set up our Application Coordinator
&lt;/h2&gt;

&lt;p&gt;This is the most essential part of our application because it starts all the child coordinators.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Foundation
import UIKit

class ApplicationCoordinator: Coordinator {

    var rootViewController: UIViewController {
        return turboSessionCoordinatior.rootViewController
    }

    let turboSessionCoordinatior = TurboSessionCoordinator()

    override init() {
        super.init()

        childCoordinators.append(turboSessionCoordinatior)

    }

    override func start() {
        childCoordinators.forEach { (childCoordinator) in
            childCoordinator.start()
        }
    }

}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, in our SceneDelegate, we need to start our &lt;code&gt;ApplicationCoordinator&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?
    private let coordinator = ApplicationCoordinator()

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to configure and attach the UIWindow `window` to the provided UIWindowScene `scene` optionally.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        guard let windowScene = (scene as? UIWindowScene) else { return }
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = coordinator.rootViewController
        window.makeKeyAndVisible()
        self.window = window
        coordinator.start()
    }
 ....

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before we can press the run button, we need to set up a local &lt;code&gt;path_configuration.json&lt;/code&gt; file, which can exist in the root of your iOS project. It can be the same as the one we created in the previous blog post.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"rules":[
  {"patterns":["/new$","/edit$"],
  "properties": 
  {"presentation":"modal"}}
]}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you press the run button right now, you will see that the App runs like that, and we have our beautiful Rails app inside.&lt;/p&gt;

&lt;p&gt;It’s that magic that gets me every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  A brief tour
&lt;/h2&gt;

&lt;p&gt;Now, everything is up and running. However, let’s try and access a logged-in page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Vt8WfdFA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://williamkennedy.ninja/assets/images/posts/turbo_login_attempt.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Vt8WfdFA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://williamkennedy.ninja/assets/images/posts/turbo_login_attempt.gif" alt="alt text" title="Logging in" width="376" height="798"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our server is returning a 401 unauthorised response. When navigating, our TurboSessionCoordinator is triggering the &lt;code&gt;didFailRequestForVisitable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now we can work around this in several ways. For example, we can log in using a web form. However, we still get that white screen when we navigate to &lt;code&gt;posts/new&lt;/code&gt; as a guest user.&lt;/p&gt;

&lt;p&gt;The UX is less than ideal, and we can do much better by reacting to the 401 status code and pushing a new coordinator onto the stack. As a bonus, we can also retrieve an API token which will make it easier to render native views later on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Native Authentication
&lt;/h2&gt;

&lt;p&gt;Another coordinator handles native authentication. We plan to react to the 401 status and push the login view onto the stack.&lt;/p&gt;

&lt;p&gt;Let’s start by creating our AuthCoordinator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Foundation
import UIKit
import SwiftUI

class AuthCoordinator: Coordinator {
    var didAuthenticate: (() -&amp;gt; Void)?

    private let navigationController: UINavigationController
    private let authViewController: UIHostingController&amp;lt;SignInView&amp;gt;
    private let viewModel: SignInViewModel

    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
        self.viewModel = SignInViewModel()
        let newView = SignInView(viewModel: viewModel)
        self.authViewController = UIHostingController(rootView: newView)

    }

    override func start() {
        viewModel.didFinish = authenticate
        // Dismiss the current modal view controller

        if isPresentingModal(viewController: navigationController) {
            navigationController.dismiss(animated: false) {
                // Present the new view controller
                self.navigationController.present(self.authViewController, animated: true, completion: nil)
            }
        } else {
            navigationController.present(authViewController, animated: true)
        }

    }

    func authenticate() {
        didAuthenticate?()
        navigationController.dismiss(animated: true)
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s run through this code a bit because it might be new if you are coming from a Ruby background.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var didAuthenticate: (() -&amp;gt; Void)?

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;didAuthenticate&lt;/code&gt; closure or callback allows other parts of the code to be notified or informed when authentication has been completed. This closure can be assigned a value or function executed when authentication occurs. Invoking this closure can trigger any desired actions or code execution in response to the authentication event.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private let navigationController: UINavigationController
private let authViewController: UIHostingController&amp;lt;SignInView&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our navigation controller is our main controller that controls what views or logic are rendered on the screen in UIKit. Our TurboSession coordinator’s root view controller is a navigation controller(you may have noticed already).&lt;/p&gt;

&lt;p&gt;Next, we are diving into a &lt;code&gt;UIHostingController&amp;lt;SignInView&amp;gt;&lt;/code&gt;, a simple way to allow us to drop into SwiftUI.&lt;/p&gt;

&lt;p&gt;Finally, you’ll notice that we check for a modal that is being presented and dismiss any existing modal if they’re. This is because &lt;code&gt;posts/new&lt;/code&gt; happens to be presented as a modal as configured by our PathConfiguration. UIKit does not allow multiple modals in this scenario.&lt;/p&gt;

&lt;p&gt;Next, we must create a &lt;code&gt;SignInView&lt;/code&gt; and a &lt;code&gt;SignInViewModel&lt;/code&gt; to handle our auth flow.&lt;/p&gt;

&lt;p&gt;Let’s create our &lt;code&gt;SignInViewModel&lt;/code&gt; first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import SwiftUI
import Foundation
import WebKit
import KeychainAccess

class SignInViewModel: ObservableObject {
    @Published var email: String = ""
    @Published var password: String = ""

    var didFinish: (() -&amp;gt; Void)?

    @MainActor
    func signIn() async {
        let url = URL(string: "http://localhost:3005/api/v1/sessions.json")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")

        let parameters = ["email": email, "password": password]
        request.httpBody = try? JSONSerialization.data(withJSONObject: parameters)

        let session = URLSession.shared
        let task = session.dataTask(with: request) { data, response, error in
            if let error = error {
                print("Error: \(error)")
                return
            }

            if let httpResponse = response as? HTTPURLResponse {
                if let cookies = HTTPCookieStorage.shared.cookies(for: url) {
                    HTTPCookieStorage.shared.setCookies(cookies, for: url, mainDocumentURL: nil)
                    self.saveCookies(cookies)
                }
                if let authToken = httpResponse.allHeaderFields["X-Session-Token"] as? String {
                    if let bundleIdentifier = Bundle.main.bundleIdentifier {
                        print("Bundle Identifier: \(bundleIdentifier)")
                        let keychain = Keychain(service: "\(bundleIdentifier).keychain")
                        keychain[string: "token"] = authToken
                        print("Token Headers: \(authToken)")
                    }
                    // Save token to keychain
                }
            }
        }
        didFinish?()
        task.resume()
    }

    private

    func saveCookies(_ cookies: [HTTPCookie]) {
        cookies.forEach { cookie in
            DispatchQueue.main.async {
                WKWebsiteDataStore.default().httpCookieStore.setCookie(cookie)
            }
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;signIn&lt;/code&gt; method sends a web request and if successful, does two things. It saves cookies, and it saves an auth token to the keychain.&lt;/p&gt;

&lt;p&gt;Finally, we’ll create our &lt;code&gt;SignInView.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import SwiftUI

struct SignInView: View {
    @ObservedObject var viewModel: SignInViewModel

    var body: some View {
        Form {
            TextField("name@example.com", text: $viewModel.email)
                .textContentType(.username)
                .keyboardType(.emailAddress)
                .autocapitalization(.none)

            SecureField("password", text: $viewModel.password)
                .textContentType(.password)

            Button("Sign in") {
                Task {
                    await viewModel.signIn()
                }
            }
        }
    }
}

struct SignInView_Previews: PreviewProvider {
    static var previews: some View {
        SignInView(viewModel: SignInViewModel())
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Connect it to our TurboSessionCoordinator
&lt;/h2&gt;

&lt;p&gt;Now that we have the AuthCoordinator setup let’s handle the earlier error we encountered.&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;didFailReuestForVisitable&lt;/code&gt; method found in the TurboSessionCoordinator extension, we add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;extension TurboSessionCoordinator: SessionDelegate {
    func session(_ session: Session, didProposeVisit proposal: VisitProposal) {
        visit(url: proposal.url, action: proposal.options.action, properties: proposal.properties)
    }

    func session(_ session: Session, didFailRequestForVisitable visitable: Visitable, error: Error) {
        if let turboError = error as? TurboError, case let .http(statusCode) = turboError, statusCode == 401 {

            let authCoordinator = AuthCoordinator(navigationController: navigationController)
            authCoordinator.didAuthenticate = didAuthenticate
            pushCoordinator(authCoordinator)
        } else {
            let alert = UIAlertController(title: "Visit failed!", message: error.localizedDescription, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            navigationController.present(alert, animated: true)
        }
    }

    func sessionWebViewProcessDidTerminate(_ session: Session) {
        session.reload()
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you rerun the App, you’ll see that before you visit &lt;code&gt;/posts/new&lt;/code&gt;, you’ll be intercepted and have the chance to log in with the native login functionality.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vFBsSORY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://williamkennedy.ninja/assets/images/posts/turbo_login_successful_attempt.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vFBsSORY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://williamkennedy.ninja/assets/images/posts/turbo_login_successful_attempt.gif" alt="alt text" title="Successful login attempt" width="388" height="798"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is still one last problem when we click the sign-in button; we display the web login and not the native login. Let’s change that quickly by first updating our path configuration file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "rules": [
    {
      "patterns": [
        "/new$",
        "/edit$"
      ],
      "properties": {
        "presentation": "modal"
      }
    },
    {
      "patterns": [
        "/sign_in$"
      ],
      "properties": {
        "presentation": "authentication"
      }
    }
  ]
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let’s capture this flow in our TurboCoordinator session. We update our &lt;code&gt;visit&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private func visit(url: URL, action: VisitAction = .advance, properties: PathProperties = [:]) {
        let viewController = makeViewController(for: url, from: properties)
        let modal = properties["presentation"] as? String == "modal"
        let action: VisitAction = url == session.topmostVisitable?.visitableURL ? .replace : action
        let auth = properties["presentation"] as? String == "authentication"
        if auth {
            let authCoordinator = AuthCoordinator(navigationController: navigationController)
            authCoordinator.didAuthenticate = didAuthenticate
            pushCoordinator(authCoordinator)
        } else {
            navigate(to: viewController, via: action, asModal: modal)
            visit(viewController, as: modal)
        }
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So there you have it. An approach to native authentication with Turbo IOS using the Coordinator pattern. There are other approaches, but I find something like this works for my needs.&lt;/p&gt;

&lt;p&gt;The next blog post will tackle native authentication with Turbo Android.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus - API endpoint
&lt;/h2&gt;

&lt;p&gt;To access our API endpoint and render native screens, we can copy what we have done with our AuthCoordinator. We update our PathConfiguration to check for a ‘native’ flow and then push the new Coordinator onto the stack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import UIKit
import SwiftUI

class PostCoordinator: Coordinator {
    var didAuthenticate: (() -&amp;gt; Void)?

    private let navigationController: UINavigationController
    private let postViewController: UIHostingController&amp;lt;PostView&amp;gt;
    private let viewModel: PostViewModel

    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
        self.viewModel = PostViewModel()
        let view = PostView(viewModel: viewModel)
        self.postViewController = UIHostingController(rootView: view)
    }

    override func start() {
        navigationController.pushViewController(postViewController, animated: true)
    }

}


import Foundation
import SwiftUI
import Combine
import KeychainAccess

class PostViewModel: ObservableObject {
    @Published var isUpdating = false
    @Published var posts: [Post] = []

    var didFinish: (() -&amp;gt; Void)?

    @MainActor
    func fetchPosts() async {
        isUpdating = true
        var token = ""
        let url = URL(string: "http://localhost:3005/posts.json")!
        if let bundleIdentifier = Bundle.main.bundleIdentifier {
            print("Bundle Identifier: \(bundleIdentifier)")
            let keychain = Keychain(service: "\(bundleIdentifier).keychain")
            token = keychain[string: "token"] ?? ""
        } else {
            return
        }
        let config = URLSessionConfiguration.default
        config.waitsForConnectivity = true
        config.timeoutIntervalForResource = 60
        config.httpAdditionalHeaders = [
            "Authorization": "Bearer \(token)"
        ]
        print(token)

        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        request.addValue("application/json", forHTTPHeaderField: "Accept")

        URLSession(configuration: config).dataTask(with: request) { data, response, error in
            if let error = error {
                print(error.localizedDescription)
            }

            if let data = data {
                let decoder = JSONDecoder()
                decoder.keyDecodingStrategy = .convertFromSnakeCase

                do {
                    let posts = try decoder.decode([Post].self, from: data)
                    DispatchQueue.main.async {
                        self.posts = posts
                        print(self.posts)
                        self.objectWillChange.send()
                    }
                } catch {
                    print(error)
                }
            }
        }.resume()

        isUpdating = false
    }

}


import SwiftUI

struct PostView: View {
    @ObservedObject var viewModel: PostViewModel

    var body: some View {
        NavigationStack {
            List {
                ForEach(viewModel.posts, id: \.id) { post in
                    Text(post.title)
                }
            }
        }
        .task {
                await viewModel.fetchPosts()
        }
    }

}

struct PostView_Previews: PreviewProvider {
    static var previews: some View {
        PostView(viewModel: PostViewModel())
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two more steps to implement the native screen, but I’ll leave that as an exercise for the reader ;).&lt;/p&gt;

</description>
      <category>android</category>
      <category>turbo</category>
      <category>ios</category>
      <category>rails</category>
    </item>
    <item>
      <title>Turbo Native Authentication Part 1 - Rails Backend</title>
      <dc:creator>William Kennedy</dc:creator>
      <pubDate>Sun, 02 Jul 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/williamkennedy/turbo-native-authentication-part-1-rails-backend-3om9</link>
      <guid>https://dev.to/williamkennedy/turbo-native-authentication-part-1-rails-backend-3om9</guid>
      <description>&lt;p&gt;In this Turbo Native series, we’re going to build native auth. Part 1 will cover the server side. Part 2 will cover Turbo iOS, and then we’ll end with Android.&lt;/p&gt;

&lt;p&gt;Authentication is complicated. Not because it’s hard but because it’s scary.&lt;/p&gt;

&lt;p&gt;The Rails app is just an example of getting up and running, but you may need to adjust for your security needs if you wish to run it on production.&lt;/p&gt;

&lt;p&gt;The source code can be found &lt;a href="https://github.com/williamkennedy/turbo_auth_rails"&gt;here&lt;/a&gt;, and it exists as a template so you can clone it into your app. With some tweaks, the app builds off the authentication-zero gem, so we have API authentication and ordinary sign-in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial Confusion
&lt;/h2&gt;

&lt;p&gt;One of the parts I found confusing about authentication was this line in the &lt;a href="https://github.com/hotwired/turbo-ios/blob/main/Docs/Authentication.md"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For HEY, we have a slightly different approach. We get the cookies back along with our initial OAuth request and set those cookies directly to the web view and global cookie stores&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is not something I encountered before when using SPAs. When you are building a Rails backend with a SPA, I have only had experience with token-based authentication or cookie-based authentication. I have never done both.&lt;/p&gt;

&lt;p&gt;With further ado, let’s dive in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rails Scaffold
&lt;/h2&gt;

&lt;p&gt;First, we start with the traditional Rails scaffold.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails new turbo_auth_rails --javascript=esbuild --css=tailwind --database=postgresql

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we add the gem &lt;a href="https://github.com/lazaronixon/authentication-zero"&gt;authentication-zero&lt;/a&gt; to our Gemfile&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication Zero
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bundle add authentication-zero

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then ran the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails g authentication --pwned --passwordless --omniauthable &amp;amp;&amp;amp; rake db:migrate

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the next part, I wanted to implement token based authentication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Api::V1::SessionsController &amp;lt; Api::ApplicationController
  skip_before_action :authenticate, only: :create

  before_action :set_session, only: %i[show destroy]

  def index
    render json: Current.user.sessions.order(created_at: :desc)
  end

  def show
    render json: @session
  end

  def create
    user = User.find_by(email: params[:email])

    if user &amp;amp;&amp;amp; user.authenticate(params[:password])
      @session = user.sessions.create!
      response.set_header "X-Session-Token", @session.signed_id
      cookies.signed.permanent[:session_token] = { value: @session.id, httponly: true }

      render json: @session, status: :created
    else
      render json: { error: "That email or password is incorrect" }, status: :unauthorized
    end
  end

  def destroy
    @session.destroy
  end

  private
  def set_session
    @session = Current.user.sessions.find(params[:id])
  end
end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The controller exists in &lt;code&gt;app/controllers/api/v1/sessions_controller.rb&lt;/code&gt;. The corresponding routes look like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace :api do
  namespace :v1 do
    resources :sessions, only: [:index, :create, :show, :destroy] 
  end
end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are no views since everything renders JSON.&lt;/p&gt;

&lt;p&gt;We set the response header in the controller and assign the cookies with the session id.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; response.set_header "X-Session-Token", @session.signed_id
 cookies.signed.permanent[:session_token] = { value: @session.id, httponly: true }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then modified the API application controller generated by authentication-zero to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Api::ApplicationController &amp;lt; ActionController::API
  include ActionController::HttpAuthentication::Token::ControllerMethods
  include ActionController::Cookies

  before_action :set_current_request_details
  before_action :authenticate

  private
  def authenticate
    if session_record = authenticate_with_http_token { |token, _| Session.find_signed(token) }
      Current.session = session_record
    else
      request_http_token_authentication
    end
  end

  def set_current_request_details
    Current.user_agent = request.user_agent
    Current.ip_address = request.ip
  end
end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also modified the main application controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ApplicationController &amp;lt; ActionController::Base
  before_action :set_current_request_details
  before_action :authenticate

  def authenticate
    if authenticate_with_token || authenticate_with_cookies
      # Great! You're in
    elsif !performed?
      request_api_authentication || request_cookie_authentication
    end
  end

  def authenticate_with_token
    if session = authenticate_with_http_token { |token, _| Session.find_signed(token) }
      Current.session = session
    end
  end

  def authenticate_with_cookies
    if session = Session.find_by_id(cookies.signed[:session_token])
      Current.session = session
    end
  end

  def user_signed_in?
    authenticate_with_cookies || authenticate_with_token
  end

  helper_method :user_signed_in?


  def request_api_authentication
    request_http_token_authentication if request.format.json?
  end

  def request_cookie_authentication
    session[:return_path] = request.fullpath
    render 'sessions/new', status: :unauthorized, notice: "You need to sign in"
  end

  def set_current_request_details
    Current.user_agent = request.user_agent
    Current.ip_address = request.ip
  end
end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This checks if we either have a token or a cookie. We also return a 401 status code if a user is not logged in or needs to be authenticated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Posts Scaffold
&lt;/h2&gt;

&lt;p&gt;Let’s now build a scaffold.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails g scaffold Post title:string content:rich_text &amp;amp;&amp;amp; rails db:migrate

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not all styling is in the repo if you wish to copy and paste. You will need to copy the &lt;code&gt;layout/application.html.erb&lt;/code&gt; file, the &lt;code&gt;posts&lt;/code&gt; folder, and the &lt;code&gt;sessions/new&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Finally, in our posts controller, we added the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class PostsController &amp;lt; ApplicationController
  before_action :authenticate, except: [:index, :show]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will trigger a 401 status whenever a user tries to log in. This will be used to start a login on our Turbo iOS app in the next blog post.&lt;/p&gt;

&lt;p&gt;Finally, let’s add a path_configuration route.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Turbo::Ios::PathConfigurationsController &amp;lt; ApplicationController
  skip_before_action :authenticate

  def show
    render json: {
      rules: [
        {
          patterns: ["/new$", "/edit$"],
          properties: {
            presentation: "modal"
          }
        },
        {
          patterns: ["/sign_in$"],
          properties: {
            presentation: 'authentication'
          }
        }
      ]
    }
  end
end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we update our &lt;code&gt;routes.rb&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  namespace :turbo do
    namespace :ios do
      resource :path_configuration, only: [:show]
    end
  end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>android</category>
      <category>turbo</category>
      <category>ios</category>
      <category>rails</category>
    </item>
    <item>
      <title>3 Useful Tricks When Working With Turbo Android</title>
      <dc:creator>William Kennedy</dc:creator>
      <pubDate>Fri, 02 Jun 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/williamkennedy/3-useful-tricks-when-working-with-turbo-android-3904</link>
      <guid>https://dev.to/williamkennedy/3-useful-tricks-when-working-with-turbo-android-3904</guid>
      <description>&lt;p&gt;Thanks for following along with my &lt;a href="https://williamkennedy.ninja/android/2023/05/10/up-and-running-with-turbo-android-part-1/"&gt;Turbo Android&lt;/a&gt; series. I’m going to finish up with some general tips.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Debugging the WebView
&lt;/h2&gt;

&lt;p&gt;Sometimes you will want to debug via the webview. To do this, do the following:&lt;/p&gt;

&lt;p&gt;Open Chrome and type in &lt;code&gt;chrome://inspect&lt;/code&gt; in the URL bar&lt;/p&gt;

&lt;p&gt;This URL will display every Chrome process allowing you to use the Chrome Development tools on your Turbo Native webview.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Keep 99% on the Server
&lt;/h2&gt;

&lt;p&gt;Delegate to your server-side app as much as possible. It might be tempting to build many native screens, but only go native if necessary. By serving HTML over the wire, you can keep your app simply because you only have to make changes in one place.&lt;/p&gt;

&lt;p&gt;You also benefit from making changes over the wire when you need without having to get your app approved by Apple.&lt;/p&gt;

&lt;p&gt;Scenarios, where native performance is superior include maps and offline functionality. For example, Smart Strength’s workout builder is native because I want users to use swipe to delete/copy, SwiftUI Context Menu and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Variants
&lt;/h2&gt;

&lt;p&gt;If you have a legacy app, you can use variants to render different pages for the same request.&lt;/p&gt;

&lt;p&gt;In Rails, you can set the requested variant with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# application_controller.rb
before_action do
  request.variant = :native if turbo_native_app?
end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you look at the &lt;code&gt;turbo_native_app&lt;/code&gt; method in the &lt;a href="https://github.com/hotwired/turbo-rails/blob/ea00f3732e21af9c2156cf74dabe95524b17c361/app/controllers/turbo/native/navigation.rb#L8"&gt;source code&lt;/a&gt;, it just checks to see if there is a user agent string in called Turbo Native.&lt;/p&gt;

&lt;p&gt;When you do that, you can create the same view but append &lt;code&gt;html+native.erb&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When you request, Rails will evaluate the agent string to decide what variant to display.&lt;/p&gt;

&lt;p&gt;Let’s say you visit &lt;code&gt;/posts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For normal web browsing, Rails will render &lt;code&gt;index.html.erb&lt;/code&gt;, but if you are navigating from a Turbo Native app, it will render &lt;code&gt;index.html+native.erb&lt;/code&gt;. If there is no variant, it falls back to the standard view. This means you can avoid lots of &lt;code&gt;if turbo_native_app?&lt;/code&gt; statements in your views.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus Trick: 4. Use Jumpstart Pro
&lt;/h2&gt;

&lt;p&gt;If you can afford it, I would buy a Jumpstart Pro Android and iOS licence. This will save you a lot of development time, plus it comes with documentation and access to the GoRails Discord, where you ask questions. You will also learn a lot from the codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Downsides
&lt;/h2&gt;

&lt;p&gt;Every choice has trade-offs, and Turbo Native is no different.&lt;/p&gt;

&lt;p&gt;You need to get comfortable with Turbo.js. It has garnered some fans and praise, but some don’t like it. Turbo Native hinges on the fact that you are going to use Turbo. If you plan to include lots of JS libraries in your project, write a small Stimulus/JQuery wrapper for each one and get used to listening for the custom Turbo events. However, the trade-off has proved worthwhile, especially compared to the &lt;a href="https://dev.to/javascript/2022/05/03/in-defence-of-the-single-page-application/"&gt;previous approach&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>turbo</category>
      <category>hotwire</category>
      <category>android</category>
      <category>native</category>
    </item>
    <item>
      <title>How to Get Up and Running With Turbo Android Turbo Android Part 3 - How to Access Native Android Features with the JavaScript...</title>
      <dc:creator>William Kennedy</dc:creator>
      <pubDate>Tue, 23 May 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/williamkennedy/how-to-get-up-and-running-with-turbo-android-turbo-android-part-3-how-to-access-native-android-features-with-the-javascript-40hl</link>
      <guid>https://dev.to/williamkennedy/how-to-get-up-and-running-with-turbo-android-turbo-android-part-3-how-to-access-native-android-features-with-the-javascript-40hl</guid>
      <description>&lt;p&gt;&lt;a href="https://williamkennedy.ninja/android/2023/05/19/turbo-android-part-2-feel-more-native/"&gt;Continuing from&lt;/a&gt; my previous post about accessing native screens from your hybrid web app, today, we’re going to talk about the Javascript bridge which is my favourite feature within the whole Turbo native ecosystem.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;We can write the javascript we already know(and love/hate) to call native code. In previous projects, I’ve used this to delete native tokens, access permissions, and access a native SDK and in an earlier post, we’ve &lt;a href="https://williamkennedy.ninja/ios/2022/11/13/turbo-native-how-to-access-native-ios-features-from-rails/"&gt;accessed the phone’s contacts database&lt;/a&gt;. In today’s post, we’ll be doing precisely that to demonstrate the power of Turbo Native.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backend application
&lt;/h2&gt;

&lt;p&gt;We will use the same backend application we’ve used for the last two articles, which you can find here. We’ve &lt;a href="https://github.com/williamkennedy/crm_me/blob/phase_4/app/javascript/application.js"&gt;added some Javascript&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here we’ve set up a JavaScript bridge to post messages to the Android client and send messages back.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Entry point for the build script in your package.json
import "@hotwired/turbo-rails"
import "./controllers"

class Bridge {

  // Sends a message to the native app, if active.
  static postMessage(name, data = {}) {
    // iOS
    window.webkit?.messageHandlers?.nativeApp?.postMessage({name, ...data})

    // Android
    window.nativeApp?.postMessage(JSON.stringify({name, ...data}))
  }

  static importingContacts(name) {
    fetch('/contacts.json', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        "Accept": "application/json",
      },
      body: JSON.stringify({contact: { name } })
    }).then((response) =&amp;gt; response.json())
      .then((data) =&amp;gt; {
        console.log("Success:", data);
        var btn = document.querySelector('button')
        btn.textContent = `"Imported ${name}"`
      })
      .catch((error) =&amp;gt; {
        console.error("Error:", error);
      });
  }
}

// Expose this on the window object so TurboNative can interact with it
window.Bridge = Bridge
export default Bridge

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ve also created a &lt;a href="https://github.com/williamkennedy/crm_me/blob/phase_4/app/javascript/controllers/contacts_controller.js"&gt;stimulus controller&lt;/a&gt; that attaches to a button that posts that particular method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  import() {
    window.Bridge.postMessage("contacts", {name: "contacts"})
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, in our view, we’ve connected the Stimulus controller to the button that will import the contacts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;p style="color: green"&amp;gt;&amp;lt;%= notice %&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;div class="mt-8 px-4 flex items-center justify-between"&amp;gt;
  &amp;lt;div class="min-w-0 flex-1"&amp;gt;
    &amp;lt;h1 class='h1'&amp;gt;Import Contacts&amp;lt;/h1&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div data-controller='contacts' class='overflow-hidden rounded-md bg-white shadow mt-4 p-2'&amp;gt;

  &amp;lt;div class="text-center"&amp;gt;
    &amp;lt;svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"&amp;gt;
      &amp;lt;path vector-effect="non-scaling-stroke" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z" /&amp;gt;
    &amp;lt;/svg&amp;gt;
    &amp;lt;h3 class="mt-2 text-sm font-semibold text-gray-900"&amp;gt;No contacts imported&amp;lt;/h3&amp;gt;
    &amp;lt;p class="mt-1 text-sm text-gray-500"&amp;gt;Get started by creating a new project.&amp;lt;/p&amp;gt;
    &amp;lt;div class="mt-6"&amp;gt;
      &amp;lt;button data-action='contacts#import' type="button" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"&amp;gt;
        &amp;lt;svg class="-ml-0.5 mr-1.5 h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"&amp;gt;
          &amp;lt;path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z" /&amp;gt;
        &amp;lt;/svg&amp;gt;
        Import Contacts
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;ul class='divide-y divide-gray-200' id="contacts"&amp;gt;
  &amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty straightforward so far. Now let’s look and see what we’ve to do on the client side.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Native Bridge
&lt;/h3&gt;

&lt;p&gt;First, we create a new class that will act as our javascript interface and allow us to post javascript messages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const val CONTACTS = "contacts"

class NativeJavaScriptInterface {

  var onContacts: () -&amp;gt; Unit = {}

  @Suppress("unused")
    @JavascriptInterface
    fun postMessage(jsonData: String) {
      if (jsonData == null) {
        Log.d(ContentValues.TAG, "postMessage: ${jsonData}")
      } else {
        val json = JSONObject(jsonData)
          when (val command = json.optString("name")) {
            CONTACTS -&amp;gt; contacts()
              else -&amp;gt; Log.d(ContentValues.TAG, "No function: $command. Add function in ${this::class.simpleName}")
          }
      }
    }

  private fun contacts() = onContacts()
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The native bridge is connected to our MainSessionNavHostFragment, which we created in the &lt;a href="https://williamkennedy.ninja/android/2023/05/10/up-and-running-with-turbo-android-part-1/"&gt;first post in this series&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MainSessionNavHostFragment : TurboSessionNavHostFragment() {

  ....

    var nativeAppJavaScriptInterface: NativeJavaScriptInterface = NativeJavaScriptInterface()
    ....

    override fun onCreateWebView(context: Context): TurboWebView {
      val turboWebView = super.onCreateWebView(context)
        bindNativeBridge(turboWebView, context = context)
        return turboWebView
    }

  private fun bindNativeBridge(webView: TurboWebView, context: Context) {
    nativeAppJavaScriptInterface.onContacts = {
      Handler(Looper.getMainLooper()).post {

        val permissions = arrayOf(
            Manifest.permission.READ_CONTACTS,
            Manifest.permission.QUERY_ALL_PACKAGES
            )

          val requestCode = 123 // This can be any integer value

          if (Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.M) {
            if (context.checkSelfPermission(permissions[0]) == PackageManager.PERMISSION_GRANTED &amp;amp;&amp;amp;
                context.checkSelfPermission(permissions[1]) == PackageManager.PERMISSION_GRANTED) {
              // Permissions already granted
              // Do the contact reading operation here
            } else {
              // Permissions not granted
              requestPermissions(permissions, requestCode)
            }
          } else {
            // Permissions not needed in older versions of Android
            // Do the contact reading operation here
          }

        val contactsList: MutableList&amp;lt;String&amp;gt; = mutableListOf()
          val cursor = context.contentResolver.query(
              ContactsContract.Contacts.CONTENT_URI,
              null,
              null,
              null,
              null
              )

          if (cursor?.count ?: 0 &amp;gt; 0) {
            while (cursor != null &amp;amp;&amp;amp; cursor.moveToNext()) {
              val name =
                cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
                contactsList.add(name)
            }
          }

        cursor?.close()

          for (contact in contactsList) {
            Log.d("Contact", contact)
              val script = "Bridge.importingContacts('${contact}')"
              session.webView.evaluateJavascript(script, null)
          }
        Log.d(ContentValues.TAG, "bindNativeBridge onImportContacts")
      }
    }

    webView.addJavascriptInterface(nativeAppJavaScriptInterface, "nativeApp")

    webView.loadData("", "text/html", null)
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our AndroidManifest file, we have to add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;uses-permission android:name="android.permission.READ_CONTACTS" /&amp;gt;
&amp;lt;uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding these lines ensures we can access the phone’s contacts.&lt;/p&gt;

&lt;p&gt;Now everything is hooked up; when we run our app, we should see that we can import the user’s contacts. Using the JavaScript bridge is another powerful tool that Hotwire gives us, opening up a whole new world with a fraction of the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So far, we’ve covered getting up and running, utilising modals to feel more native, and accessing native screens, and today we covered the Javascript bridge. In the following article, we will talk about debugging, structuring your Android project, and external libraries that are useful in the Android ecosystem without going overboard.&lt;/p&gt;

&lt;p&gt;Till next time.&lt;/p&gt;

</description>
      <category>turbo</category>
      <category>hotwire</category>
      <category>android</category>
      <category>native</category>
    </item>
    <item>
      <title>How to Get up and Running with Turbo Android Part 2 - Feel More Native</title>
      <dc:creator>William Kennedy</dc:creator>
      <pubDate>Fri, 19 May 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/williamkennedy/how-to-get-up-and-running-with-turbo-android-part-2-feel-more-native-5h9h</link>
      <guid>https://dev.to/williamkennedy/how-to-get-up-and-running-with-turbo-android-part-2-feel-more-native-5h9h</guid>
      <description>&lt;p&gt;So far, we’ve covered getting up and running with &lt;a href="https://dev.to/android/2023/05/10/up-and-running-with-turbo-android-part-1/"&gt;Turbo Android&lt;/a&gt;. Now we can move on to the fun part of Turbo Android, which is about making it feel more native. Compared to &lt;a href="http://localhost:4000/ios/2022/09/12/how-to-render-a-native-home-screen-with-turbo-ios/"&gt;Turbo-ios&lt;/a&gt;, Turbo Android has a lot built into the framework and can easily route different endpoints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Setup with Modals
&lt;/h2&gt;

&lt;p&gt;You may have noticed that the Android app doesn’t feel very native. Instead, it feels like a webpage inside an app’s skin. Luckily, we can add some quick changes utilising &lt;a href="https://github.com/hotwired/turbo-android/tree/main/turbo/src/main/kotlin/dev/hotwire/turbo/fragments"&gt;Turbo Fragments&lt;/a&gt; found in the &lt;a href="https://github.com/hotwired/turbo-android/tree/main/turbo/src/main/kotlin/dev/hotwire/turbo/fragments"&gt;Turbo-Android library&lt;/a&gt;. By inheriting from these and updating out Path Configuration, we have routes display different Turbo Fragments.&lt;/p&gt;

&lt;p&gt;For now, we will update our codebase so that every route that ends with &lt;code&gt;new&lt;/code&gt; and &lt;code&gt;edit&lt;/code&gt; will result in a modal popup like the one shown in this gif.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AKdPH0A8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://williamkennedy.ninja/assets/images/posts/android/part_2/bottom_modal.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AKdPH0A8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://williamkennedy.ninja/assets/images/posts/android/part_2/bottom_modal.gif" alt="alt text" title="Rendering Modal" width="384" height="790"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Update PathConfiguration
&lt;/h3&gt;

&lt;p&gt;We created a &lt;a href="https://github.com/hotwired/turbo-android/blob/main/docs/PATH-CONFIGURATION.md"&gt;path configuration&lt;/a&gt; file under assets/json in the previous tutorial. Let’s edit that and add a new rule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "patterns": [
    "/new$",
    "/edit$"
  ],
  "properties": {
    "context": "modal",
    "uri": "turbo://fragment/web/modal/sheet",
    "pull_to_refresh_enabled": false
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is telling our Turbo Android app to use the class that’s annotated with &lt;code&gt;@TurboNavGraphDestination(uri = "turbo://fragment/web/modal/sheet")&lt;/code&gt; for any route that ends in &lt;code&gt;new&lt;/code&gt; or &lt;code&gt;edit&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the Fragment and add it
&lt;/h3&gt;

&lt;p&gt;Now we need to create the fragment. Add a new Kotlin class and call it &lt;code&gt;WebBottomSheetFragment&lt;/code&gt; and make sure to inherit from &lt;code&gt;TurboWebBottomSheetDialogFragment()&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
import dev.hotwire.turbo.fragments.TurboWebBottomSheetDialogFragment
import dev.hotwire.turbo.nav.TurboNavGraphDestination

@TurboNavGraphDestination(uri = "turbo://fragment/web/modal/sheet")
class WebBottomSheetFragment : TurboWebBottomSheetDialogFragment() {}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’re not done yet. We’ve to add it to the list of registered fragments in our &lt;code&gt;MainSessionNavHost&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MainSessionNavHost : TurboSessionNavHostFragment() {
  ...

    override val registeredFragments: List&amp;lt;KClass&amp;lt;out Fragment&amp;gt;&amp;gt;
    get() = listOf(
        WebFragment::class,
        WebBottomSheetFragment::class,
        )
    ...
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we run the app and navigate to any route that ends in &lt;code&gt;new&lt;/code&gt; or &lt;code&gt;edit&lt;/code&gt;, we display a modal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering a Native Screen
&lt;/h2&gt;

&lt;p&gt;The exact process that we used to add a modal we can also use it to navigate to a native screen. Let’s navigate to a simple Hello World screen written with Jetpack Compose. Jetpack Compose is Google’s modern UI tool and is similar to SwiftUI.&lt;/p&gt;

&lt;p&gt;There is a little setup involved if we use the modern approach to building Android tools. So let’s dive in step by step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update build.gradle
&lt;/h3&gt;

&lt;p&gt;We have to add the compose libraries to our app and ensure that it will compile Jetpack Compose.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;android {
  ...
    buildFeatures {
      compose true
    }
  ...
}

dependencies {

  implementation 'androidx.compose.ui:ui:1.4.2'
    implementation 'androidx.compose.material:material:1.2.0'
    def composeBom = platform("androidx.compose:compose-bom:2023.03.00")
    implementation(composeBom)
    ....
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to sync your changes.&lt;/p&gt;

&lt;p&gt;Next, we update our Path Configuration to render specific routes with a native screen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "patterns": [
    "/hello_world"
  ],
  "properties": {
    "context": "default",
    "uri": "turbo://fragment/hello_world"
  }
},

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we create the &lt;code&gt;HelloWorldFragment&lt;/code&gt;, which inherits from &lt;code&gt;TurboFragment&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  @TurboNavGraphDestination(uri = "turbo://fragment/hello_world")
  class HelloWorldFragment : TurboFragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
      return ComposeView(requireContext()).apply {
        setContent {
          Hello()
        }
      }
    }

    @Composable
      fun Hello() {
        Column(
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
            ) {
          Text("Hello World")
        }
      }
  }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we add this fragment to the list of registered fragments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MainSessionNavHost : TurboSessionNavHostFragment() {
  ...

    override val registeredFragments: List&amp;lt;KClass&amp;lt;out Fragment&amp;gt;&amp;gt;
    get() = listOf(
        WebFragment::class,
        WebBottomSheetFragment::class,
        HelloWorldFragment::class 
        )
    ...
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;*NOTE: You may have to change your Kotlin version to 1.7.0 to compile. This can be changed in &lt;code&gt;build.grade(project)&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once we’ve run the app, we should be able to navigate to the &lt;code&gt;/hello\_world&lt;/code&gt; route and voila:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OSFWw3uc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://williamkennedy.ninja/assets/images/posts/android/part_2/native_screen.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OSFWw3uc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://williamkennedy.ninja/assets/images/posts/android/part_2/native_screen.gif" alt="alt text" title="Rendering native screen gif" width="374" height="786"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s pretty basic, but it’s enough to build on. In the next blog post, we will talk about the JavaScript bridge, and then we will talk about little improvements we can make to our Turbo app to make it even better.&lt;/p&gt;

</description>
      <category>turbo</category>
      <category>hotwire</category>
      <category>android</category>
      <category>native</category>
    </item>
    <item>
      <title>Up and Running With Turbo Android Part 1</title>
      <dc:creator>William Kennedy</dc:creator>
      <pubDate>Wed, 10 May 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/williamkennedy/up-and-running-with-turbo-android-part-1-2f36</link>
      <guid>https://dev.to/williamkennedy/up-and-running-with-turbo-android-part-1-2f36</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j_U0MRvV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://williamkennedy.ninja/assets/images/posts/hello_iphone.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j_U0MRvV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://williamkennedy.ninja/assets/images/posts/hello_iphone.jpg" alt="alt text" title="iPhone 8 with Hello text on screen" width="640" height="471"&gt;&lt;/a&gt;Photo by &lt;a href="https://unsplash.com/@lastly?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Tyler Lastovich&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/iphone?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Turbo Android - Part 1 Getting Setup
&lt;/h1&gt;

&lt;p&gt;In previous blog posts, I’ve covered getting set up with &lt;a href="https://dev.to/ios/2022/09/12/how-to-render-a-native-home-screen-with-turbo-ios/"&gt;Turbo iOS&lt;/a&gt; and implementing native functionality.&lt;/p&gt;

&lt;p&gt;However, Android is a massive part of the mobile market, and there are too few resources on getting up and running with Turbo Android.&lt;/p&gt;

&lt;p&gt;This post aims to rectify that.&lt;/p&gt;

&lt;p&gt;You will need Android Studio for this tutorial.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Basics
&lt;/h2&gt;

&lt;p&gt;First of all, we will assume you have a Turbo-enabled app. I’ll be using &lt;a href="https://github.com/williamkennedy/crm_me"&gt;this app&lt;/a&gt; for a &lt;a href="https://www.figma.com/file/BHUq2FqNAVZibRKTnjhTu1/Hotwiring-Android-and-IOS?type=design&amp;amp;node-id=1%3A80&amp;amp;t=EumTHj22nAAc9Yjy-1"&gt;talk&lt;/a&gt; I’m giving.&lt;/p&gt;

&lt;p&gt;The app is called CRM ME. It’s a simple CRM app. You can add, delete and update contacts—all your basic functionality.&lt;/p&gt;

&lt;p&gt;You can create your own using the following Rails command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails new crm_me -j esbuild

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we’re using esbuild. This is because import maps are not supported on the Android browser yet.&lt;/p&gt;

&lt;p&gt;With your Rails app installed, you can run &lt;code&gt;rails g scaffold Contacts name:string&lt;/code&gt; followed by &lt;code&gt;rails db:migrate&lt;/code&gt;, and you’re good to go.&lt;/p&gt;

&lt;p&gt;Let’s run your app on the following port.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bin/dev -p 3000 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting up Turbo Android and Installing the Library
&lt;/h2&gt;

&lt;p&gt;Open up Android Studio, click New Project, then click ‘No Activity’.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CHkAuBrX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://williamkennedy.ninja/assets/images/posts/android/part_1/android_studio_no_activity.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CHkAuBrX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://williamkennedy.ninja/assets/images/posts/android/part_1/android_studio_no_activity.png" alt="alt text" title="Android Studio No activity" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the next screen, you can name the app whatever you want and click Finish.&lt;/p&gt;

&lt;p&gt;To get Turbo up and running, we first need the Hotwire library.&lt;/p&gt;

&lt;p&gt;Open up &lt;code&gt;build.gradle(Module:app)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;At the bottom of this file, you’ll see the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies {

    implementation 'androidx.core:core-ktx:1.8.0'
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.8.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add in the Turbo Android library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies {

    implementation 'androidx.core:core-ktx:1.8.0'
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.8.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

    implementation "dev.hotwire:turbo:7.0.0-rc18" 
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will be asked to sync your Gradle file. Ensure that you do as this installs all the dependencies.&lt;/p&gt;

&lt;p&gt;Now, we need to get our boilerplate up and running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the MainActivity and MainSessionNavHost
&lt;/h2&gt;

&lt;p&gt;Android is made up of activities. An activity is a single screen with a user interface. We will then add this activity to the Android Manifest file.&lt;/p&gt;

&lt;p&gt;Let’s dive into the code:&lt;/p&gt;

&lt;p&gt;First, create a kotlin file called &lt;code&gt;MainActivity&lt;/code&gt; under the java/com.example.yourappname&lt;/p&gt;

&lt;p&gt;Enter the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package com.example.yourappname
 
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import dev.hotwire.turbo.activities.TurboActivity
import dev.hotwire.turbo.delegates.TurboActivityDelegate

class MainActivity : AppCompatActivity(), TurboActivity {
    override lateinit var delegate: TurboActivityDelegate

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        delegate = TurboActivityDelegate(this, R.id.main_nav_host)
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll notice that &lt;code&gt;activity_main&lt;/code&gt; and &lt;code&gt;main_nav_host&lt;/code&gt; are currently red. We’ll rectify that soon.&lt;/p&gt;

&lt;p&gt;We must first create our &lt;code&gt;MainSessionNavHost&lt;/code&gt;, the main class that handles the flow of our application.&lt;/p&gt;

&lt;p&gt;Create a kotlin file called &lt;code&gt;MainSessionNavHost&lt;/code&gt; under java/com.example.yourappname&lt;/p&gt;

&lt;p&gt;Enter the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import dev.hotwire.turbo.config.TurboPathConfiguration
import dev.hotwire.turbo.session.TurboSessionNavHostFragment
import kotlin.reflect.KClass

class MainSessionNavHost : TurboSessionNavHostFragment() {
    override var sessionName = "main"
    override var startLocation = "http://10.0.2.2:3000"

    override val registeredFragments: List&amp;lt;KClass&amp;lt;out Fragment&amp;gt;&amp;gt;
    get() = listOf()

    override val registeredActivities: List&amp;lt;KClass&amp;lt;out AppCompatActivity&amp;gt;&amp;gt;
    get() = listOf()

    override val pathConfigurationLocation: TurboPathConfiguration.Location
    get() = TurboPathConfiguration.Location( assetFilePath = "json/configuration.json")

}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have three more files to create; a small change to our Android manifest file, and we should be up and running. We need to make three resource files, a &lt;code&gt;json/configuration.json&lt;/code&gt; asset file, a &lt;code&gt;main_activity&lt;/code&gt; layout and a WebFragment.&lt;/p&gt;

&lt;p&gt;Let’s start with our JSON file.&lt;/p&gt;

&lt;p&gt;Right-click on the app and go to New &amp;gt; Folder &amp;gt; Assets Folder.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--77gHLNio--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://williamkennedy.ninja/assets/images/posts/android/part_1/android_studio_assets.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--77gHLNio--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://williamkennedy.ninja/assets/images/posts/android/part_1/android_studio_assets.png" alt="alt text" title="Android Studio assets folder" width="800" height="730"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, right-click on the assets directory, New &amp;gt; Directory and create the JSON directory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NMvDczHg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://williamkennedy.ninja/assets/images/posts/android/part_1/android_studio_directory.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NMvDczHg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://williamkennedy.ninja/assets/images/posts/android/part_1/android_studio_directory.png" alt="alt text" title="Android Studio directory" width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, right-click on the json directory and add a file called &lt;code&gt;configuration.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NMvDczHg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://williamkennedy.ninja/assets/images/posts/android/part_1/android_studio_directory.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NMvDczHg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://williamkennedy.ninja/assets/images/posts/android/part_1/android_studio_directory.png" alt="alt text" title="Android Studio file" width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the following to the file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "settings": {
    "screenshots_enabled": true
  },
  "rules": [
    {
      "patterns": [
        ".*"
      ],
      "properties": {
        "context": "default",
        "uri": "turbo://fragment/web",
        "pull_to_refresh_enabled": true
      }
    }
  ]
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the most important file as it determines the navigation on your Turbo-enabled app. We will cover Path Configuration in a future article, but I highly recommend reading &lt;a href="https://github.com/hotwired/turbo-android/blob/main/docs/PATH-CONFIGURATION.md"&gt;this documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Next, we will create a &lt;code&gt;WebFragment&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Place this in the same directory as your &lt;code&gt;MainActivity&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It will have the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import dev.hotwire.turbo.fragments.TurboWebFragment
import dev.hotwire.turbo.nav.TurboNavDestination
import dev.hotwire.turbo.nav.TurboNavGraphDestination

@TurboNavGraphDestination(uri = "turbo://fragment/web")
open class WebFragment : TurboWebFragment(), TurboNavDestination {}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We add this to the list of registered fragments in our &lt;code&gt;MainSessionNavHost&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;override val registeredFragments: List&amp;lt;KClass&amp;lt;out Fragment&amp;gt;&amp;gt;
    get() = listOf(
        WebFragment::class
    )

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are so close to getting up and running. There are just three little things to do.&lt;/p&gt;

&lt;p&gt;First, we create our &lt;code&gt;layout_main&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Click Resource Manager &amp;gt; Layout &amp;gt; + Icon &amp;gt; Layout Resource File&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m9xRxSQk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://williamkennedy.ninja/assets/images/posts/android/part_1/android_studio_resource_layout.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m9xRxSQk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://williamkennedy.ninja/assets/images/posts/android/part_1/android_studio_resource_layout.png" alt="alt text" title="Android Studio resource layout" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mine looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; &amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"&amp;gt;

    &amp;lt;androidx.fragment.app.FragmentContainerView
        android:id="@+id/main_nav_host"
        android:name="com.example.thirdturbo.MainSessionNavHost"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="false" /&amp;gt;

&amp;lt;/androidx.constraintlayout.widget.ConstraintLayout&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we have to edit our Android Manifest(found in the top-level directory) file to ensure we can access the internet and localhost.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"&amp;gt;
    &amp;lt;uses-permission android:name="android.permission.INTERNET"/&amp;gt;

    &amp;lt;application
        android:usesCleartextTraffic="true"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.ThirdTurbo"
        tools:targetApi="31"&amp;gt;
    &amp;lt;activity
        android:name=".MainActivity"
        android:exported="true"&amp;gt;
        &amp;lt;intent-filter&amp;gt;
            &amp;lt;action android:name="android.intent.action.MAIN" /&amp;gt;

            &amp;lt;category android:name="android.intent.category.LAUNCHER" /&amp;gt;
        &amp;lt;/intent-filter&amp;gt;
    &amp;lt;/activity&amp;gt;
&amp;lt;/application&amp;gt;

&amp;lt;/manifest&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you should be able to click the Run button and watch it build your app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ffmPVhKD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://williamkennedy.ninja/assets/images/posts/android/part_1/android_studio_run.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ffmPVhKD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://williamkennedy.ninja/assets/images/posts/android/part_1/android_studio_run.png" alt="alt text" title="Android Studio run button" width="363" height="54"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congratulations, you now have a Turbo-enabled Android app.&lt;/p&gt;

&lt;p&gt;In the next part, we’ll take the app further by adding more Turbo-enabled fragments, such as modals and native screens.&lt;/p&gt;

</description>
      <category>turbo</category>
      <category>hotwire</category>
      <category>android</category>
      <category>native</category>
    </item>
    <item>
      <title>Turbo Native - How to Access Native iOS features with the JavaScript bridge</title>
      <dc:creator>William Kennedy</dc:creator>
      <pubDate>Sun, 13 Nov 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/williamkennedy/turbo-native-how-to-access-native-ios-features-with-the-javascript-bridge-4686</link>
      <guid>https://dev.to/williamkennedy/turbo-native-how-to-access-native-ios-features-with-the-javascript-bridge-4686</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j_U0MRvV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://williamkennedy.ninja/assets/images/posts/hello_iphone.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j_U0MRvV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://williamkennedy.ninja/assets/images/posts/hello_iphone.jpg" alt="alt text" title="iPhone 8 with Hello text on screen" width="640" height="471"&gt;&lt;/a&gt;Photo by &lt;a href="https://unsplash.com/@lastly?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Tyler Lastovich&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/iphone?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Turbo Native - How to Access Native iOS features with the JavaScript bridge
&lt;/h1&gt;

&lt;p&gt;Turbo Native is described by Basecamp as allowing you to build “high-fidelity” cross-platform apps. What exactly does this mean? To me, it’s the ability to build cross-platform apps that allow you to access the native functionality without the caveat of building the same functionality across the many different platforms. In this article, we’ll cover how we can access native functionality by writing just a tiny bit of Swift but mostly, just sticking to Rails.&lt;/p&gt;

&lt;p&gt;In my &lt;a href="https://williamkennedy.ninja/ios/2022/09/12/how-to-render-a-native-home-screen-with-turbo-ios/"&gt;last article&lt;/a&gt;, I covered navigating to a native screen via tabs. You can also navigate to native screens via the &lt;a href="https://github.com/hotwired/turbo-ios/blob/main/Docs/PathConfiguration.md"&gt;Path Configuration&lt;/a&gt;. In this article, we’re going to touch on the JavaScript bridge by building two features. One that sends an event to the iOS app via JavaScript and another that sends an event to the browser via the iOS app.&lt;/p&gt;

&lt;p&gt;For this, we’re going to access the &lt;a href="https://developer.apple.com/documentation/contacts/cncontactstore/"&gt;phone’s contacts&lt;/a&gt;. This was a feature I was asked about recently. Here’s what it looks like.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UqPiHceR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://williamkennedy.ninja/assets/images/posts/ios_contacts_demo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UqPiHceR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://williamkennedy.ninja/assets/images/posts/ios_contacts_demo.gif" alt="Access Contacts demo" width="396" height="798"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this, you’re going to have to build off my &lt;a href="https://williamkennedy.ninja/ios/2022/09/12/how-to-render-a-native-home-screen-with-turbo-ios/"&gt;last post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This post is aimed at being step-by-step so you can get an idea of the link between Turbo Native and your Rails app. If you get stuck following along, feel free to email me and let me know.&lt;/p&gt;

&lt;p&gt;The code can be found &lt;a href="https://github.com/williamkennedy/ios_blog/tree/js_bridge"&gt;here&lt;/a&gt; and &lt;a href="https://github.com/williamkennedy/ios_blog_rails/tree/js-bridge"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Set up the JavaScript bridge
&lt;/h2&gt;

&lt;p&gt;Sending messages to the iOS app is our first port of call. We do this by implementing a JavaScript bridge. In the Rails app that we created in the previous post, add the following to our &lt;code&gt;app/javascript/application.js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
class Bridge {

  // Sends a message to the native app, if active.
  static postMessage(name, data = {}) {
    // iOS
    window.webkit?.messageHandlers?.nativeApp?.postMessage({name, ...data})
  }
}

// Expose this on the window object so TurboNative can interact with it
window.Bridge = Bridge
export default Bridge

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we’ll create a simple stimulus controller for a proof of concept. We just want to see if we can send a message to our iOS app and react to it. In &lt;code&gt;app/javascript/controllers/hello_controller.js&lt;/code&gt;, change the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    window.Bridge.postMessage("hello")
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now connect the controller to the DOM. In &lt;code&gt;app/views/posts/index.html.erb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div data-controller='hello' id="posts"&amp;gt;
  &amp;lt;% @posts.each do |post| %&amp;gt;
    &amp;lt;%= render post %&amp;gt;
    &amp;lt;p&amp;gt;&amp;lt;%= link_to "Show this post", post %&amp;gt;&amp;lt;/p&amp;gt;
  &amp;lt;% end %&amp;gt;
&amp;lt;/div&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now for the final step, we need to open up our iOS codebase and add a handler for our scripts.&lt;/p&gt;

&lt;p&gt;In our iOS codebase, add a new folder called Models and then add a new file called &lt;code&gt;ScriptMessageHandler.swift&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import WebKit

class ScriptMessageHandler: NSObject, WKScriptMessageHandler {
  func userContentController(
      _ userContentController: WKUserContentController,
      didReceive message: WKScriptMessage
      ) {
    print("JavaScript message received", message.body)
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we just have to adjust our Turbo Coordinator to allow us to react to JavaScript messages. In &lt;code&gt;TurboCoordinator.swift&lt;/code&gt;, add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
import SafariServices
import WebKit

class TurboCoordinator: Coordinator {
  ...
  private func makeSession() -&amp;gt; Session {
    let configuration = WKWebViewConfiguration()
    let scriptMessageHandler = ScriptMessageHandler()
    configuration.userContentController.add(scriptMessageHandler, name: "nativeApp")
    let session = Session(webViewConfiguration: configuration)
    session.delegate = self
    session.pathConfiguration = PathConfiguration(sources: [
        .file(Bundle.main.url(forResource: "PathConfiguration", withExtension: "json")!),
    ])
    return session
  }
  ...
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now build and run the app and take a look at our output in Xcode.&lt;/p&gt;

&lt;p&gt;You should see something like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JavaScript message received {
  name = "Hello from Rails";
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will notice that you’ll actually receive it when the app loads up the first time. This is because the tab connects to the DOM straight away.&lt;/p&gt;

&lt;p&gt;If we open the tab with the Rails app, you will see navigating between posts will log in XCode.&lt;/p&gt;

&lt;p&gt;Even though we only got to Hello World, we have laid the foundation to go a bit further and extend on what we are doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  React to JavaScript Messages
&lt;/h2&gt;

&lt;p&gt;Now we have set the stage to react to JavaScript messages. Let’s do something bit more interesting than “Hello World”. Let’s go back to our original goal of retrieving the contacts from the iPhone.&lt;/p&gt;

&lt;p&gt;First, we need to expand on our &lt;code&gt;ScriptMessageHandler&lt;/code&gt; to make it a bit smarter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import WebKit

class ScriptMessageHandler: NSObject, WKScriptMessageHandler {
  func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage ) {
      guard
        let body = message.body as? [String: Any],
        let msg = body["name"] as? String
          else {
            print("No call")
              return
          }
          handleMessage(ScriptMessageHandler.MessageTypes(rawValue: msg) ?? ScriptMessageHandler.MessageTypes.none)
    }

      private func handleMessage(_ messageType: MessageTypes) -&amp;gt; String? {
        switch messageType {
          case .hello:
            print("hello world")
              return nil
          case .contacts:
              print("contacts")
                return nil
          case .none:
                print("No message")
                  return nil
        }
      }

      enum MessageTypes: String {
        case hello = "hello"
        case contacts = "contacts"
        case none = "none"
      }
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now rerun and build the app again. You should still see Hello World in the terminal but this time, we added a enum that allows us to start expanding our functionality.&lt;/p&gt;

&lt;p&gt;Let’s see can we print off the contacts from the local Address book.&lt;/p&gt;

&lt;p&gt;To keep things simple for the tutorial, we’ll keep everything in the ScriptMessageHandler.&lt;/p&gt;

&lt;p&gt;First off, we import the contacts library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Contacts

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we add a method that will simply print off the contacts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func fetchContacts() {
  let store = CNContactStore()
    var contacts = [CNContact]()
    let keys = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName)]
    let request = CNContactFetchRequest(keysToFetch: keys)

    do {
      try store.enumerateContacts(with: request) { (contact, stop) in
        contacts.append(contact)
      }
    } catch {
      print(error.localizedDescription)
    }
  for contact in contacts {
    print(contact.givenName, contact.familyName)
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we update our handleMessage function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private func handleMessage(_ messageType: MessageTypes) -&amp;gt; String? {
  switch messageType {
    case .hello:
      print("hello world")
        return nil
    case .contacts:
        fetchContacts()
          return nil
    case .none:
          print("No message")
            return nil
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in our Rails app, we do the following in &lt;code&gt;app/javascript/controllers/hello_controller.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    window.Bridge.postMessage("contacts", {name: "hello"})
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now rebuild our iOS app and see it crash.&lt;/p&gt;

&lt;p&gt;The reason for the crash is because we need to get add permission to our native app for contacts information. In our Info.plist, we have to add &lt;code&gt;NSContactsUsageDescription&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once that is added, then rebuild the app.&lt;/p&gt;

&lt;p&gt;You will only have to grant permission once. When permission is granted, you should see the output printed in Xcode.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kate Bell
Daniel Higgins
John Appleseed
Anna Haro
Hank Zakroff
David Taylor

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So for, so good. Now let’s send that information to our Rails app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Send Information to the Rails app
&lt;/h2&gt;

&lt;p&gt;So we’re printing to the Xcode console but now let’s send it back to the Rails app. For this, we’re going to print the names with JavaScript via &lt;code&gt;console.log&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here we’re going to introduce a concept that Ruby programmers might not be familiar with and that is delegates.&lt;/p&gt;

&lt;p&gt;Delegates in swift are classes that do &lt;a href="https://stackoverflow.com/questions/24099230/delegates-in-swift"&gt;work for other classes&lt;/a&gt;. They have 3 parts.&lt;/p&gt;

&lt;p&gt;A protocol with the methods, a class(or delegatee class) and the class that is delegating.&lt;/p&gt;

&lt;p&gt;It took me some time to get them so if this is a new concept, then feel free to reach out.&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;ScriptMessageHandler&lt;/code&gt;, we start by adding a protocol.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protocol ScriptMessageDelegate: AnyObject {
  func evaluate(_ name: String)
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in the &lt;code&gt;ScriptMessageHandler&lt;/code&gt; itself, we do the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ScriptMessageHandler: NSObject, WKScriptMessageHandler {
  weak var delegate: ScriptMessageDelegate?

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we move back to our TurboCoordinator and add an extension.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;extension TurboCoordinator: ScriptMessageDelegate {
  func evaluate(_ name: String) {
    session.webView.evaluateJavaScript("console.log('\(name)')")
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we are ready to assign the scriptMessageHandler delegate to the TurboCoordinator.&lt;/p&gt;

&lt;p&gt;We do this in the TurboCoordinator’s &lt;code&gt;makeSession()&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private func makeSession() -&amp;gt; Session {
  let configuration = WKWebViewConfiguration()
  let scriptMessageHandler = ScriptMessageHandler()
  scriptMessageHandler.delegate = self
  ...
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you can rebuild the app and navigate to the tab. Navigating back and forth, you’ll see logs in both Xcode and if you open Safari &amp;gt; Develop &amp;gt; Simulator, you’ll see JavaScript being printed to the console.&lt;/p&gt;

&lt;p&gt;Now that we’ve covered the two way communication without having to integrate an API, let’s finish off our project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate a Contacts Scaffold
&lt;/h2&gt;

&lt;p&gt;Let’s generate a Rails scaffold called contacts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails g scaffold Contacts name:string

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now migrate your database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails db:migrate

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we just need to make it easy to navigate to our contacts page. In our &lt;code&gt;posts/index.html.erb&lt;/code&gt;, change it to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;%= link_to "New post", new_post_path %&amp;gt;
&amp;lt;%= link_to "Contacts", contacts_path %&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s add a stimulus controller for triggering the importing of contacts from iOS then add it to our &lt;code&gt;contacts/index.html.erb&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Let’s create a file called &lt;code&gt;app/javascript/controllers/contacts_controller.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  import() {
    window.Bridge.postMessage("contacts", {name: "contacts"})
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we add it to our &lt;code&gt;app/views/contacts/index.html.erb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;p style="color: green"&amp;gt;&amp;lt;%= notice %&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;h1&amp;gt;Contacts&amp;lt;/h1&amp;gt;

&amp;lt;button data-controller='contacts' data-action='contacts#import'&amp;gt;Import&amp;lt;/button&amp;gt;

&amp;lt;%= turbo_stream_from :contacts %&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we broadcast over a turbo stream when a new contact is created, in &lt;code&gt;app/models/contacts.rb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Contact &amp;lt; ApplicationRecord
  after_create_commit { broadcast_append_to :contacts, target: :contacts }
end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now there is only two more changes left:&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;app/javascript/application.js&lt;/code&gt;, add the following method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
import "controllers"
import "trix"
import "@rails/actiontext"

class Bridge {

  // Sends a message to the native app, if active.
  static postMessage(name, data = {}) {
    // iOS
    window.webkit?.messageHandlers?.nativeApp?.postMessage({name, ...data})
  }

  static importingContacts(name) {
    const csrfToken = document.getElementsByName("csrf-token")[0].content;
    fetch('/contacts.json', {
          method: 'POST',
          headers: {
          "X-CSRF-Token": csrfToken,
          'Content-Type': 'application/json',
          "Accept": "application/json",
          },
          body: JSON.stringify({contact: { name } })
          }).then((response) =&amp;gt; response.json())
          .then((data) =&amp;gt; {
              console.log("Success:", data);
              })
          .catch((error) =&amp;gt; {
              console.error("Error:", error);
      });
  }
}

// Expose this on the window object so TurboNative can interact with it
window.Bridge = Bridge

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then back in our Swift codebase, change the following in &lt;code&gt;TurboCoordinator&lt;/code&gt;,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;extension TurboCoordinator: ScriptMessageDelegate {
  func evaluate(_ name: String) {
    session.webView.evaluateJavaScript("Bridge.importingContacts('\(name)')")
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, rebuild the app for the final time. Navigate to contacts and press the Import button.&lt;/p&gt;

&lt;p&gt;You should see the names instantly appear thanks to Turbo Streams.&lt;/p&gt;

&lt;p&gt;This feels like magic and opens up a whole world of possibilities for web developers. In the famous words of DHH, “&lt;a href="https://www.youtube.com/watch?v=Gzj723LkRJY"&gt;look at all the stuff I’m not doing&lt;/a&gt;.”&lt;/p&gt;

&lt;p&gt;We’re not building a new native screen. We are just building one screen. We are not creating a new API endpoint, we’re simply using the endpoint provided by the scaffold. In a time where business efficiency will become more and more important, small teams that go down this road will be able to pack a big punch.&lt;/p&gt;

&lt;p&gt;This is just the tip of the iceberg as well. My latest project interacts with MapKit. You can interact with the Haptic engine, the &lt;a href="https://www.hackingwithswift.com/read/37/6/wiggling-cards-and-background-music-with-avaudioplayer"&gt;Audio Player&lt;/a&gt; and more.&lt;/p&gt;

&lt;p&gt;I just want to give a massive shout out to &lt;a href="https://masilotti.com/"&gt;Joe Masilotti&lt;/a&gt; who provided technical and editing feedback on this article. His &lt;a href="https://masilotti.com/turbo-ios/hybrid-apps-with-turbo/"&gt;Turbo Native posts&lt;/a&gt; are the best in the business. I also recommend checking out the &lt;a href="https://jumpstartrails.com/ios?utm_source=williamkennedy"&gt;Jumpstart iOS Pro template&lt;/a&gt; by Joe alongside Chris Oliver of &lt;a href="https://gorails.com/"&gt;GoRails&lt;/a&gt; fame if you are looking to jumpstart your Turbo iOS app.&lt;/p&gt;

&lt;p&gt;Happy Hacking.&lt;/p&gt;

</description>
      <category>turbo</category>
      <category>hotwire</category>
      <category>ios</category>
      <category>native</category>
    </item>
  </channel>
</rss>
