Positive parts

Architecture

Let me start with architecture. It was always a painful part of Android development as Google seemed to not worry about adequate recommendations in this area. They started to do it recently and even build Architecture Components library pack which is pretty cool but does not cover all the blocks of a real-life application (yet?)

On the other hand, Facebook proposed Flux architecture as an addition to React some time ago and this is good I must say. At least Redux implementation of it (there should be a more profound difference between Flux and Redux under the hood but I won’t cover it here). It takes some time to dive into Redux but after that, it became quite comfortable to use it.

Redux has several main components that are instruments to build more or less flexible and maintainable codebase and prevent messed up connections between app parts. First, you have a Store that represents the state of the application at any moment of time. Like really. If you cook it right you may get a dump of app state by grabbing a store and restore it anywhere, probably even on another device (that’s how redux-persist works but we’ll talk about it later). You may listen to store changes but do not modify the store directly. You should notify about events that will affect the store only by sending (dispatching) Actions. These are plain JS objects that may contain some additional payload (extra data) in addition to an action type. So if you’re developing (yet another) Todo app you will for sure have some action with type “ADD_TODO” and payload that contains todo title and maybe notification time. As actions are plain objects they don’t (and shouldn’t) contain information on how the app’s store should change, they only tell what’s happened. Reducers are the blocks to modify the store according to actions. These are just functions that receive the current app state plus action to handle and return the state that was modified accordingly.

Something like this (randomly googled image)

So, the data layer built upon a combination of these three blocks is quite solid I must say. UI components can be connected to the store and receive all the necessary updates to modify the presentation accordingly. This gives a lot of opportunities. For example, we implemented a hot app locale switch almost effortlessly.

No lifecycle callbacks pain

BTW, talking about locales. Historically Android components like Activity can be recreated literally at any moment. And they call it configuration change. Screen rotation, system locale change and even keyboard connection are some of those triggers. Overall Activities and Fragments lifecycle is a complete mess. But you gonna understand it to write apps that work correctly and save data somehow to restore it after recreation.

I saw this image in some book but copied it from here. Just to scare you.

React Native is much easier in this area. You have to understand a lot fewer callbacks to start to write working apps. These are mostly componentDidMount and componentWillUnmount. And you are not forced to handle screen rotation unless you need it (when writing regular Android apps you can make Activities to act the same way by adding a relevant parameter to AndroidManifest, and I’m starting to think this should be the default behavior. That’s not ideal but at least better than locking orientation for the whole app just to escape dealing with activity recreation. Long live to ViewModel from Architecture Components which survives screen rotation). Data and even navigation state (if you’re using a right navigator) can be easily saved to survive process death using redux-persist. It is useful for a lot of things, like the next point.

Data caching

This one is related to architecture a little bit. Let’s assume you are developing yet another weather app and want to cache the weather for the next week locally for the user to see it being offline. If you are an Android developer you know how to solve it. You get SQLite (you say Realm/ObjectBox? Okay man, just don’t spill your smoothie on me). Even though a database is not necessarily a bad thing, for simple cases it may be (and often is) overkill. Using React Native and Redux with redux-persist we just made our stores be saved to AsyncStorage in the background and worked with them the same way as with simple in-memory stores. Worked like a charm. Nothing about manual data mapping and SQLite queries.

JS and (mostly) JSX

I always heard bad things about JavaScript. Well, this language has a lot to complain about, but actually, it’s not that bad. At least in its latest ECMAScript revision. Map-like objects, spread operator and destructuring are fun. And functions with functions as parameters and return functions may sweep you off your feet if you’re only used to Java 7 with all its limitations. But it’s neat when you get used to it.

Of course, weakly & dynamically typed language allows you a lot of dangerous things that you shouldn’t do, but it may help sometimes when you iterate fast :) For example, we had some mock data (lists of objects with numeric id field) when started development. We imagined the result data in some way. But when the real data was ready ids became strings. This would break everything in “normal” languages, but with JavaScript, we just got warnings from prop-types. Of course, we fixed typings eventually but it didn’t prevent us from doing our urgent tasks (that’s true unless you’re trying to do arithmetics with ids. But you completely screwed up doing that in any case).

And JSX is my love. If you’re not trying to write all the logic in render() function it is splendid. Here’s a brief example. You happened to need some limited list of elements (let’s say up to 10). If you’re an Android developer you have 2 options usually. You can go with RecyclerView (which is too much for lists with such a small amount of static elements) or you can try to add Views programmatically to ScrollView (which requires quite a lot of boilerplate code). But that’s how you can do it with React Native:

<View> {someList.map((someObject, i) =>

<Icon key={i}

name={someObject.name}

size={18}

color="#8bc34a" />)

} </View>

Right. You just map your list of data to React Native components (don’t do this with long lists, go learn FlatList).

