uFincs Update #13

What comes after import rules?

Today, on matters concerning my existence, is a discussion on what the heck comes next after I spent far too much time on the Import Rules feature.

Last Time

uFincs Update #12 was just a preview of the new Import Rules feature (of which the official announcement can be found here).

I anticipated this being a rather large feature to implement and — lo and behold — it was! I originally estimated that it would take 4-6 weeks, then 4-8 weeks, and it ended up taking the full 8 weeks. Just another grand example of Parkinson's Law at work!

Anyways, that's in the past. Now we have to discuss the future. What exactly is next for uFincs?

There's one large elephant in the room that I want to talk about: native apps.

Native Apps

A 'feature' that has been highly requested (for good reasons), native apps have been something that I've just kept pushing back more and more. This has been for primarily two reasons:

  1. There is a large amount of upfront work to build them, even taking into account all of the efforts I've gone to ensure that we can re-use as much code as possible.

  2. There is a non-insignificant time cost (mostly related to development velocity) to supporting more platforms than 'just' web.

As much as everyone (myself included) wants there to be native apps for uFincs, I've found it hard to justify their development. Sticking with 'just' a web app seems so much simpler and easier, especially for the company-of-one that is uFincs.

And while I had included them as part of my roadmap for 2021, if I was going to go about building them in React Native (my original plan), then they would have likely taken up the majority of my 2021 development time by themselves. Weighing feature development vs native app development (vs marketing vs ...) is always a tough decision, with features generally winning out with web apps since web apps technically do work on all platforms.

However, I recently came across a piece of technology while reading Hacker News that, quite frankly, I probably should have already known about. However, in my defense, if I didn't know about it, then it certainly stands to reason that maybe they are lacking in their own marketing department.

In any case, I'd like to do some of their marketing work for them and introduce you to Capacitor JS.

Capacitor

From the team behind Ionic, Capacitor is a "cross-platform native runtime for web apps". In other words, you just plop the @capacitor/core package in your web app, run a command or two to bootstrap the files for an Android and/or iOS app, and now you've got your web app running in a 'native' Android/iOS app.

Quite literally.

I know there have been several attempts to achieve such an experience in the past, but wow, Capacitor is the first one I've tried that actually nails it. Heck, it took me longer to get a stupid Android emulator working than it did it to integrate Capacitor into uFincs!

Within maybe half an hour, I'd gone from "installing Capacitor" to "uFincs running as a native app on an emulated device". Now, to be fair, that half an hour did not include having uFincs working on a native device (I only said running). There was still a bit more work to be done to get all of the networking config in place so that the app could talk to my local API server (which, again, is more the fault of the Android development experience than anything to do with Capacitor).

But still. That turnaround time is insane compared to what it would take to, say, rebuild the whole app (mainly the UI) in React Native. I'm talking like 1 hour vs several months here.

So how does Capacitor do it? Well, as far as I can tell, it's not especially more innovative than other solutions have been in the past. It basically just takes the built static assets from your web app and serves them inside a web view that is rendered within the native app. It also does a bunch of bridging to allow the web code to talk to native code for features like push notifications (aka stuff I don't really care about for uFincs). The core idea is still just "run the web app in a web view with a native shell", but with a more refined and polished development experience.

Obviously, if uFincs didn't already have a mobile-optimized design, then all this Capacitor magic would be a rather moot point. So you could argue that the many months I spent in 2020 redesigning the uFincs UI would be where all the 'work' has gone.

But all I really care about right now is the marginal work it would take to go from "uFincs as a web app" to "uFincs as a native app that can be installed from the app stores". In this case, Capacitor seems to shrink that time down dramatically, again relative to my original plan of using React Native.

The Spike

In that vein, I decided to take on the Capacitor integration as a 'spike' (aka a short-term experiment or proof-of-concept, but you know how those Agile folks love their buzzwords). After my initial experimentation with getting Capacitor working with Android, I wanted to see if I could then get it working on iOS and desktop, and then get it as close as possible to a 'publishable' version (aka iron out the kinks).

iOS ended up taking a bit more work, mostly due to styling issues. Since the web view on iOS is still just Safari, I ended up having to deal with all the same quirks/bugs I ran into when first testing on Safari. They all happened again because the "is Safari?" logic that I had relied on checking the user agent (an already unreliable indicator), but the iOS web view didn't have the same user agent. Thankfully, it was mostly just a matter of adding a Capacitor.getPlatform() === "ios" check.

