React-Native vs native mobile app

I’m going to raise a discussion about a Status’ taboo subject – a choice between React-Native and native iOS/Android app development. It’s always been in the Top-3 list of discussed topics in private conversations with other devs, and too many people outside of status-react team (and some from inside) were unhappy with this choice.

To make sure everyone on the same page, it’s a classic choice for anyone willing to build a mobile app:

  1. Write native mobile apps for iOS and Android with their native stacks (Swift/iOS and Java/Kotlin)

  2. Use React-Native

Both approaches have a number of cons and pros, in more than one dimension and it often boils down to the personal decision of a CTO. In the very early days of Status, @jarrad put bets on React-Native, but with more layer on top, which is Clojure-Script.

I guess it made perfect sense back then – initial product, development speed vs performance and maintainability, cross-platform support out of the box – all that was making React-Native a really promising candidate.

As the company grew more and more developers joined, the first problem of our React-Native stack become evident: it’s extremely hard to build, so let’s talk about it.

Building status-react

I wrote about this problem number of times before, but the building experience in status-react is the worst I have ever seen. In Bangkok 4 core members working on status-react daily were sitting at my laptop for 2 hours straight trying to achieve a simple goal – just build it. We succeeded only partially back then (iOS build never worked).

I have a litmus test for the processes inside the company – will you be proud to share this experience in the conference or meetup? I doubt anyone can be proud with build pipeline of status-react.

But it’s not just about complexity in the puritanic software engineering sense. It was a huge hindrance to:

  • test out ideas. We often wanted to change something in status-go and test it on the real app – after months of fighting build system, I just dropped this idea completely.
  • contribute to the status-react. Again, I had multiple ideas I wanted to implement, and extremely negative experience with the build was a stopper.
  • create bounties for status-react during hackathon – due to incredibly buggy and complex build process, we just could not afford ask hackers on hackathon to build it and be able to assist them if something fails during the build.
  • it virtually excluded iOS builds from being actively tested and considered as important – there were other reasons too, of course, but building for iOS has always been broken and nobody in the team for over the year could fix this.

It created an unexpectedly high entry barrier for developers, while the original goal of React-Native seems to be the opposite.

ClojureScript

Now, this topic has been a second taboo among Status developers, probably because many Clojure developers have been hired and discussing it may piss them off.

The first problem with choosing Clojure Script is that it’s one more layer on top of an already complicated multi-layered stack of status-react. The current status-react stack consists of human- and code-generated code written in:

  • Clojure-Script
  • React
  • JavaScript
  • Objective-C
  • Java

Each of those layers has its own building step in the pipeline, its own tooling, its own dependency management, and its own set of problems and bugs. Managing this mess is an extremely hard task, and I don’t remember anyone at Status who understands all those layers good enough to solve practical problems.

The second problem is that Clojure(and ClojureScript) is still a relatively exotic and unknown language, making it hard to find developers. Even more, it’s not the easiest language, making it another high entry barrier to the contributions even from within the core team.

The third problem is that most Clojure folks come from backend development field. It’s easy to understand that backend engineers aren’t usually good in UI/UX, design and nuances of mobile app development. I think it’s shaped quite some choices in status-react design, especially at the beginning.

Tabooing the subject

I raised those concerns in many private conversations and always felt that people are afraid of talking about it. So I went straight to @jarrad and asked him about it (it was around a year ago). The response was a rather harsh and cold message in public Slack channel, and later in private conversation, stating that:

  1. React-Native is absolutely the right choice, many cool companies build their apps with React-Native, proving it’s great and anyone who thinks otherwise should think twice
  2. Clojure is a great choice because it attracts smart people with the right mindset and a lot of experience in computer science.

I never pushed further these discussions in public, because it’s usually hard to make changes with such a response from the top. Plus, hiring people for the specific stack is almost automatically shifts the balance of opinions and discussing the technology may be seen as a threat for the job position for some people. I think those two factors contributed most to the tabooing the subject.

But let me respond to both of those claims.

React-Native success stories

