This is a work in progress.
If anything is unclear or does not work, please let me know via email or create an issue/PR and I will attend to it as soon as I can.
If you'd like to test that you are setting the project up correctly, do a build on iOS and android after each major step.
-
Placeholder
react-native init temp
NOTE: We use 'temp' as a project name here so that we can correctly rename the project in [Step 4](#4-update-display-and-package-name)
-
Add repository to GitHub/Bitbucket.
-
Initialise git in project:
cd temp
git init
git remote add origin GIT_REPO_URL
git add .
git commit -m "Initial commit"
git push -u origin master
Create file ./android/local.properties
with the following contents:
sdk.dir = PATH_TO_SDK
Display name: The name of the app as it appears on the device screen, e.g. TapOff. Package name: The signature used by the app and play stores, e.g. co.za.auxstudio.tapoff.
(ONCE-OFF).
yarn global add react-native-rename
- Update the display and package name (android only):
NOTE: The display name will need to be different to the name you initialised the project with.
react-native-rename "NEW DISPLAY NAME" -b NEW_PACKAGE_NAME
- Update the package name in XCode (iOS only):
NOTE: Follow this step to a tee, don't modify Info.plist's bundle identifier, you will run into build issues.
In Xcode, Project
=> General
=> Identity
=> Bundle Identifier
=> NEW_PACKAGE_NAME
.
- Setup iOS app signing (since you have XCode open):
In Xcode, Project
=> General
=> Signing
=> Select team.
Approximately 33% smaller.
- In
./android/app/build.gradle
, replace as necessary:
def enableSeparateBuildPerCPUArchitecture = true
- Same file as above, remove (from android.defaultConfig):
ndk {
abiFilters "armeabi-v7a", "x86"
}
- Generate keystore:
keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000
-
Enter a password and your details.
-
Move the generated
./src/my-release-key.keystore
to./android/app/
. -
In
./android/gradle.properties
, add:
MYAPP_RELEASE_STORE_FILE=my-release-key.keystore
MYAPP_RELEASE_KEY_ALIAS=my-key-alias
MYAPP_RELEASE_STORE_PASSWORD=YOUR_PASSWORD
MYAPP_RELEASE_KEY_PASSWORD=YOUR_PASSWORD
- In .
/android/app/build.gradle
, add (in android.defaultConfig):
signingConfigs {
release {
if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) {
storeFile file(MYAPP_RELEASE_STORE_FILE)
storePassword MYAPP_RELEASE_STORE_PASSWORD
keyAlias MYAPP_RELEASE_KEY_ALIAS
keyPassword MYAPP_RELEASE_KEY_PASSWORD
}
}
}
- In the same file add (in android.buildTypes.release):
signingConfig signingConfigs.release
- If you want to commit the keystore to git:
In ./gitignore
, remove *.keystore
.
- Add two projects to the Firebase console.
The projects should be called PROJECT_NAME-development and PROJECT_NAME-production. In Step 18 we will configure these environments.
-
Add android and iOS apps to the
development
project. -
Copy the
development
project config files:- Copy
google-services.json
to./android/app
. - In XCode, drag
GoogleService-Info.plist
to project (make sure Copy items if needed is ticked).
- Copy
Remove what you don't need.
yarn add prop-types react-native-simple-components react-native-simple-animators react-native-vector-icons react-native-snackbar react-native-fast-image react-native-firebase redux redux-persist react-redux redux-saga react-native-router-flux react-native-fbsdk react-native-google-signin react-native-image-picker react-native-image-resizer react-native-permissions react-native-geocoder redux-logger react-native-keyboard-aware-scroll-view react-native-material-menu
In ./android/app/build.gradle
(at bottom of file add):
// react-native-vector-icons
project.ext.vectoricons = [
iconFontNames: [ 'MaterialIcons.ttf' ] // add whatever other icons you want
]
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
-
In Xcode, drag fonts to project (eg. MaterialIcons.ttf and any other custom fonts you want).
-
In
./ios/PROJECT_NAME/info.plist
add to the outermost dict:
<key>UIAppFonts</key>
<array>
<string>MaterialIcons.ttf</string>
</array>
react-native link react-native-snackbar
react-native link react-native-fast-image
NOTE: This is setup for login only.
-
Add Facebook app (you can skip the steps besides 3 and 6).
-
Add key hashes to Facebook app.
Run the below command twice. First with android as password and second with your project password. This will generate two debug key hashes.
keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64
Run the below command once with your project password. This will an additional key hash (which will enable the fbsdk on your production app).
keytool -exportcert -alias my-key-alias -keystore ./android/app/my-release-key.keystore | openssl sha1 -binary | openssl base64
You should have a total of 3 key hashes added to your Facebook app.
NOTE: Once Facebook app setup is complete, there is a toggle button at the top of the page that will default to development mode. When in production, switch this to live (you will need a privacy policy link). Otherwise your production build facebook logins will fail with all other users who are not admins.
- Link:
react-native link react-native-fbsdk
- In
./android/app/src/main/java/MainApplication.java
add (at top):
import com.facebook.CallbackManager;
import com.facebook.FacebookSdk;
- Same file as above add (beginning of public class MainApplication...):
private static CallbackManager mCallbackManager = CallbackManager.Factory.create();
protected static CallbackManager getCallbackManager() {
return mCallbackManager;
}
- Same file as above overwrite @Override (public void onCreate()):
@Override
public void onCreate() {
super.onCreate();
FacebookSdk.sdkInitialize(getApplicationContext());
}
- Same file as above replace (in packages):
new FBSDKPackage(mCallbackManager),
- In
./android/app/src/main/java/MainActivity.java
add (top of file):
import android.content.Intent;
- Same file as above, add (at beginning of public class MainActivity):
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
MainApplication.getCallbackManager().onActivityResult(requestCode, resultCode, data);
}
- In
./android/app/build.gradle
replace (to dependencies):
implementation project(':react-native-fbsdk')
implementation 'com.facebook.android:facebook-login:[4,5)'
- In
./android/app/src/main/res/values/strings.xml
add (completed as part of step 6 in Facebook app setup):
<string name="facebook_app_id">FACEBOOK_APP_ID</string>
<string name="fb_login_protocol_scheme">FACEBOOK_LOGIN_SCHEME</string>
- In
./android/app/src/main/AndroidManifest.xml
add (within tags):
<meta-data android:name="com.facebook.sdk.ApplicationId"
android:value="@string/facebook_app_id"/>
<activity android:name="com.facebook.FacebookActivity"
android:configChanges=
"keyboard|keyboardHidden|screenLayout|screenSize|orientation"
android:label="@string/app_name" />
<activity
android:name="com.facebook.CustomTabActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="@string/fb_login_protocol_scheme" />
</intent-filter>
</activity>
- In
./android/build.gradle
, add to allprojects:
configurations.all {
resolutionStrategy {
force 'com.facebook.android:facebook-android-sdk:4.28.0'
}
}
-
Follow the steps here.
-
Download the FacebookSDK. (ONCE-OFF).
-
Drag the downloaded Bolts.framework, FBSDKCoreKit.framework, FBSDKLoginKit.framework and FBSDKShareKit.framework into Frameworks folder of the project in XCode.
- Link:
react-native link react-native-google-signin
- In
./android/app/build.gradle
add/replace (dependencies):
implementation(project(":react-native-google-signin")){
exclude group: "com.google.android.gms" // very important
}
implementation 'com.google.android.gms:play-services-auth:12.0.1'
-
Drag and drop contents of the
./node_modules/react-native-google-signin/ios/GoogleSdk
folder to your XCode project. (make sure Copy items if needed is ticked) (copy this folder to./ios/
if you don't see it there). -
Configure URL types in the Info panel:
- add Identifier and URL Schemes with your REVERSED*CLIENT_ID (found inside the plist)
- add Identifier and URL Schemes set to your bundle id
- Add top of
./ios/AppDelegate.m
:
#import <RNGoogleSignin/RNGoogleSignin.h>
- Same file as above, replace openUrl function with:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
return [[FBSDKApplicationDelegate sharedInstance] application:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation
]
|| [RNGoogleSignin application:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation
];
}
NB
: In XCode, change the Framework Search Paths of RNGoogleSignIn to:
$(inherited) non-recursize
$(PROJECT_DIR) recursive
No android linking necessary.
Add permissions to ./android/app/src/main/AndroidManifest.xml
(remove what you don't need):
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_INTERAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_INTERAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" />
-
In the XCode's "Project navigator", right click on Libraries folder under your project =>
Add Files to <...>
-
Go to
node_modules
=>react-native-permissions
=>ios
=> selectReactNativePermissions.xcodeproj
-
Add
libReactNativePermissions.a
toBuild Phases -> Link Binary With Libraries
-
Add necessary permissions to
./ios/PROJECT_NAME/Info.plist
(remove what you don't need):
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>NSPhotoLibraryUsageDescription</key>
<string></string>
<key>NSCameraUsageDescription</key>
<string></string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string></string>
- Link:
react-native link react-native-firebase
- In
./android/build.gradle
, add/update to buildscript.dependencies:
classpath 'com.android.tools.build:gradle:3.1.4'
classpath 'com.google.gms:google-services:3.2.1'
- In
./android/app/build.gradle
, add to bottom of file:
apply plugin: 'com.google.gms.google-services'
- Same file as above, add to dependencies:
// Firebase dependencies
implementation "com.google.android.gms:play-services-base:15.0.1"
implementation "com.google.firebase:firebase-core:15.0.0"
implementation "com.google.firebase:firebase-analytics:15.0.2"
implementation "com.google.firebase:firebase-auth:15.1.0"
implementation "com.google.firebase:firebase-database:15.0.0"
implementation "com.google.firebase:firebase-storage:15.0.2"
implementation "com.google.firebase:firebase-messaging:15.0.2"
implementation "com.google.firebase:firebase-firestore:16.0.0"
implementation 'com.android.support:multidex:1.0.3' // needed for multidex
- Enable multi dex:
Same file as above, in android.defaultConfig:
multiDexEnabled true
- Same file as above, in dependencies, update all compile statements to use implementation, e.g.:
implementation(project(':react-native-firebase')) {
transitive = false
}
- In
./android/app/src/main/java/.../MainApplication.java
, add at top of file:
import io.invertase.firebase.RNFirebasePackage;
import io.invertase.firebase.analytics.RNFirebaseAnalyticsPackage;
import io.invertase.firebase.auth.RNFirebaseAuthPackage;
import io.invertase.firebase.database.RNFirebaseDatabasePackage;
import io.invertase.firebase.storage.RNFirebaseStoragePackage;
import io.invertase.firebase.messaging.RNFirebaseMessagingPackage;
import io.invertase.firebase.notifications.RNFirebaseNotificationsPackage;
import io.invertase.firebase.firestore.RNFirebaseFirestorePackage;
Same file as above, in getPackages(), add:
new RNFirebasePackage(),
new RNFirebaseAnalyticsPackage(),
new RNFirebaseAuthPackage(),
new RNFirebaseDatabasePackage(),
new RNFirebaseStoragePackage(),
new RNFirebaseMessagingPackage(),
new RNFirebaseNotificationsPackage(),
new RNFirebaseFirestorePackage()
- In
./ios/PROJECT_NAME/AppDelegate.m
, add to top of file:
#import <Firebase.h>
- Same file as above, add to beginning of didFinishLaunchingWithOptions method:
[FIRApp configure];
- Setup cocoapods:
cd ios
pod init
-
In
./ios/PodFile
delete duplicate PROJECT_NAME-tvOSTests within main project target. -
Update pods:
pod update
- Same file as above, uncomment:
platform :ios, '9.0'
- Same file as above, add these pods:
pod 'Firebase/Core'
pod 'Firebase/Analytics'
pod 'Firebase/Auth'
pod 'Firebase/Database'
pod 'Firebase/Storage'
pod 'Firebase/Messaging'
pod 'Firebase/Firestore'
- Install the pods:
pod install
- In
android/settings.gradle
...
include ':react-native-geocoder', ':app'
project(':react-native-geocoder').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-geocoder/android')
- In
android/app/build.gradle
...
dependencies {
...
implementation project(':react-native-geocoder')
}
- Register module (in MainApplication.java)
import com.devfd.RNGeocoder.RNGeocoderPackage;
public class MainActivity extends ReactActivity {
......
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new RNGeocoderPackage()) // <------ add this
}
......
}
-
In the XCode's "Project navigator", right click on Libraries folder under your project =>
Add Files to <...>
-
Go to
node_modules
=>react-native-geocoder
=>ios
=> selectRNGeocoder.xcodeproj
-
Add
libRNGeocoder.a
toBuild Phases -> Link Binary With Libraries
-
Add the following lines to
android/settings.gradle
:include ':react-native-image-picker' project(':react-native-image-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-image-picker/android')
-
Add the implementation line to the dependencies in
android/app/build.gradle
:dependencies { implementation project(':react-native-image-picker') }
-
Add the required permissions in
AndroidManifest.xml
(this was done when linking react-native-permissions):<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
-
Add the import and link the package in
MainApplication.java
:import com.imagepicker.ImagePickerPackage; // <-- add this import public class MainApplication extends Application implements ReactApplication { @Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), new ImagePickerPackage() // <-- add this ); } }
-
In the XCode's "Project navigator", right click on your project's Libraries folder =>
Add Files to <...>
-
Go to
node_modules
=>react-native-image-picker
=>ios
=> selectRNImagePicker.xcodeproj
-
Add
RNImagePicker.a
toBuild Phases -> Link Binary With Libraries
react-native link react-native-image-resizer
-
In the XCode's "Project navigator", right click on your project's Libraries folder =>
Add Files to <...>
-
Go to
node_modules
=>react-native-image-resizer
=>ios
=> selectRNImageResizer.xcodeproj
-
Add
RNImageResizer.a
toBuild Phases -> Link Binary With Libraries
- Clone the source files:
git clone https://github.com/shaunsaker/react-native-boilerplate.git src
- Delete and move files. FIXME: script
sudo rm ./App.js && sudo rm ./src/.gitignore && sudo rm ./src/package.json && sudo rm ./src/README.md && sudo rm ./src/snippets.json && sudo rm -R ./src/.git && sudo rm ./src/CHANGELOG.md && sudo mv ./src/docs/CHANGELOG.md ./CHANGELOG.md && sudo rm -r ./src/docs && sudo rm ./src/CODE_OF_CONDUCT.md && sudo rm ./src/CONTRIBUTING.md && sudo rm ./src/LICENCE && sudo rm ./src/PULL_REQUEST_TEMPLATE.md && sudo mv ./src/envscript.sh ./envscript.sh && sudo rm ./src/.babelrc && sudo rm ./src/.travis.yml && sudo rm ./src/yarn.lock && sudo mv ./src/.eslintrc.json ./.eslintrc.json && sudo mv ./src/.prettierrc ./.prettierrc && mv ./src/__mocks__/ ./
- In
./index.js
, change:
import App from './App';
to
import App from './src/App';
- Finish react-native-google-signin setup by adding google web client id and ios client id (which can be found in your google-services.json - look for the "client_id" associated with "client_type": 3) to
./src/config/googleSignIn.js
.
- Install dependencies:
yarn add --dev eslint babel-eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-native eslint-plugin-detox
- Copy
./src/assets/fonts/AppIcons.ttf
to
./android/app/src/assets/fonts
(you'll need to create the assets/fonts/ directory)./ios/PROJECT_NAME/
Remove what you don't need.
- Anonymous
- Facebook (add Facebook App ID and App secret and add OAuth redirect URI to Facebook app as per Firebase docs)
- Google (download and replace new google-services.json and GoogleService-Info.plist)
Optional.
Copy the fonts to ./android/app/src/main/assets/fonts
.
Follow this guide.
(ONCE-OFF).
npm i -g @storybook/cli
getstorybook
Saves sooo much time! We normally just set it up to automate beta distribution and manually release to production but fastlane has tools to do that too.
sudo gem install fastlane -NV
- Get your json secret_key file
Follow the steps here.
-
Save the downloaded file to
./android/app/secret_key.json
-
Initialise fastlane
cd android
fastlane init
-
Follow the steps and inpu the relevant information:
- Input
./app/secret_key.json
- Input
n
- Input
-
Add the following to
./android/fastlane/Fastfile
:
desc "Deploy a new version to the Alpha track"
lane :alpha do
gradle(task: "clean assembleRelease")
upload_to_play_store(track: 'alpha')
end
- Initialise fastlane
cd ios
fastlane init
-
Follow the steps and input the relevant information:
- Input
2
(to select Automate beta distribution to TestFlight) - Select the correct scheme (it's usually just PROJECT_NAME)
- Input your developer login credentials
- Select the correct APP ID. ...
- Input
- Add android and iOS apps to the
production
project.
NOTE: When adding android app, don't bother adding the debug signing key, you don't need it.
-
Download each of the config files created in Step 8 to
./config/firebase/development
and./config/firebase/production
respectively. -
Copy
development
project config files:
cp ./android/app/google-services.json ./config/firebase/development/google-services.json
cp ./ios/PROJECT_NAME/GoogleService-Info.plist ./config/firebase/development/GoogleService-Info.plist
- Add the following scripts to
./package.json
, scripts object:
"env-development": "ENV=development ./envscript.sh",
"env-production": "ENV=production ./envscript.sh",
"ios-development": "react-native run-ios",
"android-development": "react-native run-android",
"android-staging": "react-native run-android --variant=release",
"android-apk:": "cd android && ./gradlew clean && ./gradlew assembleRelease",
"android-install": "adb install ./android/app/build/outputs/apk/release/app-armeabi-v7a-release.apk",
"code-push-android": "code-push release-react PROJECT_NAME-android android --deploymentName 'Production'",
"code-push-ios": "code-push release-react PROJECT_NAME-ios ios --deploymentName 'Production'",
"code-push:": "yarn run code-push-android && yarn run code-push-ios",
"beta-android": "yarn run env-production && cd android && fastlane alpha",
"beta-ios": "yarn run env-production && cd ios && fastlane beta"
- Set permissions on env script:
chmod +x envscript.sh
Done! Use the scripts to develop or release the beta builds, e.g:
yarn run ios-dev
- Add dev dependency needed for saga unit tests
yarn add --dev redux-saga-testing
-
Setup Detox
- Follow Step 1 of the Getting Started guide.
(ONCE-OFF).
- Add detox as a dependency:
yarn add --dev detox
- Add the following to
./package.json
:
NOTE: Replace
example
with your PROJECT_NAME."detox": { "configurations": { "ios.sim.debug": { "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/example.app", "build": "xcodebuild -project ios/example.xcodeproj -scheme example -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build", "type": "ios.simulator", "name": "iPhone 7" } } }
- Same file as above, add this to the "jest" object:
"testMatch": [ "<rootDir>/src/**/*.test.js" ], "transformIgnorePatterns": [ "node_modules/(?!(jest-)?react-native|react-navigation)" ]
This avoids conflics with Detox *.spec.js files and an issue with react-navigation.
- Same file as above update test script and add detox script:
"test": "jest && yarn run detox", "detox": "detox build && detox test",
- Initialise Detox
detox init -r jest
- Test the setup with:
detox build detox test
This test will fail. We just need to make sure it is building here.
We log errors to Slack. If you'd like this functionality enabled, you'll need to update the Slack config object in ./src/config/slack/index.js
.
- Install the Code-Push cli globally:
(ONCE-OFF).
yarn global add code-push-cli
- Add apps to Code-Push:
code-push login
code-push app add PROJECT_NAME-android android react-native
code-push app add PROJECT_NAME-ios ios react-native
- Link dependency:
yarn add react-native-code-push
react-native link react-native-code-push
NOTE: You will be asked for your deployment keys. Use the relevant production keys from Step 2 above.
-
Remove the pod added to
./ios/PodFile
in the previous step (we're going to link the dependency manually in the next step). -
Link dependency (iOS):
-
In the XCode's "Project navigator", right click on Libraries folder under your project =>
Add Files to <...>
-
Go to
node_modules
=>react-native-code-push
=>ios
=> selectCodePush.xcodeproj
-
Add
libCodePush.a
toBuild Phases -> Link Binary With Libraries
-
Done! Release updates with:
code-push release-react PROJECT_NAME-android android --deploymentName "Production"
code-push release-react PROJECT_NAME-ios ios --deploymentName "Production"
Optional.
Most of it was already set up in the react-native-firebase step.
- In
./android/app/src/main/AndroidManifest.xml
, add to application:
<service android:name="io.invertase.firebase.messaging.RNFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service android:name="io.invertase.firebase.messaging.RNFirebaseInstanceIdService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
</intent-filter>
</service>
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id"/>
- Same file as above, add to activity:
android:launchMode="singleTop"
- In
./android/app/src/main/res/values/strings.xml
, add to resources:
<string name="default_notification_channel_id">Notifications</string>
TBC
.
- Setup certificates
Follow this guide.
- Enable capabilities
In XCode, enable the following capabilities:
- Push Notifications
- Background modes => Remote notifications
- Upload APNs Authentication Key to Firebase console (Project Settings => Cloud Messaging)