Another fancy thing is rendering parts of layout only when some clauses are true, like this:

<View>

{isDataAvailable() && <DataRenderer data={this.props.data}/>} </View>

Or this:

<View>

{pojo !== null ? <DataRenderer data={pojo}/> : null} </View>

Android’s DataBinding can do something similar by adding clauses to visibility param in XML, but that’s far from such JSX elegance.

So generally I enjoyed building simple UI with React Native more than with Android.

Cross-platform development

Yeah, this is a good part of React Native. Even though you cannot have 100% code reuse between platforms, it’s a deal-breaker when you have limited resources. We successfully made apps for both Android and iOS. That was definitely not twice as quick as developing native apps separately (especially if you’re not an expert in Android, iOS, and React simultaneously), but still quicker. You just have to be ready to dabble with Xcode and Objective-C (which was sometimes more painful than React Native itself).

Writing platform-specific code is easy-peasy when we’re talking about the JS part. In simpler cases you can just make checks on the fly like this:

<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : null}>

...

</KeyboardAvoidingView>

When you have more than a single line related to only one platform or want to have drastically different UI on Android and iOS you create two files with names filename.android.js and filename.ios.js and then import this module as filename. No drama here.

I haven’t had a chance to develop native modules in Java/Obj-C myself so I can’t say anything about its quirks but documentation looks straightforward. At least if you are a native developer.

Disappointments

Of course React Native is not ideal. I have some things to complain about. Here are some of them.

Performance

React Native is faster than Cordova and all that WebView stuff. It’s a fact. But still not as fast as real native apps, and it’s noticeable. Which makes sense if remember about JS bridge overhead. I don’t want to dive into low-level details here but of course, JS Bridge instantiation and communication with UI thread takes some time. Here is a nice article about how it works under the hood. It has “part 1” in its name, but unfortunately, it is the only part. Still good tho. And another one.

Actually, I was ready for cross-platform development penalties, and performance is one of them. It’s not a disaster if you do it right (like use FlatList instead of ScrollView) but don’t expect to compete with “real native” apps.

Tooling

Android Studio and its tooling iterate fast as well as build tools, so I’m not going to lie that they are super stable now, but they still more stable than React Native tooling.

I used VS Code for writing the code. Besides some integrated terminal quirks and build-in git tools (ugh, this jumping after reverting a changed line of code), it’s nice. Though I still don’t really understand how to debug the code with or without it. I feel like there are different approaches here influenced by the web development, but on Android you just put a breakpoint on a line of code, attach a debugger to the app, code execution will stop on exact line you want and you’ll be able to inspect all the variables you’re interested in. And you don’t need to dance around to make it work. Shame on me, but I leveraged React Native Debugger and console.log to do such things. It was a pain on the Android emulator because the app worked so slow with the attached debugger. Shame on me again, but I, Android developer, used mostly iOS simulator to debug the app.

Errors inside the emulator/simulator are a joke. Maybe I’m just used to Android Studio logcat and verbose stacktrace, but there should be better ways to show errors. Yes, I can attach a debugger and see some errors in JS console, but read the previous paragraph about app speed in this state.

Sometimes errors say “Catch me if you can”

I regularly got errors when tried to build or launch the app. And according to SO/Github issues most problems can be resolved by removing the node_modules directory and reinstalling dependencies (lucky you when clearing Watchman cache helps in your particular case). It’s just uncomfortable.

ESLint doesn’t understand if you’re trying to import a module with index.android.js and index.ios.js (eslint-disable-line to the rescue):

You better not to try using native libs auto-linking. It was driving me crazy when I launched linking two times and the whole Android project messed up as all the dependencies in Gradle files and required code in Java classes were added twice. Cleaning this up is not funny at all. So I ended up doing linking manually all the time.

This can be a long story, I regret a didn’t make notes while fighting with all the problems I stumbled upon. There were Xcode problems as well but they are not really React Native problems. But at the end of the day, I’m just saying that Android Studio and Gradle are pieces of cake when doing the comparison :)

Libraries

Luckily there are a lot of cross-platform libraries for React Native. But while they support twice as many platforms they are twice as buggy at the same time. And from time to time there is no library you need and you have to go and implement it by yourself. Let me point only on a couple of problems I struggled with.

react-native-maps had initial location property broken on iOS. It’s fixed now but I had to do a workaround by myself somehow.

react-native-material-kit still uses Gradle syntax removed from the latest releases, so I’ve added its fork as a dependency for now.

I wasn’t able to found anything comparable to phonegap-launch-navigator, so I had to write separate logic to launch maps apps for Android and iOS. Luckily I did it with JavaScript only. Boo iOS for not having general URI to launch any navigation app.

Googling didn’t help me to set up images loading with correct caching (even though React Native must be using the Fresco library internally). Nobody does that manually on Android if you have Glide.