Home Blog CV Projects Patterns Notes Book Colophon Search

App Development

30 Dec, 2023

Every now and then on Hacker News there is a long thread like this one on whether to use ExpoJS, Progressive Web Apps or SwiftUI and Kotlin for developing native apps. I thought I'd share my take.

Firstly, production apps are being built in React Native and deployed on the web. kick.com is an example. So it is perfectly possible to use a single React Native codebase for iOS/Android/Web and then bundle the web code in Electron for desktop.

The two main downsides I've found of this are that your web/desktop code looks a bit like it has been built for a phone rather a web/desktop app (because of both the aspect ratio differences and the navigation pattern differences), and that the bundle size is about 1.5Mb. If you are building a modern app for Western audiences on fibre broadband neither of these is likely a big dealbreaker.

So, I think React Native is good if you have a primarily app use case that needs a secondary web/desktop experience and where you want the UI to feel reasonably native on both iOS and Android.

Now, in reality, you often want your app to have its own look and feel that is consistent on all platforms. In that case using native components with their native iOS and Android look and feel isn't very useful. Also I personally tend to build apps that are primarily web apps and only benefit from a few native features.

So I would build my app as a single page app first (probably in React with hooks, React Router and React Aria, rather than say Web Components/Stencil) and then embed that app in a native runtime to access the native features I required.

Two choices for the native runtime are Capacitor or Expo JS with the Web View. I would choose the latter. The ability to develop and test an iOS app on Linux is really useful, as are hot reloading and over the air updates. Most of all I just like people being able to try the app during development using Expo Go and scanning the QR code. Also the React Native ecosystem of native adapters is bigger.

Then all that is required is an interface in your React code for accessing either the server/device/browser and you write one implementation for each, giving a great experience on all devices, but without needing to write multiple codebases. This is what Standard Notes do for example.

On the backend I would build everything important in PostgreSQL using functions like this with row level security based on this for reports, search and most screens. I’d use RDS or Aurora Serverless v2 in production, with a thin Lambda Python adapter all deployed via CloudFormation. Any temporary data could use DynamoDB tables with a time to live set and files would be stored in S3. Complex data that needed to be retrieved fast could also use DynamoDB. I'd use API Gateway v2 HTTP API for JWT checking and I'd use Apple and Google login (and perhaps username/password) for auth into my own code that issued a JWT. I'd verify the email received from SSO via an email confirmation using AWS SES. Any realtime notifications needed can be handled using AWS AppSync with a null resolver (or I might just use polling or Firebase).

The beauty of these choices is that everything can be run/tested locally with open source components and without needing physical hardware. Whilst in production it runs natively and can scale without management of any servers. I feel that these choices always 'go with the grain' as much as possible rather than fighting it or aiming for perfection.


Be the first to comment.

Add Comment

Copyright James Gardner 1996-2020 All Rights Reserved. Admin.