People who make fun of Javascript probably don’t understand implicit type coersion and when to use triple equal. Javascript is very unexpected, but when we work with this language, we need to be aware.
Coercion–Automatically changing a value from one type to another.
If x is Number and y is String, return x == ToNumber(y)
If x is String or Number and y is Object, return x == ToPrimitive(y)
Javascript primitive types number and string are too general and do not express the domain objects. Because lack of type alias in Javascript, we can use flow
export type Centimeter = number export type Color = string export type ImageSource = number export type Kilogram = number export type Kilocalorie = number // 1 cal = 4.1840 J export type Second = number export type SecondsSince1970 = number export type Minute = number export type DateString = string // 2018-11-20 export type DateTimeString = string // 2018-11-20T00:00:00 export type YearWeekString = string // 201838
export type FunctionWithVoid = () =>void export type FunctionWithString = (string) =>void export type FunctionWithBoolean = (boolean) =>void export type FunctionWithNumber = (number) =>void export type JSONObject = any export type JSONString = string export type StringToString = any
export type Degree = number export type Radian = number
For a normal electron app created with npm init, we can use all features of ES6, but not the JSX syntax for React. We can use just Babel to transpile JSX, as used in IconGenerator
Install electron as dev npm install electron --save-dev Update electron-packager npm install electron-packager@latest --save-dev Use no space in app name
If you have multiple developer identities in your keychain:
electron-osx-sign searches your keychain for the first signing certificates that it can locate. If you have multiple certificates then it may not know which cert you want to use for signing and you need to explicitly provide the name:
1
electron-osx-sign "My App-mas-x64/My App.app" --identity="3rd Party Mac Developer Application: My Company, Inc (ABCDEFG1234)" --verbose
For distribution in the Mac App Store: Have the provisioning profile for distribution placed in the current working directory and the signing identity installed in the default keychain.
Certificate
On developer.apple.com, create Mac App Distribution certificate. Make sure when we download in Keychain Access, it has associated private key
electron Bad CFBundleExecutable. Cannot find executable file
ERROR ITMS-90261: “Bad CFBundleExecutable. Cannot find executable file that matches the value of CFBundleExecutable in the nested bundle MyApp [com.onmyway133.MyApp.pkg/Payload/MyApp.app/Contents/Frameworks/MyApp (GPU).app] property list file.”
In terms of tests, we usually have files for unit test, UI test, integeration test and mock.
Out of sight, out of mind.
Unit tests are for checking specific functions and classes, it’s more convenient to browse them side by side with source file. For example in Javascript, Kotlin and Swift
As someone who comes to React Native from iOS and Android background, I like React and Javascript as much as I like Swift and Kotlin. React Native is a cool concept, but things that sound good in theory may not work well in practice.
Another layer of abstraction
Up until now, I still don’t get why big companies choose React Native over native ones, as in the end what we do is to deliver good experience to the end user, not the easy development for developers. I have seen companies saying goodbye to React Native and people saying that React Native is just React philosophy learn once, write anywhere, that they choose React Native mainly for the React style, not for cross platform development. But the fact can’t be denied that people choose React Native mostly for cross platform development. Write once, deploy many places is so compelling. We still of course need to tweak a bit for each platform, but most of the code is reuse.
I have to confess that I myself spend less time developing than digging into compiling and bundling issues. Nothing comes for free. To be successful with React Native, not only we need strong understanding on both iOS and Android, but also ready to step into solving issues. We can’t just open issues, pray and wait and hope someone solves it. We can’t just use other dependencies blindly and call it a day. A trivial bug can be super hard to fix in React Native, and our projects can’t wait. Big companies have, of course, enough resource to solve issues, but for small teams, React Native is a trade off decision to consider.
A wrapper for a wrapper
Enough about that. Let’s go to official React Native getting started guide. It recommend create-react-native-app. As someone who made electron.js apps and used create-react-app, I choose create-react-native-app right away, as I think it is the equivalent in terms of creating React Native apps. I know there is react-native init and though that it must have some problems, otherwise people don’t introduce create-react-native-app.
After playing with create-react-native-app and eject, and after that read carefully through its caveats, I know it’s time to use just react-native init . In the end I just need a quick way to bootstrap React Native app with enough tooling (Babel, JSX, Hot reloading), however create-react-native-app is just a limited experience in Expo with opinionated services like Expo push notification docs.expo.io/versions/latest/guides/push-notifications.html and connectivity to Google Cloud Platform and AWS. I know every thing has its use case, but in this case it’s not the tool for me. Dependencies are the root of all evils, and React Native already has a lot, I don’t want more unnecessary things.
The concept of Expo is actually nice, in that it allows newbie to step into React Native fast with recommended tooling already set up, and the ability to reject later. It also useful in sharing Snack to help reproduce issues. But the name of the repo create-react-native-app is a bit misleading.
No, I would never use Expo for a serious project. I imagine what ends up happening in most projects is they reach a point where they have to ultimately eject the app (e.g. needing a native module) which sounds very painful.
Expo is a great tool for getting started quickly with React Native. However, it’s not always something that can get you to the finish line. From what I’ve seen, the Expo team is making a lot of great decisions with their product roadmap. But their rapid pace of development has often led to bugs in new features.
If you are coming from web world, you need to know that create-react-native-app is not the create-react-app equivalent But if you want more control over your project, something like tweaking your react native Android and iOS project, I highly suggest you use the official react-native-cli. Still, one simple command, react-native init ProjectName, and you are good to go.
I wish I would have used “Native” from the get go, instead I wasted quite a bit of time with Expo Maybe the biggest one for me is that the whole thing feels like adding another layer of indirection and complexity to an already complicated stack I wish Expo would get removed from the react-native Getting Started guide to avoid confusing new arrivals
The Expo dev team did a lot of good stuff there, and they are all doing it for free, so I definitely want to thank all of them for providing a smoother entrance to this world. If they ever manage to solve this issue of custom native code somehow, it may become my platform of choice again.
I love everything about Expo except the size of the binaries. Each binary weighs around 25 MB regardless of your app. So the first thing I did was to migrate my existing Expo app to React Native.
If you are new to React Native and you think this is the “must” way to go. Check if it meets your needs first. If you are planning to use third party RN packages that have custom native modules. Expo does not support this functionality and in that case, you will have to eject Expo-Kit. In my opinion, if you are going to eject any kit, don’t use it in the first place. It will probably make things harder than if you hadn’t used the kit at all.
Besides React style programming, Yoga is another cool feature of React Native. It is a cross-platform layout engine which implements Flexbox so we use the same layout code for both platforms.
As someone who uses Auto Layout in iOS and Constraint Layout in Android, I find Flexbox bit hard to use at first, but there are many tasks that Flexbox does very well, they are distribute elements in space and flow layout. In this post we will use Flexbox to build a tag selection view using just Javascript code. This is very easy to do so we don’t need to install extra dependencies.
Our tag view will support both multiple selection and exclusive selection. First, we need a custom Button .
Button with Background
Button is of the basic elements in React Native, but it is somewhat limited if we want to have custom content inside the button, for example texts, images and background
import { Button } from 'react-native'
...
<Button
onPress={onPressLearnMore}
title="Learn More"
color="#841584"
accessibilityLabel="Learn more about this purple button"
/>
Luckily we have TouchableOpacity, which is a wrapper for making views respond properly to touches. On press down, the opacity of the wrapped view is decreased, dimming it.
To implement button in our tag view, we need to a button with background a check image. Create a file called BackgroundButton.js
Normally we use const styles = StyleSheet.create({}) but since we want our button to be configurable, we make styles into a function, so on every render we get a new styles with proper configurations. The properties we support are borderColor, textColor, backgroundColor and showImage
In the makeImageIfAny we only need to return Image if the view is selected. We don’t have the else case, so in if showImage is false, this returns undefined and React won’t render any element
To understand padding and margin, visit CSS Box Model. Basically padding means clearing an area around the content and padding is transparent, while margin means clearing an area outside the border and the margin also is transparent.
Pay attention to styles . We have margin for touchable so that each tag button have a little margin outside each other.
In the view we need flexDirection as row because React Native has flexDirection as column by default. And a row means we have Image and Text side by side horizontally inside the button. We also use alignItems and justifyContent to align elements centeredly on both main and cross axises. The padding is used to have some spaces between the inner text and the view.
We parse an array of tags to build BackgroundButton . We keep the selected array in state because this is mutated inside the TagsView component. If it is isExclusive then the new selected contains just the new selected tag. If it is multiple selection, then we add the new selected tag into the selected array.
The addOrRemove is a our homegrown utility function to add an item into an array if it does not exists, or remove if it exists, using the high orderfilter function.
const addOrRemove = (array, item) => {
const exists = array.includes(item)
if (exists) {
return array.filter((c) => { return c !== item })
} else {
const result = array
result.push(item)
return result
}
}
The hero here is flexWrap which specifies whether the flexible items should wrap or not. Take a look at CSS flex-wrap property for other options. Since we have main axis as row , element will be wrapped to the next row if there are not enough space. That’s how we can achieve a beautiful tag view.
Using TagsView
Then consuming TagsView is as easy as declare it inside render
I hope you learn something useful in this post. For more information please consult the official guide Layout with Flexbox and layout-props for all the possible Flexbox properties.
As you know, in the Pragmatic Programmer, section Your Knowledge Portfolio, it is said that
Learn at least one new language every year. Different languages solve the same problems in different ways. By learning several different approaches, you can help broaden your thinking and avoid getting stuck in a rut. Additionally, learning many languages is far easier now, thanks to the wealth of freely available software on the Internet
I see learning programming languages as a chance to open up my horizon and learn some new concepts. It also encourage good habit like immutability, composition, modulation, …
I’d like to review some of the features of all the languages I have played with. Some are useful, some just make me interested or say “wow”
Curly braces
Each language can have its own style of grouping block of code, but I myself like the curly braces the most, which are cute :]
Some like C, Java, Swift, … use curly braces
Swift
init(total: Double, taxPct: Double) {
self.total = total
self.taxPct = taxPct
subtotal = total / (taxPct + 1)
}
Some like Haskell, Python, … use indentation
Haskell
bmiTell :: (RealFloat a) => a -> String
bmiTell bmi
| bmi <= 18.5 = "You're underweight, you emo, you!"
| bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"
| bmi <= 30.0 = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
Some like Elixir use keyword list
ELixir
if false, do: :this, else: :that
Named parameter
Language like Objective C, Swift offer named parameter, which make it easier to reason about a function call
func sayHello(to person: String, and anotherPerson: String) -> String {
return "Hello \(person) and \(anotherPerson)!"
}
Explicit type
Language like C, Swift, Java, … have type information in parameter and in return, which make it easier to reason about a function call
Languages like Haskell, Python, Elixir, support list comprehension
Elixir
iex> for n <- [1, 2, 3, 4], do: n * n
[1, 4, 9, 16]
First class function
I enjoy functional programming, so first class function support in Javascript, Swift, Haskell, Elixir, … really make me happy
Haskell
zipWith' (*) (replicate 5 2) [1..]
Curry
Currying is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument (partial application)
Language like Swift 2, Haskell, … have curry by default. Some like Javascript can use libraries (Lodash, …) to achieve this. In Haskell, every function officially only takes one parameter.
The |> symbol used in the snippet above is the pipe operator: it simply takes the output from the expression on its left side and passes it as the first argument to the function call on its right side
Haskell often takes advantage of this custom -: operator Haskell
x -: f = f x
(0,0) -: landLeft 1 -: landRight 1 -: landLeft 2
Functor, Applicative Functor, Monoid, Monad
I really like enjoy Haskell because of these typeclasses. It realizes common pattern (map, apply, join, bind, …) with comptutational context. It really enlightens me when I find that function is a Monad as well (you should read the Reader monad)
Haskell
instance Monad Maybe where
return x = Just x
Nothing >>= f = Nothing
Just x >>= f = f x
fail _ = Nothing
landLeft :: Birds -> Pole -> Maybe Pole
landLeft n (left,right)
| abs ((left + n) - right) < 4 = Just (left + n, right)
| otherwise = Nothing
landRight :: Birds -> Pole -> Maybe Pole
landRight n (left,right)
| abs (left - (right + n)) < 4 = Just (left, right + n)
| otherwise = Nothing
ghci> return (0,0) >>= landLeft 1 >>= banana >>= landRight 1
Nothing
List comprehension in Haskell is just syntactic sugar for using lis as Monad Haskell
enum Result<T> {
case Value(T)
case Error(NSError)
}
extension Result {
func map<U>(f: T -> U) -> Result<U> {
switch self {
case let .Value(value):
return Result<U>.Value(f(value))
case let .Error(error):
return Result<U>.Error(error)
}
}
}
extension Result {
static func flatten<T>(result: Result<Result<T>>) -> Result<T> {
switch result {
case let .Value(innerResult):
return innerResult
case let .Error(error):
return Result<T>.Error(error)
}
}
}
extension Result {
func flatMap<U>(f: T -> Result<U>) -> Result<U> {
return Result.flatten(map(f))
}
}
Trait and mixin
Languages like Scala, … support trait
Similar to interfaces in Java, traits are used to define object types by specifying the signature of the supported methods. Unlike Java, Scala allows traits to be partially implemented; i.e. it is possible to define default implementations for some methods
Scala
trait Similarity {
def isSimilar(x: Any): Boolean
def isNotSimilar(x: Any): Boolean = !isSimilar(x)
}
class Point(xc: Int, yc: Int) extends Similarity {
var x: Int = xc
var y: Int = yc
def isSimilar(obj: Any) =
obj.isInstanceOf[Point] &&
obj.asInstanceOf[Point].x == x
}
module Greetings
def hello
puts "Hello!"
end
def bonjour
puts "Bonjour!"
end
def hola
puts "Hola!"
end
end
class User
include Greetings
end
Delegate property
There are certain common kinds of properties that would be very nice to implement once and for all like lazy, observable and storing. An example is in Kotlin
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
Where to go from here
Hope you find something interesting. Each language has its own pros and is designed for specific purpose. So no list will be enough to cover them all.
To take a quick peek into other programming languages, I find Learn in One Video by Derek very helpful.
There are things that intrigue us every day like Swift initialization rule make it explicit when using initializer, Go goroutine and channel for concurrent code, Elixir process for easy concurrent and message communication. You’ll be amazed by how process encapsulates state, Haskell data type encourages immutability and thread safe code, Elixir macro for great extension of the language. The best way to to learn is to use and dive into the languages often.
React Native was designed to be “learn once, write anywhere,” and it is usually used to build cross platform apps for iOS and Android. And for each app that we build, there are times we need to reuse the same code, build and tweak it a bit to make it work for different environments. For example, we might need multiple skins, themes, a free and paid version, or more often different staging and production environments.
And the task that we can’t avoid is adding app icons and splash screens to our apps.
In fact, to add a staging and production environment, and to add app icons, requires us to use Xcode and Android Studio, and we do it the same way we do with native iOS or Android projects.
Let’s call our app MyApp and bootstrap it with react-native init MyApp . There will of course, be tons of libraries to help us with managing different environments.
In this post, we will do just like we did with native apps, so that we know the basic steps.
Build configuration, target, build types, production flavor, and build variant
There are some terminologies we needed to remember. In iOS, debug and releases are called build configurations, and staging and production are called targets.
A build configuration specifies a set of build settings used to build a target’s product in a particular way. For example, it is common to have separate build configurations for debug and release builds of a product.
A target specifies a product to build and contains the instructions for building the product from a set of files in a project or work-space. A target defines a single product; it organizes the inputs into the build system — the source files and instructions for processing those source files — required to build that product. Projects can contain one or more targets, each of which produces one product
In Android, debug and releases are called build types, and staging and production are called product flavors. Together they form build variants.
For example, a “demo” product flavor can specify different features and device requirements, such as custom source code, resources, and minimum API levels, while the “debug” build type applies different build and packaging settings, such as debug options and signing keys. The resulting build variant is the “demoDebug” version of your app, and it includes a combination of the configurations and resources included in the “demo” product flavor, “debug” build type, and main/ source set.
Staging and production targets in iOS
Open MyApp.xcodeproj inside ios using Xcode. Here is what we get after bootstrapping:
React Native creates iOS and tvOS apps, and two test targets. In Xcode, a project can contain many targets, and each target means a unique product with its own build settings — Info.plist and app icons.
Duplicate target
If we don’t need the tvOS app, we can delete the MyApp-tvOS and MyApp-tvOSTests . Let’s use MyApp target as our production environment, and right click -> Duplicate to make another target. Let’s call it MyApp Staging.
Each target must have unique bundle id. Change the bundle id of MyApp to com.onmyway133.MyApp and MyApp Staging to com.onmyway133.MyApp.Staging.
Info.plist
When we duplicate MyApp target , Xcode also duplicates Info.plist into MyApp copy-Info.plist for the staging target. Change it to a more meaningful name Info-Staging.plist and drag it to the MyApp group in Xcode to stay organised. After dragging, MyApp Staging target can’t find the plist, so click Choose Info.plist File and point to the Info-Staging.plist.
Scheme
Xcode also duplicates the scheme when we duplicate the target, so we get MyApp copy:
Click Manage Schemes in the scheme drop-down to open Scheme manager:
I usually delete the generated MyApp copy scheme, then I create a new scheme again for the MyApp Staging target. You need to make sure that the scheme is marked as Shared so that it is tracked into git.
For some reason, the staging scheme does not have all the things set up like the production scheme. You can run into issues like ‘React/RCTBundleURLProvider.h’ file not found or RN: ‘React/RCTBridgeModule.h’ file not found . It is because React target is not linked yet.
To solve it, we must disable Parallelise Build and add React target and move it above MyApp Staging.
Staging and production product flavors in Android
Open the android folder in Android Studio. By default there are only debug and release build types:
They are configured in the app module build.gradle:
First, let’s change application id to com.onmyway133.MyApp to match iOS. It is not required but I think it’s good to stay organised. Then create two product flavors for staging and production. For staging, let’s add .Staging to the application id.
From Android Studio 3, “all flavors must now belong to a named flavor dimension” — normally we just need default dimensions. Here is how it looks in build.gradle for our app module:
Click Sync Now to let gradle do the syncing job. After that, we can see that we have four build variants:
How to run staging and production
To run the Android app, we can specify a variant like react-native run-android — variant=productionDebug, but I prefer to go to Android Studio, select the variant, and run.
To run iOS app, we can specify the scheme like react-native run-ios — simulator=’iPhone X’ — scheme=”MyApp Staging” . As of react-native 0.57.0 this does not work. But it does not matter as I usually go to Xcode, select the scheme, and run.
Add app icon for iOS
According to the Human Interface Guideline, we need app icons of different sizes for different iOS versions, device resolutions, and situations (notification, settings, Spring Board). I’ve crafted a tool called IconGenerator, which was previously mentioned in Best Open Source Tools For Developers. Drag the icon that you want — I prefer those with 1024x1024 pixels for high resolution app icons — to the Icon Generator MacOS app.
Click Generate and we get AppIcon.appiconset . This contains app icons of the required sizes that are ready to be used in Asset Catalog. Drag this to Asset Catalog in Xcode. That is for production.
For staging, it’s good practice to add a “Staging” banner so that testers know which is staging, and which is production. We can easily do this in Sketch.
Remember to set a background, so we don’t get a transparent background. For an app icon with transparent background, iOS shows the background as black which looks horrible.
After exporting the image, drag the staging icon to the IconGenerator the same way we did earlier. But this time, rename the generated appiconset to AppIcon-Staging.appiconset. Then drag this to Asset Catalog in Xcode.
For the staging target to use staging app icons, open MyApp Staging target and choose AppIcon-Staging as App Icon Source.
Add app icon for Android
I like to switch to Project view, as it is easier to change app icons. Click res -> New -> Image Asset to open Asset Studio. We can use the same app icons that we used in iOS:
Android 8.0 (API level 26) introduced Adaptive Icons so we need to tweak the Resize slider to make sure our app icons look as nice as possible.
Android 8.0 (API level 26) introduces adaptive launcher icons, which can display a variety of shapes across different device models. For example, an adaptive launcher icon can display a circular shape on one OEM device, and display a squircle on another device. Each device OEM provides a mask, which the system then uses to render all adaptive icons with the same shape. Adaptive launcher icons are also used in shortcuts, the Settings app, sharing dialogs, and the overview screen. — Android developers
We are doing for production first, which means the main Res Directory. This step will replace the existing placeholder app icons generated by Android Studio when we bootstrapped React Native projects.
Now that we have production app icons, let’s make staging app icons. Android manages code and assets via convention. Click on src -> New -> Directory and create a staging folder. Inside staging, create a folder called res . Anything we place in staging will replace the ones in main — this is called source sets.
You can use source set directories to contain the code and resources you want packaged only with certain configurations. For example, if you are building the “demoDebug” build variant, which is the crossproduct of a “demo” product flavor and “debug” build type, Gradle looks at these directories, and gives them the following priority: src/demoDebug/ (build variant source set) src/debug/ (build type source set) src/demo/ (product flavor source set) src/main/ (main source set)
Right click on staging/res -> New -> Image Asset to make app icons for staging. We also use the same staging app icons like in iOS, but this time we choose staging as Res Directory. This way Android Studio knows how to generate different ic_launcher and put them into staging.
Add launch screen for iOS
The splash screen is called a [Launch Screen](http://Launch Screen) in iOS, and it is important.
A launch screen appears instantly when your app starts up. The launch screen is quickly replaced with the first screen of your app, giving the impression that your app is fast and responsive
In the old days, we needed to use static launch images with different sizes for each device and orientation.
Launch Screen storyboard
For now the recommended way is to use Launch Screen storyboard . The iOS project from React Native comes with LaunchScreen.xib but xib is a thing of the past. Let’s delete it and create a file called Launch Screen.storyboard .
Right click on MyApp folder -> New and chose Launch Screen, add it to both targets as usually we show the same splash screen for both staging and production.
Image Set
Open asset catalog, right click and select New Image Set . We can name it anything. This will be used in the Launch Screen.storyboard.
Open Launch Screen.storyboard and add an UIImageView . If you are using Xcode 10, click the Library button in the upper right corner and choose Show Objects Library.
Set image for Image View, and make sure Content Mode is set to Aspect Filled, as this ensures that the image always covers the full screen (although it may be cropped). Then connect ImageView using constraints to the View, not the Safe Area. You do this by Control+drag from the Image View (splash) to the View.
Constrains without margin
Click into each constraint and uncheck Relative to Margin. This makes our ImageView pin to the very edges of the view and with no margin at all.
Now go to both targets and select Launch Screen.storyboard as Launch Screen File:
On iOS, the launch screen is often cached, so you probably won’t see the changes. One way to avoid that is to delete the app and run it again.
Add a launcher theme for Android
There are severalways to add splash screen for Android, from using launcher themes, Splash Activity, and a timer. For me, a reasonable splash screen for Android should be a very minimal image.
As there are many Android devices with different ratios and resolutions, if you want to show a full screen splash image, it will probably not scale correctly for each device. This is just about UX.
For the splash screen, let’s use the launcher theme with splash_background.xml .
Learn about Device Metric
There is no single splash image that suits all Android devices. A more logical approach is to create multiple splash images for all common resolutions in portrait and landscape. Or we can design a minimal splash image that works. You can find more info here: Device Metric.
Here is how to add splash screen in 4 easy steps:
Add splash image
We usually need a common splash screen for both staging and production. Drag an image into main/res/drawble . Android Studio seems to have a problem with recognising some jpg images for the splash screen, so it’s best to choose png images.
Add splash_background.xml
Right click on drawable -> New -> Drawable resource file . Name it whatever you want — I choose splash_background.xml . Choose the root element as layer-list:
A [Layer List](http://Layer List) means “a Drawable that manages an array of other Drawables. These are drawn in array order, so the element with the largest index is drawn on top”. Here is how splash_background.xml looks like:
<?xml version="1.0" encoding="utf-8"?>
<!-- The android:opacity=”opaque” line — this is critical in preventing a flash of black as your theme transitions. -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:opacity="opaque">
<!-- The background color, preferably the same as your normal theme -->
<item android:drawable="@android:color/white"/>
<!-- Your splash image -->
<item>
<bitmap
android:src="@drawable/iron_man"
android:gravity="center"/>
</item>
</layer-list>
Note that we point to our splash image we added earlier with android:src=”@drawable/iron_man”.
Go to Manifest.xml and change the theme of the the launcher activity, which has category android:name=”android.intent.category.LAUNCHER” . Change it to android:theme=”@style/SplashTheme” . For React Native, the launcher activity is usually MainActivity . Here is how Manifest.xml looks:
Run the app now and you should see the splash screen showing when the app starts.
Managing environment configurations
The differences between staging and production are just about app names, application ids, and app icons. We probably use different API keys, and backend URL for staging and production.
Right now the most popular library to handle these scenarios is react-native-config, which is said to “bring some 12 factor love to your mobile apps”. It requires lots of steps to get started, and I hope there is a less verbose solution.
Where to go from here
In this post, we touched Xcode and Android Studio more than Visual Studio Code, but this was inevitable. I hope this post was useful to you. Here are some more links to read more about this topic:
React Native can be very easy to get started with, and then at some point problems occur and we need to dive deep into it.
The other day we had a strange bug that was only occurring in production build, and in iOS only. A long backtrace in the app revealed that it was due to Date constructor failure.
const date = new Date("2019-01-18 12:00:00")
This returns the correct Date object in debug mode, but yields Invalid Date in release. What’s special about Date constructor? Here I’m using react native 0.57.5 and no Date libraries.
Date constructor
The best resource for learning Javascript is via Mozilla web docs, and entering Date:
Creates a JavaScript Date instance that represents a single moment in time. Date objects use a Unix Time Stamp, an integer value that is the number of milliseconds since 1 January 1970 UTC.
Pay attention to how Date can be constructed by dateString:
So Date constructor uses static method Date.parse under the hood. This has very specific requirement about the format of date string that it supports
The standard string representation of a date time string is a simplification of the ISO 8601 calendar date extended format (see Date Time String Format section in the ECMAScript specification for more details). For example, “2011-10-10” (date-only form), “2011-10-10T14:48:00” (date-time form), or “2011-10-10T14:48:00.000+09:00” (date-time form with milliseconds and time zone) can be passed and will be parsed. When the time zone offset is absent, date-only forms are interpreted as a UTC time and date-time forms are interpreted as local time. The ECMAScript specification states: If the String does not conform to the standard format the function may fall back to any implementation–specific heuristics or implementation–specific parsing algorithm. Unrecognizable strings or dates containing illegal element values in ISO formatted strings shall cause Date.parse() to return NaN.
The reason that we get Invalid Date in iOS must be because the code was run in two different JavaScript environments and they somehow have different implementation of the Date parsing function.
When using React Native, you’re going to be running your JavaScript code in two environments:
In most cases, React Native will use JavaScriptCore, the JavaScript engine that powers Safari. Note that on iOS, JavaScriptCore does not use JIT due to the absence of writable executable memory in iOS apps.
When using Chrome debugging, all JavaScript code runs within Chrome itself, communicating with native code via WebSockets. Chrome uses V8 as its JavaScript engine.
While both environments are very similar, you may end up hitting some inconsistencies. We’re likely going to experiment with other JavaScript engines in the future, so it’s best to avoid relying on specifics of any runtime.
React Native also uses Babel and some polyfills to have some nice syntax transformers, so some of the code that we write may not be necessarily supported natively by JavascriptCore.
Now it is clear that while we debug our app via Chrome debugger, it works because V8 engine handles that. Now try turning off Remote JS Debugging: we can see that the above Date constructor fails, which means it is using JavascriptCore.
To confirm this issue, let’s run our app in Xcode and go to the Safari app on MacOS to enter Development menu. Select the active Simulator and choose JSContext on the current iOS app. Remember to turn off Remote JS Debugging so that the app uses JavascriptCore:
Now open the Console in Safari dev tools, and we should have access to JavascriptCore inside our app. Try running the above Date constructor to confirm that it fails:
And it was fully confirmed a year later in JSC 💕 ES6
ES2015 (also known as ES6), the version of the JavaScript specification ratified in 2015, is a huge improvement to the language’s expressive power thanks to features like classes, for-of, destructuring, spread, tail calls, and much more WebKit’s JavaScript implementation, called JSC (JavaScriptCore), implements all of ES6
For more details about JavaScript features supported by different JavaScript engines, visit this ECMAScript comparison table.
Now for the date string format, from Date.parse, let’s visit ECMAScript 2015 specification to see what it says about date string format:
ECMAScript defines a string interchange format for date-times based upon a simplification of the ISO 8601 Extended Format. The format is as follows: YYYY-MM-DDTHH:mm:ss.sssZ
Where the fields are as follows:
“T” appears literally in the string, to indicate the beginning of the time element.
So JavascriptCore requires T specifier and V8 can work without it. The fix for now is to always include that T specifier. This way we always follow ECMAScript standards to make sure it works across different JavaScript environments.
const date = new Date("2019-01-18 12:00:00".replace(' ', 'T'))
And now it returns correct Date object. There may be difference between JavascriptCore on iOS and macOS, and among different iOS versions. The lesson learned here is that we should always test our app thoroughly in production and on devices to make sure it works as expected.
React Native lets us build mobile apps using only Javascript. It works by providing a common interface that talks to native iOS and Android components. There are enough essentials components to get started, but the cooler thing is that it is easy to build our own, hence we are not limited by React Native. In this post we will implement a linear gradient view, which is not supported by default in React Native, using native UI component, particularly CAGradientLayer in iOS and GradientDrawable in Android.
In Javascript there are hundreds of libraries for a single problem and you should check if you really need it or not. A search on Google for linear gradient shows a bunch of libraries, like react-native-linear-gradient. The less dependencies the better. Linear gradient is in fact very easy to build and we probably don’t need to add extra dependencies. Also integratingandfollowingupdates with 3rd libraries are painful, I would avoid that as much as possible.
Native UI component vs Native module
In React Native, there are native UI component and native module. React Native moves pretty fast so most of the articles will be outdated, it’s best to consult official documentation for the latest React Native version. This post will try to give you overview of the whole picture because for now the official guide seems not completed.
In simple explanation, native UI component is about making UIView in iOS or View in Android available as React.Component and used in render function in Javascript.
There are tons of native UI widgets out there ready to be used in the latest apps — some of them are part of the platform, others are available as third-party libraries, and still more might be in use in your very own portfolio. React Native has several of the most critical platform components already wrapped, like ScrollView and TextInput, but not all of them, and certainly not ones you might have written yourself for a previous app.
Native module is more general in that we make any native class available in Javascript.
Sometimes an app needs access to platform API, and React Native doesn’t have a corresponding module yet. Maybe you want to reuse some existing Objective-C, Swift or C++ code without having to reimplement it in JavaScript, or write some high performance, multi-threaded code such as for image processing, a database, or any number of advanced extensions.
View Manager
To expose native UI views, we use the ViewManager as the bridge, it is RCTViewManager in iOS and SimpleViewManager in Android. Then inside this ViewManager we can just return our custom view. I see it’s good to use Objective C/Java for the ViewManager to match React Native classes, and the custom view we can use either Swift/Objective C in iOS and Kotlin/Java in Android.
I prefer to use Swift, but in this post to remove the overhead of introducing bridging header from Swift to Objective C, we use Objective C for simplicity. We also add the native source code directly into iOS and Android project, but in the future we can extract them easily to a React Native library.
For now let ‘s use the name RNGradientViewManager and RNGradientView to stay consistent between iOS and Android. The RN prefix is arbitrary, you can use any prefix you want, but here I use it to indicate that these classes are meant to be used in Javascript side in React Native.
Implement in iOS
Project structure
Add these Objective-C classes to the projects, I usually place them inside NativeComponents folder
Native views are created and manipulated by subclasses of RCTViewManager. These subclasses are similar in function to view controllers, but are essentially singletons - only one instance of each is created by the bridge. They expose native views to the RCTUIManager, which delegates back to them to set and update the properties of the views as necessary. The RCTViewManagers are also typically the delegates for the views, sending events back to JavaScript via the bridge.
RNGradientViewManager
Create a RNGradientViewManager that inherits from RCTViewManager
In iOS we use macro RCT_EXPORT_MODULE() to automatically register the module with the bridge when it loads. The optional js_name argument will be used as the JS module name. If omitted, the JS module name will match the Objective-C class name.
#define RCT_EXPORT_MODULE(js_name)
The ViewManager, not the View, is the facade to the Javascript side, so we expose properties using RCT_EXPORT_VIEW_PROPERTY . Note that we do that inside @implementation RNGradientViewManager
Here we specify the types as NSNumber and UIColor , and later in Javascript we can just pass number and color hex string, and React Native can do the conversions for us. In older versions of React Native, we need processColor in Javascript or RCTConvert color in iOS side, but we don’t need to perform manual conversion now.
RNGradientView
In the Native UI component example for iOS, they use WKWebView but here we make a RNGradientView which subclasses from RCTView to take advantage of many features of React Native views, and to avoid some problems we can get if using a normal UIView
We can implement anything we want in this native view, in this case we use CAGradientLayer to get nicely displayed linear gradient. Since RNGradientViewManager exposes some properties like progress, cornerRadius, fromColor, toColor we need to implement some setters as they will be called by React Native when we update values in Javascript side. In the setter we call setNeedsLayout to tell the view to invalidate the layout, hence layoutSubviews will be called again.
requireNativeComponent
Open project in Visual Studio Code, add GradientView.js to src/nativeComponents . The folder name is arbitrary, but it’s good to stay organised.
import { requireNativeComponent } from 'react-native'
module.exports = requireNativeComponent('RNGradientView', null)
Here we use requireNativeComponent to load our RNGradientView . We only need this one Javascript file for interacting with both iOS and Android. You can name the module as RNGradientView but I think the practice in Javascript is that we don’t use prefix, so we name just GradientView .
Before I tried to use export default for the native component, but this way the view is not rendered at all, even if I wrap it inside React.Component . It seems we must use module.exports for the native component to be properly loaded.
Now using it is as easy as declare the GradientView with JSX syntax
Add these Java classes to the projects, I usually place them inside nativeComponents folder
RNGradientManager
Create a RNGradientManager that extends SimpleViewManager RNGradientManager.java
package com.onmyway133.myApp.nativeComponents;
import android.support.annotation.Nullable;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
public class RNGradientViewManager extends SimpleViewManager<RNGradientView> {
[@Override](http://twitter.com/Override)
public String getName() {
return "RNGradientView";
}
[@Override](http://twitter.com/Override)
protected RNGradientView createViewInstance(ThemedReactContext reactContext) {
return new RNGradientView(reactContext);
}
// Properties
[@ReactProp](http://twitter.com/ReactProp)(name = "progress")
public void setProgress(RNGradientView view, [@Nullable](http://twitter.com/Nullable) float progress) {
view.setProgress(progress);
}
[@ReactProp](http://twitter.com/ReactProp)(name = "cornerRadius")
public void setCornerRadius(RNGradientView view, [@Nullable](http://twitter.com/Nullable) float cornerRadius) {
view.setCornerRadius(cornerRadius);
}
[@ReactProp](http://twitter.com/ReactProp)(name = "fromColor", customType = "Color")
public void setFromColor(RNGradientView view, [@Nullable](http://twitter.com/Nullable) int color) {
view.setFromColor(color);
}
[@ReactProp](http://twitter.com/ReactProp)(name = "toColor", customType = "Color")
public void setToColor(RNGradientView view, [@Nullable](http://twitter.com/Nullable) int color) {
view.setToColor(color);
}
}
We usually use Color as android.graphics.Color , but for the GradientDrawable that we are going to use, it use color as ARGB integer. So it’s nifty that React Native deals with Color as int type. We also need to specify customType = “Color” as Color is something kinda custom.
RNGradientView
This is where we implement our view, we can do that in Kotlin if we like.
RNGradientView.java
package com.onmyway133.myApp.nativeComponents;
import android.content.Context;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.ScaleDrawable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
public class RNGradientView extends View {
float progress;
float cornerRadius;
int fromColor;
int toColor;
public RNGradientView(Context context) {
super(context);
}
public RNGradientView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public RNGradientView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public RNGradientView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
// update
void update() {
GradientDrawable gradient = new GradientDrawable();
gradient.setColors(new int[] {
this.fromColor,
this.toColor
});
gradient.setOrientation(GradientDrawable.Orientation.*LEFT_RIGHT*);
gradient.setGradientType(GradientDrawable.*LINEAR_GRADIENT*);
gradient.setShape(GradientDrawable.*RECTANGLE*);
gradient.setCornerRadius(this.cornerRadius * 4);
ScaleDrawable scale = new ScaleDrawable(gradient, Gravity.*LEFT*, 1, -1);
scale.setLevel((int)(this.progress * 10000));
this.setBackground(scale);
}
// Getter & setter
public void setProgress(float progress) {
this.progress = progress;
this.update();
}
public void setCornerRadius(float cornerRadius) {
this.cornerRadius = cornerRadius;
this.update();
}
public void setFromColor(int fromColor) {
this.fromColor = fromColor;
this.update();
}
public void setToColor(int toColor) {
this.toColor = toColor;
this.update();
}
}
Pay attention to the setColors as it use an array of int
Sets the colors used to draw the gradient. Each color is specified as an ARGB integer and the array must contain at least 2 colors.
If we call setBackground with the GradientDrawable it will be stretched to fill the view. In our case we want to support progress which determines how long the gradient should show. To fix that we use ScaleDrawable which is a Drawable that changes the size of another Drawable based on its current level value.
The same value for cornerRadius works in iOS, but for Android we need to use higher values, that’s why the multiplication in gradient.setCornerRadius(this.cornerRadius * 4)
Shape drawable
Another way to implement gradient is to use Shape Drawable with xml , it’s the equivalent of using xib in iOS. We can create something like gradient.xml and put that inside /res/drawable
We can also use the class directly ShapeDrawable in code
A Drawable object that draws primitive shapes. A ShapeDrawable takes a Shape object and manages its presence on the screen. If no Shape is given, then the ShapeDrawable will default to a RectShape. This object can be defined in an XML file with the element.
GradientManagerPackage
In iOS we use RCT_EXPORT_MODULE to register the component, but in Android, things are done explicitly using Package . A package can register both native module and native UI component. In this case we deal with just UI component, so let’s return RNGradientManager in createViewManagers
GradientManagerPackage.java
package com.onmyway133.myApp.nativeComponents;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class RNGradientViewPackage implements ReactPackage {
[@Override](http://twitter.com/Override)
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
[@Override](http://twitter.com/Override)
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new RNGradientViewManager()
);
}
}
Then head over to MainApplication.java to declare our package
[@Override](http://twitter.com/Override)
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new RNGradientViewPackage()
);
}
That’s it for Android. We already have the GradientView.js written earlier, when running the app in Android, it will look up and load our RNGradientView
Where to go from here
Hope you learn something about native UI component. In the post we only touch the surfaces on what native UI component can do, which is just passing configurations from Javascript to native. There are a lot more to discover, like event handling, thread, styles, custom types, please consult the official documentation for correct guidance.
Showing and dismiss keyboard seems like a trivial thing to do in mobile apps, but it can be tricky in automatically dismissing it when it comes together with react-navigation and modal presentation. At least that’s according to my initial assumption. This article aims to detail what I have learned about keyboard handling and how to avoid extra tap when dealing with TextInput There will also be lots of code spelunking, thanks to the all the libraries being open source. The version of React Native I’m using at the time of writing is 0.57.5
The built in TextInput component
React Native comes with a bunch of basic components, one of them is the TextInput for inputting text into the app via a keyboard.
That’s it, whenever we click on the text input, keyboard appears allowing us to enter values. To dismiss the keyboard by pressing anywhere on the screen, the easy solution is to TouchableWithoutFeedback together with Keyboard . This is similar to having UITapGestureRecognizer in iOS UIView and calling view.endEditing
import { Keyboard } from 'react-native'
Keyboard.dismiss()
TextInput inside ScrollView
Normally we should have some text inputs inside a scrolling component, in React Native that is mostly ScrollView to be able to handle long list of content and avoid keyboard. If TextInput is inside ScrollView then the way keyboard gets dismissed behaves a bit differently, and depends on keyboardShouldPersistTaps
Determines when the keyboard should stay visible after a tap.
‘never’ (the default), tapping outside of the focused text input when the keyboard is up dismisses the keyboard. When this happens, children won’t receive the tap.
‘always’, the keyboard will not dismiss automatically, and the scroll view will not catch taps, but children of the scroll view can catch taps.
‘handled’, the keyboard will not dismiss automatically when the tap was handled by a children, (or captured by an ancestor).
The never mode should be the desired behaviour in most cases, clicking anywhere outside the focused text input should dismiss the keyboard.
In my app, there are some text inputs and an action button. The scenario is that users enter some infos and then press that button to register data. With the never mode, we have to press button twice, one for dismissing the keyboard, and two for the onPress of the Button . So the solution is to use always mode. This way the Button always gets the press event first.
<ScrollView keyboardShouldPersistTaps='always' />
ScrollView cares about keyboard
The native RCTScrollView class that power react native ScrollView has code to handle dismiss mode
The option that it chooses is UIScrollViewKeyboardDismissMode for keyboardDismissMode property
The manner in which the keyboard is dismissed when a drag begins in the scroll view.
As you can see, the possible modes are onDrag and interactive . And react native exposes customization point for this via keyboardShouldPersistTaps
case none The keyboard does not get dismissed with a drag.
case onDrag The keyboard is dismissed when a drag begins.
case interactive The keyboard follows the dragging touch offscreen, and can be pulled upward again to cancel the dismiss.
ScrollView inside a Modal
But that does not work when ScrollView is inside Modal . By Modal I meant the Modal component in React Native. The only library that I use is react-navigation , and it supports Opening a full-screen modal too, but they way we declare modal in react-navigation looks like stack and it is confusing, so I would rather not use it. I use Modal in react-native and that works pretty well.
So if we have TextInput inside ScrollView inside Modal then keyboardShouldPersistTaps does not work. Modal seems to be aware of parent ScrollView so we have to declare keyboardShouldPersistTaps=’always’ on every parent ScrollView . In React Native FlatList and SectionList uses ScrollView under the hood, so we need to be aware of all those ScrollView components.
Like every traditional mobile apps, my app consists of many stack navigators inside tab navigator. In iOS that means many UINavigationViewController inside UITabbarController . In react-navigation I use createMaterialTopTabNavigator inside createBottomTabNavigator
import { createMaterialTopTabNavigator } from 'react-navigation'
import { createBottomTabNavigator, BottomTabBar } from 'react-navigation-tabs'
The screen I have keyboard issue is a Modal presented from the 2nd screen in one of the stack navigators, so let’s examine every possible ScrollView up the hierarchy. This process involves lots of code reading and this’s how I love open source.
class TabNavigationView extends React.PureComponent<Props, State>
export default createTabNavigator(TabNavigationView);
Tab navigator has tab bar view below ScreenContainer , which is used to contain view. ScreenContainer is from react-native-screens “This project aims to expose native navigation container components to React Native”. Below is how tab navigator works.
Tab bar is rendered using BottomTabBar in _renderTabBar function. Looking at the code, the whole tab navigator has nothing to do with ScrollView .
So there is only createMaterialTopTabNavigator left on the suspecting list. I use it in the app with swipeEnabled: true . And by looking at the imports, top tab navigator has
import MaterialTopTabBar, { type TabBarOptions,} from '../views/MaterialTopTabBar';
and it renders PagerDefault, which in turn uses PagerScroll for iOS
import { Platform } from 'react-native';
let Pager;
switch (Platform.OS) {
case 'android':
Pager = require('./PagerAndroid').default;
break;
case 'ios':
Pager = require('./PagerScroll').default;
break;
default:
Pager = require('./PagerPan').default;
break;
}
export default Pager;
So PagerScroll uses ScrollView to handle scrolling to match material style that user can scroll between pages, and it has keyboardShouldPersistTaps=”always” which should be correct.
So nothing looks suspicious in react-navigation , which urges me to look at code from my project.
Debugging FlatList, SectionList and ScrollView
Like I stated in the beginning of this article, the root problem is that we need to declare keyboardShouldPersistTaps for all parent ScrollView in the hierarchy. That means to look out for any FlatList, SectionList and ScrollView
Luckily, there is react-devtools that shows tree of all rendered components in react app, and that is also guided in Debugging section of react native.
So after searching I found out that there is a SectionList up the hierarchy that should have keyboardShouldPersistTaps=’always’ while it didn’t.
Taking a thorough look at the code, I found out that the Modal is trigged from a SectionList item. We already know that triggering Modal in react native means that to embed that Modal inside the view hierarchy and control its visibility via a state. So in terms of view and component, that Modal is inside a SectionList . And for your interest, if you dive deep into react native code, SectionList in my case is just VirtualizedSectionList , which is VirtualizedList, which uses ScrollView
So after I declare keyboardShouldPersistTaps=’always’ in that SectionList , the problem is solved. User can now just enters some values in the text inputs, then press once on the submit button to submit data. The button now captures touch events first instead of scrollview.
Where to go from here
The solution for this is fortunately simple as it involves fixing our code without having to alter react-navigation code. But it’s good to look at the library code to know what it does, and to trace where the problem originates. Thanks for following such long exploring and hope you learn something.
React and React Native are just frameworks, and they do not dictate how we should structure our projects. It all depends on your personal taste and the project you’re working on.
In this post, we will go through how to structure a project and how to manage local assets. This of course is not written in stone, and you are free to apply only the pieces that suit you. Hope you learn something.
There is the ios folder for Xcode projects, the android folder for Android projects, and an index.js and an App.js file for the React Native starting point.
ios/
android/
index.js
App.js
As someone who has worked with native on both Windows Phone, iOS and Android, I find that structuring a project all comes down to separating files by type or feature
type vs feature
Separating by type means that we organise files by their type. If it is a component, there are container and presentational files. If it is Redux, there are action, reducer, and store files. If it is view, there are JavaScript, HTML, and CSS files.
Group by type
redux
actions
store
reducers
components
container
presentational
view
javascript
html
css
This way, we can see the type of each file, and easily run a script toward a certain file type. This is general for all projects, but it does not answer the question “what is this project about?” Is it news application? Is it a loyalty app? Is it about nutrition tracking?
Organising files by type is for a machine, not for a human. Many times we work on a feature, and finding files to fix in multiple directories is a hassle. It’s also a pain if we plan to make a framework out of our project, as files are spread across many places.
Group by feature
A more reasonable solution is to organise files by feature. Files related to a feature should be placed together. And test files should stay close to the source files. Check out this article to learn more.
A feature can be related to login, sign up, onboarding, or a user’s profile. A feature can contain sub-features as long as they belong to the same flow. If we wanted to move the sub feature around, it would be easy, as all related files are already grouped together.
My typical project structure based on features looks like this:
Besides the traditional files App.js and index.js and the ios1 and android folders, I put all the source files inside the src folder. Inside src I have res for resources, library for common files used across features, and screens for a screen of content.
As few dependencies as possible
Since React Native is heavily dependent on tons of dependencies, I try to be pretty aware when adding more. In my project I use just react-navigation for navigation. And I’m not a fan of redux as it adds unneeded complexity. Only add a dependency when you really need it, otherwise you are just setting yourself up for more trouble than value.
The thing I like about React is the components. A component is where we define view, style and behavior. React has inline style — it’s like using JavaScript to define script, HTML and CSS. This fits the feature approach we are aiming for. That’s why I don’t use styled-components. Since styles are just JavaScript objects, we can just share comment styles in library .
src
I like Android a lot, so I name src and res to match its folder conventions.
react-native init sets up babel for us. But for a typical JavaScript project, it’s good to organise files in the src folder. In my electron.js application IconGenerator, I put the source files inside the src folder. This not only helps in terms of organising, but also helps babel transpile the entire folder at once. Just a command and I have the files in src transpiled to dist in a blink.
babel ./src --out-dir ./dist --copy-files
Screen
React is based around components. Yup. There are container and presentational components, but we can compose components to build more complex components. They usually end in showing in the full screen. It is called Page in Windows Phone, ViewController in iOS and Activity in Android. The React Native guide mentions screen very often as something that covers the entire space:
Mobile apps are rarely made up of a single screen. Managing the presentation of, and transition between, multiple screens is typically handled by what is known as a navigator.
index.js or not?
Each screen is considered the entry point for each feature. You can rename the LoginScreen.js to index.js by leveraging the Node module feature:
Modules don’t have to be files. We can also create a find-me folder under node_modules and place an index.js file in there. The same require(‘find-me’) line will use that folder’s index.js file
So instead of import LoginScreen from ‘./screens/LoginScreen’ , we can just do import LoginScreen from ‘./screens’.
Using index.js results in encapsulation and provides a public interface for the feature. This is all personal taste. I myself prefer explicit naming for a file, hence the name LoginScreen.js.
Navigator
react-navigation seems to be the most popular choice for handling navigation in a React Native app. For a feature like onboarding, there are probably many screens managed by a stack navigation, so there is OnboardingNavigator .
You can think of Navigator as something that groups sub screens or features. Since we group by feature, it’s reasonable to place Navigator inside the feature folder. It basically looks like this:
import { createStackNavigator } from 'react-navigation'
import Welcome from './Welcome'
import Term from './Term'
const routeConfig = {
Welcome: {
screen: Welcome
},
Term: {
screen: Term
}
}
const navigatorConfig = {
navigationOptions: {
header: null
}
}
export default OnboardingNavigator = createStackNavigator(routeConfig, navigatorConfig)
library
This is the most controversial part of structuring a project. If you don’t like the name library, you can name it utilities, common, citadel , whatever…
This is not meant for homeless files, but it is where we place common utilities and components that are used by many features. Things like atomic components, wrappers, quick fixes function, networking stuff, and login info are used a lot, and it’s hard to move them to a specific feature folder. Sometimes we just need to be practical and get the work done.
In React Native, we often need to implement a button with an image background in many screens. Here is a simple one that stays inside library/components/ImageButton.js . The components folder is for reusable components, sometimes called atomic components. According to React naming conventions, the first letter should be uppercase.
Then somewhere in the src/screens/onboarding/term/Term.js , we can import by using relative paths:
import moveToBottom from '../../../../library/utils/move'
import ImageButton from '../../../../library/components/ImageButton'
This is a big red flag in my eyes. It’s error prone, as we need to calculate how many .. we need to perform. And if we move feature around, all of the paths need to be recalculated.
Since library is meant to be used many places, it’s good to reference it as an absolute path. In JavaScript there are usually 1000 libraries to a single problem. A quick search on Google reveals tons of libraries to tackle this issue. But we don’t need another dependency as this is extremely easy to fix.
The solution is to turn library into a module so node can find it. Adding package.json to any folder makes it into a Node module . Add package.json inside the library folder with this simple content:
{
"name": "library",
"version": "0.0.1"
}
Now in Term.js we can easily import things from library because it is now a module:
You may wonder what res/colors, res/strings , res/images and res/fonts are in the above examples. Well, for front end projects, we usually have components and style them using fonts, localised strings, colors, images and styles. JavaScript is a very dynamic language, and it’s easy to use stringly types everywhere. We could have a bunch of #00B75D color across many files, or Fira as a fontFamily in many Text components. This is error-prone and hard to refactor.
Let’s encapsulate resource usage inside the res folder with safer objects. They look like the examples below:
Like library , res files can be access from anywhere, so let’s make it a module . Add package.json to the res folder:
{
"name": "res",
"version": "0.0.1"
}
so we can access resource files like normal modules:
import strings from 'res/strings'
import palette from 'res/palette'
import images from 'res/images'
Group colors, images, fonts with palette
The design of the app should be consistent. Certain elements should have the same look and feel so they don’t confuse the user. For example, the heading Text should use one color, font, and font size. The Image component should use the same placeholder image. In React Native, we already use the name styles with const styles = StyleSheet.create({}) so let’s use the name palette.
Below is my simple palette. It defines common styles for heading and Text:
Here we use the object spread operator to merge palette.heading and our custom style object. This means that we use the styles from palette.heading but also specify more properties.
If we were to reskin the app for multiple brands, we could have multiple palettes. This is a really powerful pattern.
Generate images
You can see that in /src/res/images.js we have properties for each image in the src/res/images folder:
This is tedious to do manually, and we have to update if there’s changes in image naming convention. Instead, we can add a script to generate the images.js based on the images we have. Add a file at the root of the project /scripts/images.js:
The cool thing about Node is that we have access to the fs module, which is really good at file processing. Here we simply traverse through images, and update /src/res/images.js accordingly.
Whenever we add or change images, we can run:
node scripts/images.js
And we can also declare the script inside our main package.json :
Now we can just run npm run images and we get an up-to-date images.js resource file.
How about custom fonts
React Native has some custom fonts that may be good enough for your projects. You can also use custom fonts.
One thing to note is that Android uses the name of the font file, but iOS uses the full name. You can see the full name in Font Book app, or by inspecting in running app
for (NSString* family in [UIFont familyNames]) {
NSLog(@"%@", family);
for (NSString* name in [UIFont fontNamesForFamilyName: family]) {
NSLog(@"Family name: %@", name);
}
}
For custom fonts to be registered in iOS, we need to declare UIAppFonts in Info.plist using the file name of the fonts, and for Android, the fonts need to be placed at app/src/main/assets/fonts .
It is good practice to name the font file the same as full name. React Native is said to dynamically load custom fonts, but in case you get “Unrecognized font family”, then simply add those fonts to target within Xcode.
Doing this by hand takes time, luckily we have rnpm that can help. First add all the fonts inside res/fonts folder. Then simply declare rnpm in package.json and run react-native link . This should declare UIAppFonts in iOS and move all the fonts into app/src/main/assets/fonts for Android.
"rnpm": {
"assets": [
"./src/res/fonts/"
]
}
Accessing fonts by name is error prone, we can create a script similar to what we have done with images to generate a safer accession. Add fonts.js to our scripts folder
This step depends on personal taste, but I find it more organised if we introduce the R namespace, just like how Android does for assets with the generated R class.
Once you externalize your app resources, you can access them using resource IDs that are generated in your project’s Rclass. This document shows you how to group your resources in your Android project and provide alternative resources for specific device configurations, and then access them from your app code or other XML files.
This way, let’s make a file called R.js in src/res:
import strings from './strings'
import images from './images'
import colors from './colors'
import palette from './palette'
const R = {
strings,
images,
colors,
palette
}
export default R
Replace strings with R.strings, colors with R.colors, and images with R.images. With the R annotation, it is clear that we are accessing static assets from the app bundle.
This also matches the Airbnb convention for singleton, as our R is now like a global constant.
23.8 Use PascalCase when you export a constructor / class / singleton / function library / bare object.
In this post, I’ve shown you how I think you should structure folders and files in a React Native project. We’ve also learned how to manage resources and access them in a safer manner. I hope you’ve found it useful. Here are some more resources to explore further:
// Show something on top of other exportdefaultclassOverlayContainerextendsReact.Component<Props> { render() { const { behind, front, under } = this.props
This is a script to remove Cartography, and use plain NSLayoutAnchor syntax. Use Constraint.on() from Sugar. It will change all .swift files recursively under provided folder.
/// Turn every line into flatten transformed line functionhandleMatch(match) { let lines = match.split('\n') lines = lines.map((line) => { return handleLine(line.trim()) }).filter((transforms) => { return transforms != null })
let flatten = [].concat.apply([], lines) return flatten }
/// Check to handle lines, turn them into `LayoutAnchor` statements functionhandleLine(line) { const itemPattern = '\\w*\\.\\w* (=|<|>)= \\w*\\.*\\..*' const sizePattern = '\w*\.\w* (=|<|>)= (\s|.)*'
if (line.includes('edges == ')) { return handleEdges(line) } elseif (line.includes('center == ')) { return handleCenter(line) } elseif (hasPattern(line, itemPattern) && !hasSizeKeywords(line)) { return handleItem(line) } elseif (hasPattern(line, sizePattern)) { return handleSize(line) } elseif (line.includes('>=') || line.includes('>=')) { return line } elseif (line.includes('.')) { // return the line itself to let the human fix return line } elseif (line.length == 0) { return ['\n'] } else { returnnull } }
/// For ex: listView.bottom == listView.superview!.bottom - 43 /// listView.bottom == listView.superview!.bottom - Metrics.BackButtonWidth functionhandleItem(line) { const equalSign = getEqualSign(line) const parts = line.split(` ${equalSign} `) const left = parts[0]
let rightParts = parts[1].trim() let right = rightParts.split(' ')[0] let number = rightParts.replace(right, '').replace('- ', '-').trim() if (number.startsWith('+ ')) { number = number.slice(2) } let equal = getEqual(line)
functionsort() { const string = ` - Favorite WWDC 2017 sessions https://github.com/onmyway133/blog/issues/56 - Favorite WWDC 2018 sessions https://github.com/onmyway133/blog/issues/245 - How to do clustering with Google Maps in iOS https://github.com/onmyway133/blog/issues/191 `
You may encounter curry in everyday code without knowing it. Here is a bit of my reflections on curry and how to apply it in Javascript and Swift.
Taking one parameter in Haskell
In Haskell, all function officially takes only 1 parameter. Function with many parameters are just curried, which means they will be partially applied. Calling sum 1 just returns a function with 1 parameter, then 2is passed into this function. The following 2 function calls are the same.
ghci> sum 1 2
3
ghci> (max 1) 2
3
I tend to think of curried function or partially applied function as something that carry dependencies at each application step. Each curried function can be assigned to variable or pass around, or as returned value.
Curry in Swift for predicate
When I was trying to make my own Signal library, I have
and Event
filter
Then there should be a filter for Signal. The idea of filter is that we should update signal if the Event is Next with right filtered value
public func filter(f: T -> Bool) -> Signal<T>{
let signal = Signal<T>()
subscribe { result in
switch(result) {
case let .Success(value):
if f(value) {
signal.update(result)
}
case let .Error(error): signal.update(.Error(error))
}
}
return signal
}
2 parameters
But having Event as another monad, I think it should be more encapsulated if that switching logic gets moved into the Event. Here the filter takes 2 params
Event.swift
func filter(f: T -> Bool, callback: (Event<T> -> Void)) {
switch self {
case let .Next(value) where f(value):
callback(self)
case .Failed:
callback(self)
default:
break
}
}
Signal.swift
public func filter(f: T -> Bool) -> Signal<T> {
let signal = Signal<T>()
subscribe { event in
event.filter(f, callback: signal.update)
}
return signal
}
Currying
With currying, we can make filter a more abstract function, and defer the decision to pass the callback param. It is a little carried away but I find it helpful this way
Now filter accepts 1 param, and it returns a function that takes callback as its param
Event.swift
func filter(f: T -> Bool) -> ((Event<T> -> Void) -> Void) {
return { g in
switch self {
case let .Next(value) where f(value):
g(self)
case .Failed:
g(self)
default:
break
}
}
}
Signal.swift
public func filter(f: T -> Bool) -> Signal<T> {
let signal = Signal<T>()
subscribe { event in
event.filter(f)(signal.update)
}
return signal
}
Curry syntax in Swift 2 and above
Swift 2 supports curry syntax function
func sum(a: Int)(b: Int) -> Int {
return a + b
}
let sumWith5 = sum(5)
let result = sumWith5(b: 10)
Unfortunately, the syntactic sugar for declaring curry has been dropped since Swift 3. You may want to find out in Bidding farewell to currying. But it’s not a big deal as we can easily create curry function. It is just a function that returns another function.
Using curry for partial application in UIKit
I used this curry technique in my Xkcd app. See MainController.swift. MainController is vanilla UITabBarController with ComicsController and FavoriteController , all embedded in UINavigationViewController .
The feature is that when a comic is selected, a comic detail screen should be pushed on top of the navigation stack. For example in ComicsController
/// Called when a comic is selected
var selectComic: ((Comic) -> Void)?
All ComicsController needs to know is to call that selectComic closure with the chosen Comic, and someone should know how to handle that selection. Back to the handleFlow function inside MainController.
private func handleFlow() {
typealias Function = (UINavigationController) -> (Comic) -> Void
let selectComic: Function = { [weak self] navigationController in
return { (comic: Comic) in
guard let self = self else {
return
}
let detailController = self.makeDetail(comic: comic)
navigationController.pushViewController(detailController, animated: true)
}
}
comicsController.selectComic = selectComic(comicNavigationController)
favoriteController.selectComic = selectComic(favoriteNavigationController)
}
I declared Function as typealias to explicitly state the curry function that we are going to build
typealias Function = (UINavigationController) -> (Comic) -> Void
We build selectComic as curried function, that takes UINavigationViewController and returns a function that takes Comic and returns Void . This way when we partially apply selectComic with the a navigationController , we get another function that has navigationController as dependency, and ready to be assigned to selectComic property in comicsController .
Curry promised function in Javascript
I like to work with Promise and async/await in Javascript. It allows chainable style and easy to reason about. So when working with callbacks in Javascript, for example callback from native modules in React Native, I tend to convert them into Promise.
For example when working with HealthKit, we need to expose a native modules around it
However, as you can see in the signature, it only works with a callback of type (any) => void In other words, this callback must have exactly 1 parameter, because a Promise can either returns a value or throws an error.
To remedy this, we can build a curry function that can turns function with either 1, 2, 3 parameters into curried function. Thanks to the dynamic nature of Javascript, we have
So with a function that have 3 parameters, we can use curry3 to partially apply the first 2 parameters. Then we have a function that accepts just a callback, and this is turned into Promise via toPromise