The apps were mentioned as React-Native success stories include Airbnb, Facebook (obviously), Instagram, Uber and others. While clearly suffering from the survivorship bias, when you look closer, you might see that the picture is not that bright.

Uber success story is actually not about an Uber app, but UberEats dashboard for restaurants’ tables that is written in RN.

Instagram app is native as well, but they integrated React-Native code into various parts of the app like “Push Notification Settings” or “List of saved items” screens.

Facebook is a clear outsider here because the app is awful and they are creators of React-Native, which means they have enormous resources just to work on React-Native itself.

Airbnb app is beautiful, and it could be a good success story, but this summer they shared a series of blog posts describing their experience with React-Native and saying that they’re moving back to native mobile app development.

Clojure as a language of extremely skilled CS people

I don’t have anything against Clojure per-se, but I’ve been programming since the age of 6, and I just can’t buy this argument. Clojure is definitely a modern language with its aura of coolness around, but it doesn’t automatically make people smart or experienced or right.

From my experience, people often choose Clojure not because it reflects their decades-long aspirations about best programming approaches, but because of coolness and social perception of this choice. PHP is not cool, Clojure is, that’s it. This bias is actually known, and here is an article that comes to mind: https://daiyi.co/blog/2016/11/30/Signaling-in-tech-is-some-fucked-up-shit/ A quote of the author, who switched from being a web developer to a Clojure developer:

People started telling me I was really cool. I noticed people making all these wildly positive assumptions about my programming ability despite not having read a single line of code I’ve written ever. I instantly get elevated to a respect-worthy status for free and maybe for people who are used to being treated well that’s not a big deal, but coming from being treated like trash after telling people I am a web person the contrast was super intense and really obvious.

So the argument “Clojure attracts really skilled people”, no matter how true it is, simply can’t be used in reverse (“If someone have chosen Clojure, they’re automatically skilled”).

Conclusions

I always liked working on UI apps and been writing apps in Qt, Delphi, QML, QuickScript, PyQT, TCL, Objective-C/Swift and I’m always happy to learn some new technology to work on, but the Status choice of technological stack literally excluded me from being able to contribute to the UI part (and not only UI, unfortunately) of the app for more than a year.

Now I’m practising with Swift 4, and it’s hard to even compare those experiences – you have just one stack – Swift lang and compiler, and the tooling is perfectly suited for this stack. I bet the similar experience with Android native app development.

Status hired and fired tons of people recently, and I think it’s worth rethinking if React-Native is a truly great choice if it excludes people from contribution, excludes codebase from being a good choice on hackathons and uses a bloated multi-layered stack of technologies.

As a final note, Status Desktop comes to mind, but having heard about the pain developers deal with and the need to write own half-finished bindings by Canonical raises more than an eyebrow in question if the native desktop app development with QT wouldn’t be much faster and productive.

I’m going to work in my spare time on the native Swift Status UI, meanwhile, and I don’t really care anymore if top management of Status is agreeing. I’ve heard many developers begging for switching to native app development.

Hopefully this post will start some productive discussion.

5 Likes

A middle ground would be to carve out the protocol part into a library usable from any language and let competing UIs appear in parallel, while continuing to develop the React-Native clients.

If a rewrite is too expensive, a JavaScript daemon with secure RPC access and a stable public API might just be good enough to open the ecosystem.

3 Likes

A middle ground would be to carve out the protocol part into a library usable from any language and let competing UIs appear in parallel, while continuing to develop the React-Native clients.

There is an effort to do this here GitHub - status-im/status-js-api: Status Javascript Client (WIP) , this library is being used by both status-x and status-js-desktop.

3 Likes

Thanks for this post @divan, and I’m happy to see you still participate in the Status community! While moving away from React Native for the “official” (GmbH) app is unlikely to happen for a while (for a bunch of reasons, but primarily due to the laser focus on delivering on our whitepaper promises), I just want to say I applaud the effort to introduce multiple clients.

One of the beautiful things about having a protocol (Status.app) and a DAO, is that it doesn’t matter if people disagree about the best approach. As long as you speak the same language, people can make different design trade offs and create different clients. In the end, the best or most ‘fit’ client is the one likely to survive and see adoption.

