react-native-device-info library provides various device and application information, including the app version.
Install

1
npm install --save react-native-device-info

Sample usage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from "react";
import { View, Text } from "react-native";
import DeviceInfo from "react-native-device-info";

const AppInfo = () => (
<View style={{ padding: 20 }}>
<Text style={{ fontSize: 8 }}>
Version: {DeviceInfo.getVersion()} Build Number:{" "}
{DeviceInfo.getBuildNumber()}
</Text>
</View>
);

export default AppInfo;

Purpose

As of May 2024, the latest version of React Native (7.4) no longer includes instructions for starting a project without Expo at the top level of the Table of Contents. Additionally, setting up a new project with CI/CD can often be a tedious process. This article aims to provide a guide for starting a new project with React Native 7.4 without Expo and setting up CI/CD.
The following descriptions are highly opinionated, so please refer to them with caution. They are intended to provide some ideas if you are just starting to learn React Native.

Initializing the Project

To start a new React Native project without using Expo, you can use the React Native CLI. Open your terminal and run the following command:

1
npx @react-native-community/cli@latest init sampleproject

During the setup, you will be prompted to install CocoaPods if you are planning to run your project directly in Xcode. Select yes when asked:

1
✔ Do you want to install CocoaPods now? Only needed if you run your project in Xcode directly … yes

Running the iOS Project

Navigate to your project’s directory and install the necessary dependencies:

1
2
3
4
5
cd sampleproject
cd ios
bundle install
bundle exec pod install
npm run ios -- --simulator="iPhone 15 Pro"

Running the Android Project

Switch to the Android directory and run your project:

1
2
cd android
npm run android

Scaffold the Project

Installing Navigation Libraries

For navigation within your React Native app, install the following libraries:

1
2
3
4
5
6
npm install @react-navigation/native @react-navigation/stack
npm install react-native-screens react-native-safe-area-context
npm install react-native-gesture-handler
cd ios
bundle install
bundle exec pod install

Setting Up Navigation in the App

Create your main app component with navigation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from "react";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import HomeScreen from "./HomeScreen.tsx";
import { SafeAreaProvider } from "react-native-safe-area-context";

const Stack = createStackNavigator();

function App() {
return (
<SafeAreaProvider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
</NavigationContainer>
</SafeAreaProvider>
);
}

export default App;

Creating the Home Screen

Create a simple Home Screen component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import React from "react";
import { View, StyleSheet, Text, ScrollView } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";

function HomeScreen({}: { navigation: any }) {
return (
<SafeAreaView style={styles.safeArea}>
<ScrollView contentContainerStyle={styles.container}>
<View style={styles.formContainer}>
<Text>Hello</Text>
</View>
</ScrollView>
</SafeAreaView>
);
}

const styles = StyleSheet.create({
safeArea: {
flex: 1,
},
container: {
flexGrow: 1,
alignItems: "center",
padding: 20,
backgroundColor: "#f4f4f4",
},
description: {
textAlign: "center",
fontSize: 18,
color: "#555",
marginVertical: 20,
},
formContainer: {
width: "80%",
padding: 20,
backgroundColor: "white",
borderRadius: 8,
boxShadow: "0 4px 8px rgba(0,0,0,0.1)",
alignItems: "center",
marginBottom: 20,
},
});

export default HomeScreen;

Configuring iOS Project

Set up a new bundle ID and provisioning profile in your Apple Developer account.
Add the following entries to your Info.plist and change CFBundleDisplayName to your desired app name:

1
2
3
4
<key>UIRequiresFullScreen</key>
<true/>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>

Update PRODUCT_BUNDLE_IDENTIFIER in project.pbxproj to the bundle id and switch to manual signing in Xcode for the release build.

Open xcode and add icon in Images/AppIcon (Choose single file for iOS).

Configuring Android Project

Change applicationId in build.gradle to bundle id.
Add the following in signingConfigs:

1
2
3
4
5
6
7
8
release {
if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) {
storeFile file(MYAPP_UPLOAD_STORE_FILE)
storePassword MYAPP_UPLOAD_STORE_PASSWORD
keyAlias MYAPP_UPLOAD_KEY_ALIAS
keyPassword MYAPP_UPLOAD_KEY_PASSWORD
}
}

Update the buildTypes release section to use the release signing config:

1
signingConfig signingConfigs.release

Update app_name in android/app/src/main/res/values/strings.xml.

Add keystore information to android/gradle.properties and copy my-upload-key.keystore to android/app.

Supporting CI/CD

Copy remote-build (see Ref 1) and Support/ExportOptions.plist to ios and update ExportOptions.plist with your new bundle ID and provisioning profile name:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>destination</key>
<string>export</string>
<key>manageAppVersionAndBuildNumber</key>
<true/>
<key>method</key>
<string>app-store-connect</string>
<key>signingStyle</key>
<string>manual</string>
<key>provisioningProfiles</key>
<dict>
<key>!!! your bundle id</key>
<string>!!! your profile name</string>
</dict>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string>!!! your team id</string>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>

Set up new GitHub Action files for your project to automate the build and deployment process.

References

  1. https://docs.github.com/en/actions/deployment/deploying-xcode-applications/installing-an-apple-certificate-on-macos-runners-for-xcode-development
  2. https://gist.github.com/huynguyencong/004e98e4d9e7671f93fec280ddb7fc18

Recently I successfully published my first iOS App to App Store. I used Github Actions to automate the build and deployment process. I was using the free Github Actions minutes, but I found it is not enough for my needs. I managed to use those free credits comes wit my subscription to debug the pipelines and finally published the first version of my app.

Github teams subscritpion comes with 3000 free minutes per month, and the ratio for macOS runner is 10:1, which means I can use 300 minutes per month for macOS runner. it takes about 15 minutes to build and deploy my app on Github-hosted runners, so I can only run the pipeline 20 times per month. Additional macOS minutes are charged at $0.08 per minute, leading to a cost of $1.2 per build and deploy.

However, I can easily get a basic model of Mac Mini M2 with 8GB RAM and 256GB SSD for $400 in the market. This model is always on sale, and my assumption is that the 256GB model provides only half-speed SSD, and consumers are better off buying the 512GB model. But for my use case, I only need to run the pipeline, and the storage is not a concern. With this one-time investment, I can run the pipeline as many times as I want.

After some digging, I found out that Tart, a virtualization toolset to build, run and manage macOS and Linux virtual machines on Apple Silicon, is a good choice for me. I can run the self-hosted runner inside the virtual machine and the pipelines will not jerpordize the host system. As you know, the iOS build and deployment process can mess up with the keychains, provisioning profiles, and certificates, so it is better to run the pipeline in a clean environment.

tart provides a macOS image that is pre-configured with xcode and other tools, even the github runner is pre-installed. basically I can just clone a image and start the vm, then register the runner with my github repo.

Here are some scripts for your reference.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# install tart
brew install cirruslabs/cli/tart

# clone the image an create a vm named sonoma-xcode
tart clone ghcr.io/cirruslabs/macos-sonoma-xcode:latest sonama-xcode

# set the vm resource
tart set sonoma-xcode --memory 4G

# start the vm
tart run --no-graphics sonoma-xcode

# login to the vm, the default password is admin
ssh admin@$(tart ip sonama-xcode)

# register the runner
cd actions-runner

./config.sh --url

# install the runner as service and run it
./svc.sh install
./svc.sh start

Then you are good to go. You should see the runner is online in your github repo. You can now run the pipeline on your self-hosted runner.

This is a hello-world post.

0%