An indie’s journey rewriting MobiLinc in React Native

This may just be another Medium post about rewriting an app in React Native, but I’m here to add a very honest indie perspective on the successes, failures, and realities of using RN as a single platform for iOS and Android app development.

TL;DR — RN nearly lived up to the write once, deploy everywhere goal but not without some real challenges to solve especially on the Android platform. Would I use RN on my next project…absolutely…well, maybe.

The new MobiLinc X app re-written in React Native

First some background. I wrote the original MobiLinc for iOS back in 2009 with the Android version following in 2010. MobiLinc is a home automation app for the popular ISY-994i controller from Universal Devices. With MobiLinc, users control their INSTEON and Z-Wave lights, thermostats, irrigation, garage door, etc. What started off as a hobby in 2009, I was able to leave my full-time job in 2011 to pursue MobiLinc app development full time. I’ve been fortunate that 8 years later I’m still independent, still pushing MobiLinc forward for my own interests and my community.

MobiLinc wasn’t the first or last casualty of lopsided app revenue streams.

Both my personal interests and customer base skew toward iOS. When my Android developer (rightly) couldn’t justify spending time on continuing developing the MobiLinc Android port where the income didn’t cover his expenses, my Android version fell behind the iOS version. From a business perspective, it’s just a raw numbers call, but one that wasn’t a great experience for my Android users or my cross-platform users. MobiLinc wasn’t the first or last casualty of lopsided app revenue streams.

Any app that’s been around for 10 years needs a new coat of paint. The question was, do you overhaul the existing app or start over? And what do I do about my lagging Android version?

I decided a little over a year ago to do a complete re-design of MobiLinc and rewrite it in React Native, simplify pricing into a three-tier app subscription model and call it MobiLinc X. The main goal I had for the rewrite was to maintain one code base for iOS and Android. There is real value in offering an app solution to my Android users, but only if I can reuse the majority of the code used to produce the iOS version. Separate code bases just haven’t made financial sense for my market.

I’m far too comfortable with rebooting, deleting all npm packages, and cleaning everything when all else fails.

Most of 2018 was getting my backend ready for the rewrite plus doing design iterations for the new MobiLinc app, MobiLinc X. In October 2018 I started in on standing up the base project for MobiLinc X. Being new to RN, I spent a couple of weeks experimenting with the platform and playing with different RN stacks.

One thing I quickly figured out was RN in late 2018, while it has come a long way, it’s still like the Wild West out there. Tooling works and breaks down for no obvious reason. I’m far too comfortable with rebooting, deleting all npm packages, and cleaning everything when all else fails. Plus, you can make early design decisions that will sentence you to write and think way too much about boiler code.

Being sensitive to how many characters I ultimately needed to type as an indie dev, I went with the well designed open source RN stack components from WIX. Namely remx for state management and React Native Navigation V2.

Hot Reloading is dark magic and I can’t not use it.

In October 2018, I found that the base components I wanted to use from WIX seemed to work best with RN 0.56 which was nearly current at the time. By November I was off developing MobiLinc X using iOS as the main development platform. I found that trying to keep Android up and working at the same time as iOS just killed my productivity due to these random issues with RN just not deploying or debugging on iOS occasionally and Android. Any one of these issues could derail my afternoon to figure out. So iOS it was and I’d make Android work once the app was “done” on iOS.

I will say, once my tooling was working together well, Hot Reloading is dark magic and I can’t not use it. Going back to XCode or Android Studio to do native work just feels ancient. Who wants to wait 10 minutes for a build just to test your new 2 point margin change? Not this guy. Being able to write UI RN code and see it update in real-time on device or simulator is an unreal and wonderful experience. The productivity gains are unlike anything else I’ve experienced in my career. The last time I felt this way was when I discovered promises in JS and started using them religiously in iOS development. Goodbye callback hell!

Anyways, I digress. I finished up MobiLinc X in late January 2019 for iOS and it was time to bring up the Android build and test/fix for Android. That’s when things got really interesting.

AsyncStorage is straight up broken on Android.

Android treats UI layouts mostly the same, but I found odd cases where flex works differently and SafeAreaView just does not work anywhere close to how it works on iOS. Sometimes I needed to wrap Lists in a ScrollView on Android to get the layout to draw properly. But the real kicker was AsyncStorage.

AsyncStorage is an RN module that gives your RN code a basic key/value local storage option. AsyncStorage works great on the iOS Simulator and all iOS devices. It is straight up broken on Android 7+. What happens is on Android 7+ devices, calls into AsyncStorage simply don’t return. The promises won’t resolve and hangs the RN app.

Digging into it, the RN team has known about this issue for several years and it isn’t fixed yet. Certainly not in 0.56. I hear this is now separately maintained in 0.59 but really not looking forward to the pain of upgrading RN. I understand that the RN team is listening to feedback in this area and are making improvements to the upgrade experience and flow starting in 0.59 which is great news if you can get to 0.59.

If you made it this far, I can hear you now, “just pick one of the many open source key/value packages for Android.” Well, you would be right, except, my backend and MobiLinc X is using the open source Parse platform for real-time cloud syncing of all data. Parse uses AsyncStorage on mobile and can’t be changed. I was at a crossroads. If I couldn’t find a workaround for the Android AsyncStorage, parse wouldn’t work and I couldn’t deploy on Android.

After a lot of dead-ends, I ended up writing a new Java module that matches the AsyncStorage API and shoehorned that module into RN to use instead of the Android RN AsyncStorage. This was not on my vision board…but felt really fortunate I found a path through it. I have no idea what other’s do to work around this problem. I felt like a lone wolf trying to find my way through it.

The good news was total effort getting the Android version up and running, including adding in-app subscriptions and the unplanned AsyncStorage fix for Android was about 14 days. Looking back on that time commitment, that is a perfectly fine level of effort to essentially get my Android port that is identical to iOS. However, when I was in the middle of it all, and it didn’t look like I had a compatible storage solution for Android, it felt pretty bleak.

Both apps were approved and went live in the App Store in late February 2019.

Should everyone just switch to RN? That really depends.

Overall switching to RN was a good experience for this indie developer but wasn’t without some pain. I couldn’t develop or support two separate 100% native and separate code bases for iOS/Android. Having RN there to reuse essentially 98% of my code/effort was critical in meeting my development and time resources.

Would I use RN again? In this scenario, yes. For a future project? I’d certainly default to RN to see if RN would support the project needs. Should everyone just switch to RN? That really depends. I’m not convinced that mid to large companies that can afford native developers should use RN. The tooling is a real nightmare for just one person. Let alone trying to get a whole team to get their tooling all working and in place to support development. Maybe someday this will make sense, but the tooling needs a lot of work to get there.

I think if I do another RN app for myself or a client, keeping it all in RN and JavaScript, as I did with MobiLinc X is a saner move. Trying to do cross RN/JS and native development in the same app would be quite difficult to manage and keep productivity up for any size team. There may be good reasons to do this, specialized resources and talents, for example, but it wouldn’t be my first choice.

Now, onto upgrading to 0.59 and hooks!