Personally, I see a bunch of pros and cons with a native client, and I definitely agree with a bunch of your reasoning. Once there’s a DAO, if there’s a serious proposal to to create a native client, I’d personally be up for partially funding it, as I think competition is healthy and it’d be good for the ecosystem as a whole.

4 Likes

I agree the build system needs loves, I have been certainly asking for that for longer than a year, because we need End-to-End Deterministic Builds, maybe that’s something you’d be interesting in contributing to @divan. In any case it’ll get done eventually, unfortunately we have the passage of time to deal with.

Surprisingly finding good Clojure developers has been one of the least of our concerns, I’m really proud of the talent we’ve attracted and the code quality they produce, certainly compared to the other languages in the stack.

JS, ObjC and Java are relegated to minimal Glue-code, and your conflating this with building for different platforms, you wouldn’t escape this with native apps on each platform. Besides Clojurescript, the other dominant language in the stack is Go.

There is a serious consideration of maintaining and building seperate codebases for iOS, Android, macOS, Windows & Linux. There are considerable cost savings in maintaining a largely unified codebase across all platforms. Feature implementation tracking across platforms and platform-specific bugs is quite a difficult program to wrangle. I think we’ve done a great job so far.

I also think we’re seriously underestimating the amount of work that’s gone into the application codebase, Status was 80% of the way there in under a year, before most people joined, infact it had more features than it does today, done by 7 people. It’s that last 20% that is really killer in maturing the application.

imo a better argument for Native Apps would have been to cite performance of native applications over react native, which I totally agree is a problem, but it can be alleviated in optimisation.

Having all this said, I certainly welcome more Status clients in the network.

2 Likes

There is an effort to do this here https://github.com/status-im/status-js , this library is being used by both status-x and status-js-desktop.

But you can’t use it as a library from C/C++/Java/Go/Obj-C/Swift/etc. so it has to be a daemon you connect to over network/Unix sockets.

1 Like

Well for a ‘daemon’ there is GitHub - status-im/murmur: WIP - Whisper node / client implementation built in javascript . For a library it’s hard to do a single one that can work in many languages at the same time, for that case the most important is to have a good spec.

1 Like

Just write it in C and it can be used from any other language. You can also write it in C++ with C wrappers in a separate header, in D, in Go (but you bundle the whole runtime with it) and probably other languages that can be compiled to machine code.

2 Likes

It’d be cool to get 1.0 out and then take all lessons learned and do a re-architecture of the app for 2.0.

I agree writing in C (or Nim!) would be interesting to make a portable lib.

2 Likes

Still, this “glue code” requires separate dependency management, build tooling and configuration.

  • CLJS needs re-natal and figwheel
  • Clojure needs lein
  • RN needs react-native tool
  • JS needs npm,
  • Java needs maven or/and gradle and Java
  • ObjC needs cocoapods

where each layer depends on another.

I had build failures absolutely on all levels. Again, we never had developers who understand all of these tools and could resolve problems in a way different from “try another npm version”.

In native development, you have only two separated build pipelines, heavily optimized and well-automated by native tooling – Xcode and Android Studio. It’s an order of magnitude easier from my experience.

Go has nothing to do with status-react build process. It’s consumed as a dependency.

What I meant is that we’ve excluded all the talent that doesn’t speak ClojureScript. The pool of talented RN developers or iOS/Android developers is obviously much bigger. The pool of talents that familiar with JS and can quickly pickup React-Native apps is even bigger (includes the rest of core-team devs, btw) Was CLJS choice worth this massive exclusion? I personally don’t think so.

Exemplary cross-team culture building.

It’s only exclusionary in so far as an individuals comprehension of functional programming.

1 Like

