The Hybrid Approach
Most mobile apps today are written mostly in Swift, Flutter, or React Native. At Really Good Software we've taken a different approach. We use Webviews (the mobile equivalent to iFrames), served as html from a Ruby on Rails application, as the primary mechanism for building mobile app screens.
Benefits of a Webview Driven Approach
Faster development speed
Faster feedback loops during testing (pull to refresh, open in browser)
Get a web app "for free"
Add extensive features without waiting for App Store Release
Fix bugs quickly without waiting for App Store Release
Drawbacks of a Webview Driven Approach
Can have nasty bugs based on syncing logged-in-status
✅ Yes
Webviews can't do "Mobile Native" UX like stacks and modals
✅ Yes
Links and forms in webviews don't have "slide-over" transition effects
✅ Yes
Device-level features are harder to access
Somewhat
Users with slow connections have a somewhat degraded experience
❌ No
How Our Approach Works
We have a component called
<WebViewStack />
which takes an initial url. Usually we have one per each tab in the app.When a link is clicked, it is intercepted by
onShouldStartLoadWithRequest
and we check a few things.Should we create a whole new Stack Context for the next page?
Should we execute any native functions?
Then, an entirely new webview is instantiated, with the next url, and depending on the parameters in the url, appears from the right, as a modal, or as a full screen modal.
Gotchas & Weird Stuff
A note on Top Navigation (The Header)
The "standard" way to do a top header on mobile is to use the one Apple or Android gives us. This means that there is a single Top Bar component that is used on every page, and it's behaviour changes depending on the screen the user is on. But in practical usage, we came across so many edge cases that our recommendation now is to ditch the Native Header, and build the header behaviour into the webviews themselves. This means we lose the transition behaviour where the back button appears on the left side.
Some of the challenges we had:
Conditionally showing & hiding the header - we can of course pass this from our webview to our header component, but we got into issues of timing (the header either appears too early or too late), and finding a nice UX here was very hard.
Defining consistent back navigation: When an app screen has a carousel in it - which we do often - navigating to the next slide doesn't trigger a navigation event in the router. This means the user can get several slides in, and when they hit the back button, it takes them back to outside the carousel, which from their perspective is several steps backward and bad UX. Of course we can wire this up so that the back button on the top bar is aware of the carousel steps, but the code starts to get super complex and difficult to read and update.
Last updated