- React.js + React native --> Real Native Mobile Apps (iOS/ Android)
- React itself is platform agnostic.
- react-dom adds web support.
- react-native adds react components, handle compilation, expose native platform APIs to JavaScript
- Expo Docs
- RN components(Views, TextInput) are compiled to real native app
- JS logic is not compiled
- JS logic is running in a JS thread, hosted by RN in the app
- No CSS, can use inline or StyleSheet objects (Syntax is based on CSS and only a subset is supported)
- Expo CLI or React Native CLI can be used to create a new app
- Expo offers a managed app development workflow making it very convenient and easier
- RN CLI bare bone development
- Can eject out of expo anytime
- Setting up the development environment
- Install node
sudo npm install -g expo-cli
expo --version
- Deprecated
expo init ProjectName
select blank workflow - New
npx create-expo-app ProjectName
https://blog.expo.dev/the-new-expo-cli-f4250d8e3421 npm start
orexpo start
- app.json has app configuring
For Xcode go to preferences > locations > select command line tools /Applications/Xcode.app/Contents/Developer/Applications to access simulator
- Core
- We can communicate with the parent component by passing event handler functions via props
- We can change app configurations by
app.json
file
- cmd + d for developer menu
- Install standalone react dev tools
npm install -g react-devtools
react-devtools
- Style
- Color Reference
- StyleSheet.create makes it easier with auto completion and performant
- Uses camel case Eg: backgroundColor rather than background-color
- Can pass an array of styles (last style in the array has precedence)
- Styles do not cascade down to child components
- There're some properties which will not work in Android or iOS.
- Example: borderRadius in Text component will not work in iOS. So we need to wrap it with a View component to get the same styling
- Nested text elements will be affected by text related styles from parent
elevation: 4, //to add shadow only works on android
// for ios can use below properties
shadowColor: "#000",
shadowOffset: { width: 1, height: 5 },
shadowRadius: 10,
shadowOpacity: 0.5,
backgroundColor: 'white',
backgroundColor is required for visibility
-
Uses Flexbox to create layouts
-
Flexbox uses two axis main and cross according flexDirection
-
By default flexDirection is column (top to bottom)
-
justifyContent: main axis, alignItems: cross axis
-
column > main axis: top to bottom, cross axis: left to right
-
row > main axis: left to right, cross axis: top to bottom
- Different props are available in the components which allows to interact and handle events.
- For example
<TextInput onChangeText={goalInputHandler} />
onChangeText expects a function. - Note that goalInputHandler is passed and not goalInputHandler(). If we add () it will execute the function at the time the UI is rendered.
- So we only add a pointer to the function without the ().
- When using a setState function we can pass a function which will return the existing state value.
- Rather than directly changing state. currentGoals in this example
setGoalsList(currentGoals => [...currentGoals, enteredGoalText]);
- Great for limited content like articles
- But for lists this shouldn't be used as it can have performance impact due to all items in list being refreshed if the UI is updated.
- Items will be lazy loaded so better performance for long lists
- By default it will look for key value in the data item
- If data item doesn't have a key value we can use keyExtractor prop
- If it's a network image we add the source equal to an object with the uri.
<Image source={{uri: selectedMeal.imageUrl}}/>
- For network image width and height should be set
-
expo install expo-font
-
Can also use google fonts as well using a package
-
Use in the App load
useFonts({ 'open-sans': require('./assets/fonts/OpenSans-Regular.ttf'), 'open-sans-bold': require('./assets/fonts/OpenSans-Bold.ttf'), })
-
Add in the style with the identifier given in the App component
fontFamily: 'open-sans-bold',
-
useFonts
hook returns an array and it's first element returns if the fonts are loaded
- Import from react-native
- Js Object not a component and this can be used anywhere
Dimensions.get('window')
screen and window is same in iOS- In Android screen is whole screen with status bar window is without status bar
const deviceWidth = Dimensions.get("window").width;
padding: deviceWidth < 380 ? 12 : 24,
value can be used with ternary operator or directly- Using percentage values is not helpful as width and height can be different when we want the property values to be same
-
app.json >
"orientation": "portrait",
can bedefault
= both orlandscape
-
Can use
const deviceHeight = Dimensions.get("window").height
to determine orientation and add styling -
marginTop: deviceHeight < 380 ? 30 : 100,
-
Check landscape mode if width > height
-
If orientation is not locked(when user changes orientation while using the app) above solution will not work because only once the code will executed when the file code is parsed and executed(when code is outside the component function).
-
As a solution for the above issue we can use the
useWindowDimensions
hook provided by react native inside the component function -
const { width, height } = useWindowDimensions();
this will be dynamic because the hook will keep watching for changes -
Use in JSX code
const marginTopDistance = height < 380 ? 30 : 100;
return (
<View style={[styles.rootContainer, { marginTop: marginTopDistance }]}>
-
We can use
useWindowDimensions
hook to have different UI layouts according to the orientationif (width > 500) {
content = (
- Can be used to avoid breaking the app when keyboard is viewed
- App view can be wrapped using KeyboardAvoidingView component
- KeyboardAvoidingView has 3 behaviors 'padding', 'height' and 'position'
- Position is best in most use cases as it shift the view.
- But when using position the KeyboardAvoidingView should be wrapped with a ScrollView
<ScrollView style={styles.screen}>
<KeyboardAvoidingView style={styles.screen} behavior='position'>
<View style={[styles.rootContainer, { marginTop: marginTopDistance }]}>
- For platform specific changes we can use Platform from React Native
borderWidth: Platform.OS === 'ios' ? 5 : 1,
orborderWidth: Platform.select({ ios: 5, android: 1 }),
more readable- Can have different files for platform specific changes by adding files with platform extension to filename
- Eg:
Title.android.js
Title.ios.js
Imports will only need Titleimport Title from "../components/ui/Title";
- Can use same method to have different constant/colors files
import { StatusBar } from "expo-status-bar";
in App.js- To style the status bar
<StatusBar style="light" />
dark, auto, inverted
Navigation React Navigation
-
sudo npm install @react-navigation/native
-
npx expo install react-native-screens react-native-safe-area-context
-
sudo npx expo install @react-navigation/native-stack
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name='MealCategories' component={CategoriesScreen} />
<Stack.Screen name='MealsOverview' component={MealsOverviewScreen} />
</Stack.Navigator>
</NavigationContainer>
-
First child (top most screen) inside
<Stack.Navigator>
will be used as the initial screen -
<Stack.Navigator initialRouteName="CategoriesScreen">
initialRouteName can also be used to set the initial screen
- Using
navigation
prop can be used to forward navigation in child components or - Can use
import { useNavigation } from "@react-navigation/native"
hook as an alternative to passing navigation prop const navigation = useNavigation();
can be executed in any component function(can be a registered screen or not)
- route prop is available for any screen component that is registered in the navigator
- It contains an object with params passed to that screen
useRoute
hook contains the same behavior and can be used as an alternative instead of the prop- Can be used in nested component which is not registered as a screen
- Also contains name(name of the screen), key(automatically added unique key of the screen), path(string that opened the screen via deep link)
@react-navigation/stack
is extremely customizable, it's implemented in JavaScript. While it runs animations and gestures using natively, the performance may not be as fast as a@react-navigation/native-stack
implementation. native-stack offers native performance and exposes native features such as large title on iOS etc.
- Similar to Stack navigator configuration
const Drawer = createDrawerNavigator()
<NavigationContainer>
><Drawer.Navigator>
><Drawer.Screen>
drawerIcon: ({color,size}) = > (<Ionicons name="home color={color} size={size} />)
Add add icon- Can use navigation prop
navigation.toggleDrawer()
- Can fully customize using own components
const BottomTab = createBottomTabNavigator()
<NavigationContainer>
><BottomTab.Navigator>
><BottomTab.Screen>
- Can render a fully customized tab bar
-
We can use
<Stack.Screen options={{}}
prop to customize specific screens stack-navigator options -
If we need to apply same customizations to all screens add them to the navigator
<Stack.Navigator screenOptions={{}}
-
To dynamically set options we can pass an arrow function and get route and navigation properties by react navigation, and it should return an options object
<Stack.Screen
name='MealsOverview'
component={MealsOverviewScreen}
options={({route, navigation}) => {
const title = route.params.title
return {
title: title
}
}}
/>
-
As an alternative
navigation.setOptions({ title: categoryTitle });
can be used. It should be used in useEffect/useLayoutEffect -
useLayoutEffect is used to run the effect simultaneously with the component function
- Header button can be added through the
Stack.Screen options
or from the screennavigation.setOptions
<Stack.Screen
name='MealDetail'
component={MealDetailScreen}
options={{
title: "Meal Detail",
headerRight: () => {
return <Button title='Save' />;
},
}}
/>
- or
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => {
return <Button title='Save' onPress={headerButtonPressHandler} />;
},
});
}, [navigation, headerButtonPressHandler]);
- When nesting navigators each navigator brings in a header, can be hidden
- Check commit
-
Used to open urls(tel, mailto)
-
Need to add
app.json expo.ios
to enable linking"infoPlist": { "LSApplicationQueriesSchemes": [ "tel", "mailto" ] }
- Camera
- ImagePicker
sudo npx expo install expo-image-picker
- Add permissions to
app.json
- For iOS need to use
useCameraPermissions
hook andrequestPermission
- Image picker component
npx expo install expo-location
Location- Similar to Camera permissions
- Maps Static API
- Reverse Geocoding API
- Location picker component
npx expo install react-native-maps
Map view- react-native-maps
- Map Scree
npx expo install expo-sqlite
SQLite.openDatabase('dbName)
will open if existing DB or create a new DB- Commit
expo install expo-linear-gradient
expo install expo-app-loading
sudo npx expo install react-native@0.71.4
sudo expo update 48
npm install react-native-reanimated@1 --save --save-exact
npm install axios
sudo expo publish
- With Expo(Managed or bare workflow), after configuring can build the App binaries with expo cloud service for all platforms and then submit to stores.
- Without Expo, configure project, build App binaries locally and then submit to stores.
- Set Permissions (info.plist file and androidmanifest.xml to set permissions)
- App Name & identifier (app version and unique app identifier(ID))
- Environment variable (API keys)
- Icon & splash screen Figma file
app.json
is the key configuration file app.json / app.config.js
version
user facing versionios.buildNumber
&android.versionCode
, these are internal version numbers used by the app stores- Set
ios.buildNumber
Specified format - Set
android.versionCode
Should be a positive integer Specification
- Only need to replace the files in assets folder according to the dimensions and expo will handle different resolutions when building.
npm install -g eas-cli
eas login
eas whoami
eas build:configure
- set
build.preview.android.buildType: "apk"
Used for testing purpose to get an installable apk eas build -p android --profile preview
generate build- Set App identifier (Unique reverse url)
android.package
- Set keystore automatically or manually (required for signing the app and securing it so others cannot publish the app)
- After build is done go to expo dev console and download the apk
- Drag and drop the apk to the emulator or install to phone using QR or link
- set
build.preview.ios.simulator: "true"
Used for testing purpose to get an installable app eas build -p ios --profile preview
generate build- Set iOS bundle identifier (Unique reverse url)
android.package
- Download zip file from given url, then drag and drop the .app file
- For android run the production profile
eas build --platform android
- For iOS need to get the membership and create certificates
eas build --platform ios
- Can give expo access to create the certificates automatically or can create them manually
- Steps
- Need to create Distribution Certificate and Provisioning Profile
- Login to apple developer > Go to certificates, Identifiers, Profiles > Create New Certificate > Select iOS distribution > Create Certificate Signing request file > Upload file > Continue
- Go to certificates, Identifiers, Profiles > Register an App ID > Get bundle ID added in
eas.json ios.bundleIdentifier
> Add description > Continue > Register - App Store connect > My apps > Add new app with name > Select the Bundle ID create above > Give full access > Add internal identifier > Create
- Go to certificates, Identifiers, Profiles > Create New Profile > App Store provisioning profile > Select Above app > Continue > Select Distribution certificate > Give name to profile > Generate > Download
- Create a new
certs/ios
folder at root level(not in source code) and add the files. Add certs folder to gitignore - From finder execute the cert > Add to keychain > Search cert that was added > My Certificates > Export > Export as P12 file > Add to cert folder > Set password
- Create new file
credentials.json
and add the code snippet. Set the profile path, p12 file path and password set above eas build --platform ios
- To force EAS to use local certificates set
eas.json production.credentialSource: "local"
- After build is completed can submit the app manually or with EAS submit
- Hosted service to upload and submit apps to the store automatically.
Use expo-router
to build native navigation using files in the app/
directory.
npx create-react-native-app -t with-router
npx create-expo-app@latest -e with-router
npm install expo-font axios react-native-dotenv
npx expo start --ios