While I agree that the current stack is far from optimal to attract occasional contributors who might grow into more serious contributors, I must disagree with how hard you make building the app seem, and feel it’s important to balance things with other experiences: even though I had zero experience with React Native, Clojure and very little experience with Node.js when I joined Status, I was able to build, test and contribute (changing a binding) to status-react even before I joined or had access to CCs to ask for directions, by following the instructions in the old wiki. After that, the only time where I had serious issues building status-react was about a month ago, where npm install would hang indefinitely. I was able to solve that by re-cloning the repo, so clearly there was something in the old repo state that was messing up npm (by the way, Igor was able to replace npm with yarn earlier this month, and things are much faster and reliable now). Over time, I (and not only me) learned enough ClojureScript to allow me to add new UI features and some logic independently, so it can definitely be done, but it does require grit. Disclaimer: I don’t build for iOS myself, so I can’t speak for or against how hard it is to build for the platform.

Regarding native stacks, I would definitely agree if we were talking about a strictly mobile-only ecosystem, but I don’t know how you’d be able to do the same for the 5 different platforms we support (iOS, Android, Windows, Linux, MacOS) with similar team size. I guess one approach could be what Plex was doing, have native development for mobile, and web for desktop, but that had its own challenges with lack of logic sharing between platforms (some logic could be very stable in one platform and completely broken in another).

There is a script at scripts/bundle-status-go.sh which you can call and it’ll build your currently checked out status-go into the next builds of status-react, so I can’t really agree that it’s difficult to test out new ideas.

4 Likes

I must disagree with how hard you make building the app seem

Many people were unable to build the project repeatedly. Core developers were unable to build the project repeatedly. The build system was a reason why status-react bounties were excluded from hackathons. What exactly do you disagree with?

The argument “works on my machine” is obviously not the way to handle excessive complexity of the project.

so it can definitely be done

Everything can be done. The question is in costs.

Only? It’s the majority of the talent pool.

So basically it’s the top-down decision to exclude people from Status community because they’re not fans of one particular programming paradigm. If the true motivation was to promote FP, I can totally understand that.

That argument works both ways, and it’s not the argument I’m making. That would equate to saying that it is a matter of chance that the repo can be built. I’ve built it from scratch multiple times from 2 machines, so my point is that I don’t buy that argument. I think the complexity becomes more of a problem in e.g. the time it takes to do a full CI build for a PR than being able to set up the environment in a dev machine but honestly, I haven’t often found myself waiting for a CI build because of that.

That being said, it’s important to continuously improve scripts and documentation to surface/handle known errors and not keep the developer in the dark with cryptic error messages.

Regarding the choice of ClojureScript instead of something like TypeScript, as I mentioned before, I agree that it will easily turn off the occasional contributor, who might be very talented in RN but not willing to put in the time to learn enough ClojureScript to make a contribution he knows it would otherwise take him little time to make.

If we all agree that having competing client implementations is something the Status network can benefit from, I’d like to point out that having a well-defined protocol is only half of the story. The Status-specific ÐApps will define us as a platform and the APIs for creating these ÐApps will impose certain requirements on each implementation.

It’s fair to say that the current extensions API is quite influenced by the best practices of the ClojureScript community. I can see from the current API docs that this is a bit of a misnomer as the intention is to have a data-driven programming model, but we should think about how we can make our platform most welcoming for external developers, while at the same time allowing a rich variety of ÐApps to work across multiple clients.

Let’s discuss what would be a reasonable minimal set of components a conforming client would need:

  • A JavaScript engine - a mostly fine choice since a JavaScript run-time is available on all targeted platforms and it’s the most well-known programming language today. It’s also already required for the browser-based ÐApps.

  • A view engine based on virtual DOM diffing with a limited set of custom components - This gives slight advantage to React-based implementations, but it’s still possible to implement a mini view engine in other toolkits as long as the underlying HTML capabilities are not exposed too much. Please note that allowing extensions to work with arbitrary HTML/CSS is potentially dangerous anyway, due to the possible phishing attacks.

  • A flexible templating engine for the views. Designing React-like views in practice requires support for things like computed attributes, conditions and loops in the view templates. The choice of a templating engine will affect the required footprint of the client and how many developers will be already familiar with it. IMO, the best choice here would be a native JavaScript solution as this would bring only a minimal set of additional dependencies.