Additionally, I decided to switch from using IndexedDB to using Capacitor's Storage plugin for redux-persist's storage backend, just as a more reliable way for storing user data. I know that Capacitor doesn't recommend using the Storage plugin for large amounts of data, but I'll need to do some further performance testing to see if it really hampers anything as a user scales to thousands of transactions.

Another big change I did (for Android and iOS) was remove every mention of signing up or subscribing. Not from past experience or anything, but I know that the app stores have been particularly stringent when it comes to managing paid subscriptions through anything but their own payment services. I figured preemptively removing all such mentions would save me some headaches in the future (if you want such an example of a headache, check out this latest situation).

Yes, this makes for a worse user experience (since one could download the app and be greeted with only an option to log in but not sign up), but since we provide the ability to use the app without an account, I think the compromise is worth it.

As far as desktop goes, while Capacitor doesn't have first-class support for something like Electron, there is a community-built plugin for adding Electron as a Capacitor platform. Which, I will say, worked basically flawlessly — just as the Android and iOS platforms did.

The final 'hurdle' was changing out the app icons for our own. There's an officially supported tool called cordova-res that can handle generating the necessary icon assets from a base file, but I found it to not generate the best assets for Android (they were sized incorrectly); I ended up just using Android Studio's built-in asset generator for the Android app. The iOS icons turned out fine, however.

Altogether, after a week's worth of work and play, the "Capacitor spike" resulted in the ability to build uFincs as a native Android app, a native iOS app, and an Electron app for Windows, Mac, and Linux. AKA, everything that matters.

However, this was only the spike — the beginning. I don't think the native apps are yet ready to be published, but I'll elaborate on that in a bit.

First, I want to take a short minute to discuss the catches.

The Catches

So what are the catches? Surely there must be catches right?

Well, it depends on how pedantic and nitpicky you want to be.

As much as the app that Capacitor generates is indeed a 'native' app (from a binary/distribution point-of-view), it doesn't make use of the traditional native UI elements. So if you're an utter stickler for only using those widgets which have been blessed by the designers-that-be at Google and Apple, then you probably think this approach is pretty sacrilegious!

However, that's a mostly moot point as far as uFincs is concerned, because if I were to build it in React Native, I certainly wouldn't be going out of my way to re-re-design the UI to match each system's native elements. That, my friends, is too much.

Other than that, I suppose there might be some superficial performance penalties to choosing this hybrid approach. Not that there wouldn't also be any with React Native.

So... I don't know what the catches are yet. Personally, I think this Capacitor approach is just pure upside. I get to re-use the exact same code base, everyone gets the exact same uFincs experience, but I still get to distribute on the native app stores.

Plus, from a security standpoint, this approach still gives me the native app 'benefit' of not worrying about the Frontend server being compromised and immediately serving a version of the app that breaks all of the encryption features. Of course, if I really wanted to trash my company and entire career, I (or a malicious actor) could still push a compromised update through the app stores, but for those that want an "install once" experience, the Capacitor approach still affords that.

So, with only a week's worth of Capacitor experience under my belt (and not even a published app to my name), I can super confidently say that there are no catches.

The Next Steps

While uFincs has now been made 'technically' fully functional as a native app, as is true of all things, the 80/20 rule still applies. They're still a few wrinkles to iron out.

Small things like "how do you fetch the latest data?". With the web app, you can just refresh the page and all your latest data is pulled down. Well, you can't exactly "refresh the page" with a native app, so we'll need either a button to do it manually or a process that runs in the background (probably both).

Then there are things like setting up a new CI/CD pipeline. I mean, since the code is all the same, that also means that all the same tests should apply, but how exactly do we handle things like "continuous deployment" with mobile apps? I don't think we actually do, but it's the process of figuring out how we're going to handle deployment/distribution that is important.

Speaking of which, app stores. God, app stores. I have been dreading the moment I'd have to start dealing with them. It's one thing having a web app where I have complete and utter control over the entire deployment process, being able to deploy anything and everything at the push of a commit. It's quite another having to submit every update to entities that are known for being both much slower and much less reliable than my precious build pipeline.

In the best case, maybe the uFincs native apps will be available (as a beta) within the next month or two. Assuming I don't get arbitrarily rejected from the app stores for no reason. ¯\_(ツ)_/¯

Final Thoughts

Other than the native apps, I've been slowly getting through some other (much smaller) items from my backlog. Check out the uFincs changelog for all the details.

As I've mentioned in the past, I've also been planning another big marketing push. If I can get the native apps into beta, then that'll probably happen sometime around September/October. Also got some blog posts that I need to finish up for that. Hopefully, this push will be enough to pick up a couple more customers.

Can never have enough customers :)

Till next time.