Turbo Native and pull-to-refresh
Earlier this week someone asked me on my Discord how to configure pull-to-refresh in their Turbo Native app. Matthew, the developer, is building an iOS and Android app that translates scribbled text to letters and numbers.
Drawing horizontally works fine. But every time you draw down the page refreshes.
This gesture is triggering the pull-to-refresh control added to the web view by Turbo Native. Giving the user a way to refresh a page makes sense… but not for this screen. So what’s a developer to do?
Turns out this is one of the many things we can configure without writing any native code. Everything can be configured via a path configuration rule for both platforms.
Path configuration on Android
From my last guide, Turbo Native iOS and Android apps in 15 minutes:
The path configuration is a JSON file that outlines a set of rules and settings for Turbo Native apps. On Android, it tells the library which web pages should be rendered via which fragment. It can also be used to configure modals, route native screens, and more.
Here is the minimum you need to get a path configuration working in Turbo Android:
{
"settings": {
"screenshots_enabled": true
},
"rules": [
{
"patterns": [
".*"
],
"properties": {
"context": "default",
"uri": "turbo://fragment/web"
}
}
]
}
The settings
key enables screenshots via screenshots_enabled
. When navigating back, a snapshot of the previous screen will be shown until the view finished loading (instead of a blank screen).
And the rules
key declares an array of routing rules. Whenever a link is tapped the patterns
key matches the URL path to determine what behavior to apply. The single rule used here routes all URL paths via the .*
wildcard to the fragment identified by turbo://fragment/web
.
We want to disable pull-to-refresh on specific pages. First, enable pull-to-refresh in the wildcard rule making it the default for all screens.
{
"settings": {
"screenshots_enabled": true
},
"rules": [
{
"patterns": [
".*"
],
"properties": {
"context": "default",
"uri": "turbo://fragment/web",
"pull_to_refresh_enabled": true
}
}
]
}
Then add a second rule that disables the feature. Here we match all URLs that end in /one
.
{
"settings": {
"screenshots_enabled": true
},
"rules": [
{
"patterns": [
".*"
],
"properties": {
"context": "default",
"uri": "turbo://fragment/web",
"pull_to_refresh_enabled": true
}
},
{
"patterns": [
"/one$"
],
"properties": {
"pull_to_refresh_enabled": false
}
}
]
}
Why /one
? Tapping “Navigate to another webpage” in the demo app takes you to this URL, making it easy to test. Try it out by running the app and noticing where you can and cannot pull-to-refresh.
In your app make sure to change that path pattern to whatever makes sense.
With Android done let’s switch gears to iOS.
Path configuration on iOS
The iOS side of my guide didn’t cover adding a path configuration because it isn’t needed to get started. So the first thing we need to do is wire one up. Sorry, you will have to write a bit of native code - but only two lines!
We’ll start with where the guide left off - a barebones Turbo Native iOS app. If you’re using your own project then make sure you are using the turbo-navigator branch of turbo-ios. Here’s what the SceneDelegate.swift
looks like at the end of the guide:
import Turbo
import UIKit
let baseURL = URL(string: "https://turbo-native-demo.glitch.me/")!
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
private let navigator = TurboNavigator()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
window?.rootViewController = navigator.rootViewController
navigator.route(baseURL)
}
}
First, create a new path configuration JSON file to bundle with your app. Click File → New → File… and select Empty from the Other heading.
Name the file path-configuration.json
and make sure the checkbox next to the app target is checked.
When the file opens replace the contents with the following path configuration:
{
"settings": {},
"rules": [
{
"patterns": [
"/one$",
],
"properties": {
"pull_to_refresh_enabled": false
}
}
]
}
We don’t need to configure any custom settings so the settings
key gets an empty hash. By default Turbo Native iOS automatically enables pull-to-refresh, so we only need a single rule to disable it on specific screens.
Open SceneDelegate.swift
and create a new private property named pathConfiguration
. Point this to a local .file
source for the path-configuration.json
file bundled with your app.
import Turbo
import UIKit
let baseURL = URL(string: "https://turbo-native-demo.glitch.me/")!
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
private let navigator = TurboNavigator()
private let pathConfiguration = PathConfiguration(sources: [ // <---
.file(Bundle.main.url(forResource: "path-configuration", withExtension: "json")!) // <---
]) // <---
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
window?.rootViewController = navigator.rootViewController
navigator.route(baseURL)
}
}
Finally, pass pathConfiguration
into the TurboNavigator
instance. Make sure to change let
to lazy var
to avoid the race condition.
import Turbo
import UIKit
let baseURL = URL(string: "https://turbo-native-demo.glitch.me/")!
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
private lazy var navigator = TurboNavigator(pathConfiguration: pathConfiguration) // <---
private let pathConfiguration = PathConfiguration(sources: [
.file(Bundle.main.url(forResource: "path-configuration", withExtension: "json")!)
])
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
window?.rootViewController = navigator.rootViewController
navigator.route(baseURL)
}
}
Build and run the app - now every URL that ends in /one
will no longer have a pull-to-refresh control installed. Perfect!
What’s next?
This guide touched on one of the ways to drive behavior in the apps from your Rails server. You learned how to set up a path configuration on iOS and then use it to disable pull-to-refresh on specific screens.
I recommend reading through the iOS and Android path configuration documentation next. Is there anything else that you can use to move more configuration from the native codebase to your server?