If you can trust my expertise as a programming language designer, I’m quite skeptical that the data driven programming model can scale to more complex extensions. The need for a more sophisticated evaluation engine will arrive as soon as things like functions and loops are introduced. The cited benefit of the data driven approach is that it would allow for static analysis to determine the required permissions of each extension, but this is also possible with regular JavaScript if you use a capability system where each capability must be obtained though a well-known API:

var sendMessage = requireCapability("SendMessage")
sendMessage(...)

To make it easier to implement clients and to increase developer inclusivity, I think it’s best if the extensions model is defined directly on top of JavaScript without requiring data formats and custom evaluation engines that won’t be readily available in all programming languages. Another more unorthodox option would be to also expose a WebAssembly run-time powered by the same host API.

4 Likes

Interesting idea! Only this rubs me kind of weird.

Status-specific should not exist. In theory, the extensions are “Status specific” since the dapps in the dapp browser just use an injected web3 and are in no way specific to us, but extensions of some kind exist in almost all messaging clients in the form of hooks or bots or both. I do absolutely agree that it needs to be leaning more towards the exposed API with regular JS (Chrome ext. style) than a specific implementation with more friction like data driven clojure sandbox injection (Slack’s model and curreny Status approach), but I believe it would be difficult to pull off while at the same time successfully preventing people from draining their phones with badly developed extensions. It’s the same reason why mobile browsers don’t allow extensions - script kids (and let’s not fool ourselves into thinking 80% of the JS space isn’t just script kids) will make an infinite loop within a loop within a timeout for anything and that’ll kill your battery right off.

If we had a way to “eWASM-ify” JavaScript by removing some language constructs (i.e. make Status’ JS flavor Turing-incomplete), then it could be pulled off, but I don’t see how that’s doable.

TL;DR: I like the idea of more approachable extension development, but don’t see how we can pull it off without making Status’ battery consumption problem worse. Admittedly, there’s not much preventing bad design in data-based extensions as they are, either.

2 Likes

The reason I call the Status extensions ÐApps is that their execution logic can be distributed across multiple devices and users.

Playing a game of poker with your friends is a Status extension, a very complex distributed system like LibreTaxi can be delivered as an extension as well. Heck, even something like the Slack team onboarding experience can be implemented only as an extension.

So, the extensions are not just about customizing your personal experience in Status, they are about coming up with novel ideas about how to utilize the Status communication protocol and then delivering these ideas as a very well integrated UX. In other words, Status is the platform for real-time communication ÐApps in the Ethereum network.

The battery life problem can be attacked in the same way Ethereum does it. You can give any script a certain time or instruction count budget (essentially gas) and if the script tries to go into an infinite loop, its execution will be terminated. The Web3 pages also feature scripts and they have the same problem - users will rely on things like reputation, ad-blockers and so on to limit their exposure to malicious scripts.

2 Likes

Let’s get back to the topic. I tried to explain that React-Native is not the best solution for the project like Status, if not the worst. But there were only two options (native vs RN), and now there is a third option – Flutter.

Flutter

Flutter is a new Google’s framework for writing mobile apps, and, potentially, desktop- and web-apps too. The promise is similar to RN – write once, run on all platforms, but there is a huge difference: there is no JS/CSS/DOM bullshit. RN was chasing a good idea but has been built on top of the crappiest stack in the history of the software industry, and we all see the result in how many people and millions of dollars we need just to be able to build status-react. Flutter, on the other hand, is a complete rethinking of how apps for mobiles should be built and has a completely different design.

Flutter is written in Dart, which is kinda ok language. On the one hand, it has every feature possible: classes, generics, overloads, exceptions, futures/async-await/promises/event-loop, garbage collection, JIT/AOT, you name it, and syntactically similar to C/JS/Go languages, so is relatively easy to pick up. On the other – all these features make it really hard to do anything that is not in the tutorials, but the good news, that with Flutter you don’t really write Dart, you write mostly Flutter. Frameworks are the language on top of the language, after all.

Flutter is extremely easy to learn. There is an insane amount of high-class tutorials on every topic, created by the community. API docs are crap, mostly autogenerated unreadable pages, but, again, tutorials and tons of Stack Overflow answers compensate this easily. It took me three days from the time I first read about Flutter to finishing my first app – a mobile app I wanted to make for a long time, but just could not afford to invest time to learn all iOS and Android development intricacies.

Flutter’s design is sick – it renders the app by itself and uses only native iOS or Android (or desktop) code just to talk to the GPU. All widgets, all animations and transitions – all handled internally by Skia, Flutters engine written in C++, and it’s insanely fast and designed from scratch for these things.

Remember how many days we spend chasing Status app slowing down due to switching screens in React-Native? With Flutter, you can do custom crazy animations with 3D effects while switching screens and will run smoothly at 120fps.

Status in Flutter in 5 hours

So, I decided to see, how easy would it be to rewrite that status-react UI part in Flutter. I was mostly interested in the UI part, just main screens – login, register, wallet, chat, profile screen.

It took me 5 hours.

From scratch.

And the app is running smoothly on phones and tablets, Android and iPhone/iPad.

With a framework, I’ve heard about a few weeks ago.

Let me show some quick demo:

Three simulators – Android, iPhone XS and iPad Pro 11

Real device - iPhone XS:

Real device - iPad Pro 11":

The code is available here: GitHub - divan/status_flutter: PoC of Flutter version of Status UI

React-Native vs Flutter

The main difference between Flutter and RN is that it’s really well designed. It’s not a crappy hack on top of other hacks.

The tooling out-of-the-box is fantastic. You can build the project in one command, and Flutter takes care of underlying layers – cocoapods or gradle. It’s almost Go-like experience when you have flutter tool with subcommands like build/run/test and it does everything you need. Also, IDE support and debugging are great.

To make comparison easier, I’ve made a table:

Flutter RN+CLJ Native iOS/Android
Easy to build Yes No Yes
Easy to learn Yes No No
Has great tooling Yes No Yes
Number of underlying stacks 1(3) 7 1
Need to learn one stack Yes No No
Doesn’t have JS/DOM/CSS bullshit Yes No Yes
Has great success stories Yes No Yes
Has great community Yes N/A Yes
Easy to talk to native layer No No Yes

As you can see, RN+CLJS is the worst option here, at least, at my subjective evaluation.

And, of course, you can say I’m biased, but hey – I wasn’t able to even build status-react most of the time for more then year, and having written already 3 mobile apps with Flutter just for fun in the past few weeks, I can’t even imagine that “flutter app is not building”.

Conclusion

Like it wasn’t obvious, but the current stack of status-react is crap and the worst one on the market. Somehow the core team is comfortable with that for a long time.

Locking chat layer into CLJS-RN code - the code that can’t even be easily built – in hindsight looks completely ridiculous decision. The status app could have been reimplemented dozens of times with different stacks, letting healthy competition to choose the best platform. Instead, it’s piling up more and more of sunk costs directly related to this choice.

I’ll be developing Status Flutter version in my spare time, waiting for the chat layer to be implemented in status-go. Probably that won’t happen soon, but I’m not in hurry.

Just wanted to share how much more productive you can be with the right technology.

For me, it’s an enabler – Flutter lowers the complexity of developing mobile apps to ridiculously acceptable levels – as it should be. It’s the order of magnitude difference if you compare to the native development, let alone to the status-react stack.

If even I can build the main UI part of Status in Flutter from scratch in five hours, I imagine what can do a team of talented developers with great investments. Maybe it’s time to become realistic about RN and CLJS?

8 Likes

Incredible, thanks for sharing this! Perhaps extracting the chat from something like status-js, status-x, status-desktop or nim-stratus might be a viable option for reimplementing in Flutter? That helps avoid the complexity of RN stack’s chat layer lock-in and doesn’t force you to wait for a separate API-ed protocol.

2 Likes

Thanks. Yeah, that’s the idea. Due to Flutter’s design (it does render everything by itself, not relying on OS/platform code), in order to talk to the native layer, it requires writing special bridges – “plugins”. It’s no more complex than, writing RN/CLJS wrapper for status-go, however, and gomobile-built libraries (like status-go) can be easily integrated. That’s actually my next step.