Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) OTA with expo-updates #183

Merged
merged 21 commits into from
Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,12 @@ _Flows currently run against an **iOS 15.5** simulator dev client_
## Notes

- Only make JavaScript changes OTA
- Any native code changes, app.config.ts or Expo SDK update will require a store publish
- Any native code changes, `app.config.ts` or Expo SDK update will require a store publish

## Steps

1. Make the necessary JavaScript changes
a. For a `preview` build, this can just be done locally (with uncommitted changes) or from another git branch
1. Make the necessary JavaScript changes<br/>
a. For a `preview` build, this can just be done locally (with uncommitted changes) or from another git branch<br/>
b. For a `production`, you'll likely be in some release candidate branch or `main` branch after things have been QA'd
2. Create the EAS update branch `eas update --branch [update branch name] --message "info about the update"`
- _This packages up the current project code and uploads it as a package to EAS as an update_
Expand All @@ -134,8 +134,7 @@ There is a `preview` build of version 1.0 out in the wild, lacking a new feature
git checkout -b feat/my-new-feature
# make some changes to code
git commit -a -m "feature complete"
eas update --branch preview-1.1 --message "added new feature"
eas channel:update preview --branch preview-1.1
eas update --branch preview --message "added new feature"
# from a device with the preview build, open the app to grab the update, and reload it to use the new feature
```

Expand Down
3 changes: 3 additions & 0 deletions app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
backgroundColor: "#081828",
},
updates: {
checkAutomatically: "ON_ERROR_RECOVERY",
enabled: true,
fallbackToCacheTimeout: 0,
url: "https://u.expo.dev/b72c79d7-7c87-4aa7-b964-998dcff69e07",
Expand Down Expand Up @@ -90,5 +91,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
"@react-native-firebase/app",
"@react-native-firebase/crashlytics",
["expo-build-properties", { ios: { useFrameworks: "static" } }],
["expo-updates", { username: "infinitered" }],
mazenchami marked this conversation as resolved.
Show resolved Hide resolved
["expo-localization"],
],
})
59 changes: 58 additions & 1 deletion app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
import "./i18n"
import "./utils/ignoreWarnings"
import { useFonts } from "expo-font"
import React, { useLayoutEffect } from "react"
import React, { useLayoutEffect, useState } from "react"
import {
initialWindowMetrics,
SafeAreaProvider,
useSafeAreaInsets,
} from "react-native-safe-area-context"
import * as Updates from "expo-updates"
import { AppNavigator, useNavigationPersistence } from "./navigators"
import { ErrorBoundary } from "./screens/ErrorScreen/ErrorBoundary"
import * as storage from "./utils/storage"
Expand All @@ -30,6 +31,9 @@ import Toast, { BaseToast, ToastConfig } from "react-native-toast-message"
import { $baseSecondaryStyle, $baseStyle } from "./components"
import { Dimensions, ViewStyle } from "react-native"
import { queryClient } from "./services/api/react-query"
import { reportCrash } from "./utils/crashReporting"
import { useAppState } from "./hooks"
import { Modal } from "./components/Modal"

// Set up Reactotron, which is a free desktop app for inspecting and debugging
// React Native apps. Learn more here: https://github.com/infinitered/reactotron
Expand Down Expand Up @@ -87,6 +91,58 @@ const CustomToast = () => {
return <Toast config={toastConfig} topOffset={insets.top} />
}

// Setting up our OTA Updates component
const OTAUpdates = () => {
mazenchami marked this conversation as resolved.
Show resolved Hide resolved
const [isModalVisible, setIsModalVisible] = useState(false)

async function fetchAndRestartApp() {
const fetchUpdate = await Updates.fetchUpdateAsync()
if (fetchUpdate.isNew) {
await Updates.reloadAsync()
} else {
setIsModalVisible(false)
reportCrash("Fetch Update failed")
}
}

async function onFetchUpdateAsync() {
if (__DEV__ || process.env.NODE_ENV === "development") return
try {
const update = await Updates.checkForUpdateAsync()
setIsModalVisible(update.isAvailable)
} catch (error) {
reportCrash(error)
}
}

useAppState({
match: /background/,
nextAppState: "active",
callback: onFetchUpdateAsync,
})

return (
<Modal
title="ota.title"
subtitle="ota.subtitle"
confirmOnPress={{
cta: () => {
fetchAndRestartApp()
mazenchami marked this conversation as resolved.
Show resolved Hide resolved
setIsModalVisible(false)
},
label: "ota.confirmLabel",
}}
cancelOnPress={{
cta: () => {
setIsModalVisible(false)
},
label: "ota.cancelLabel",
}}
isVisible={isModalVisible}
/>
)
}

/**
* This is the root component of our app.
*/
Expand Down Expand Up @@ -129,6 +185,7 @@ function App(props: AppProps) {
onStateChange={onNavigationStateChange}
/>
<CustomToast />
<OTAUpdates />
</QueryClientProvider>
</ErrorBoundary>
</SafeAreaProvider>
Expand Down
79 changes: 79 additions & 0 deletions app/components/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React from "react"
import { Modal as RNModal, TextStyle, View, ViewStyle } from "react-native"
import { TxKeyPath } from "../i18n"
import { colors, spacing } from "../theme"
import { Button } from "./Button"
import { Text } from "./Text"

interface OnPressProps {
cta: () => void
label: TxKeyPath
}

interface ModalProps {
title: TxKeyPath
subtitle?: TxKeyPath
confirmOnPress: OnPressProps
cancelOnPress: OnPressProps
isVisible?: boolean
}

export const Modal = ({
title,
subtitle,
confirmOnPress,
cancelOnPress,
isVisible,
}: ModalProps) => (
<RNModal animationType="slide" transparent={true} visible={isVisible}>
<View style={$wrapper}>
<View style={$card}>
<Text preset="subheading" tx={title} style={$textColor} />
{subtitle ? <Text text="body" style={$subtitle} tx={subtitle} /> : null}
<Button
shadowStyle={$confirmButton}
onPress={confirmOnPress.cta}
tx={confirmOnPress.label}
/>
<Button
preset="link"
style={$cancelButton}
onPress={cancelOnPress.cta}
tx={cancelOnPress.label}
/>
</View>
</View>
</RNModal>
)

const $wrapper: ViewStyle = {
flex: 1,
alignItems: "center",
justifyContent: "center",
}

const $card: ViewStyle = {
backgroundColor: colors.palette.neutral100,
borderRadius: spacing.medium,
paddingHorizontal: spacing.large,
paddingVertical: spacing.extraLarge,
width: "80%",
}

const $textColor: TextStyle = {
color: colors.palette.neutral800,
}

const $subtitle: TextStyle = {
...$textColor,
marginTop: spacing.medium,
}

const $confirmButton: ViewStyle = {
marginTop: spacing.large,
}

const $cancelButton: ViewStyle = {
alignSelf: "center",
marginTop: spacing.extraLarge,
}
7 changes: 7 additions & 0 deletions app/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,13 @@ const en = {
locationLabel: "Location",
moreInfo: "More info",
},
ota: {
title: "Update Available",
subtitle:
"Good things come to those who update! Check out the newest version of our app! Blame Jamon for any bugs!",
cancelLabel: "Maybe Later",
confirmLabel: "Update",
},
}

export default en
Expand Down
51 changes: 41 additions & 10 deletions app/screens/DebugScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { FC } from "react"
import { TextStyle, ViewStyle } from "react-native"
import { TextStyle, View, ViewStyle } from "react-native"
import { StackScreenProps } from "@react-navigation/stack"
import { AppStackParamList } from "../navigators/AppNavigator"
import { resetRoot } from "../navigators/navigationUtilities"
Expand All @@ -12,6 +12,8 @@ import { translate } from "../i18n"
import { clear } from "../utils/storage"
import { useQueryClient } from "@tanstack/react-query"
import { BackButton } from "../navigators/BackButton"
import * as Updates from "expo-updates"
import * as Application from "expo-application"

export const DebugScreen: FC<StackScreenProps<AppStackParamList, "Debug">> = () => {
const navigation = useAppNavigation()
Expand Down Expand Up @@ -44,18 +46,34 @@ export const DebugScreen: FC<StackScreenProps<AppStackParamList, "Debug">> = ()

return (
<Screen
contentContainerStyle={$rootContainer}
style={$root}
preset="scroll"
ScrollViewProps={{ showsVerticalScrollIndicator: false }}
preset="fixed"
safeAreaEdges={["bottom"]}
>
<Text preset="bold" tx="debugScreen.pushToken" style={$subtitle} />
<Text text={fcmToken} selectable />
<Button
shadowStyle={$resetStateButtonShadow}
tx="debugScreen.resetState"
onPress={() => clearState()}
/>
<View>
<Text preset="bold" tx="debugScreen.pushToken" style={$subtitle} />
<Text text={fcmToken} selectable />
<Button
shadowStyle={$resetStateButtonShadow}
tx="debugScreen.resetState"
onPress={() => clearState()}
/>
</View>
<View style={$footer}>
<Text preset="companionHeading" text={"Specs:"} />
{Updates.channel.length > 0 ? (
<Text preset="label" text={`Channel: ${Updates.channel}`} style={$spec} />
) : null}
{Updates.updateId ? (
<Text preset="label" text={`Update ID: ${Updates.updateId}`} style={$spec} />
) : null}
<Text
preset="label"
text={`App Version: ${Application.nativeApplicationVersion}`}
style={$spec}
/>
</View>
</Screen>
)
}
Expand All @@ -65,10 +83,23 @@ const $root: ViewStyle = {
paddingHorizontal: spacing.large,
}

const $rootContainer: ViewStyle = {
flex: 1,
justifyContent: "space-between",
}

const $resetStateButtonShadow: ViewStyle = {
marginTop: spacing.large,
}

const $subtitle: TextStyle = {
marginBottom: spacing.medium,
}

const $footer: ViewStyle = {
marginBottom: spacing.medium,
}

const $spec: TextStyle = {
marginTop: spacing.extraSmall,
}
46 changes: 21 additions & 25 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"postinstall": "node ./bin/postInstall"
},
"dependencies": {
"@expo/webpack-config": "^0.17.2",
"@expo/webpack-config": "^18.0.1",
"@react-native-async-storage/async-storage": "~1.17.3",
"@react-native-firebase/app": "^16.5.0",
"@react-native-firebase/crashlytics": "^16.5.0",
Expand All @@ -42,32 +42,32 @@
"@react-navigation/native": "~6.0.1",
"@react-navigation/native-stack": "^6.0.2",
"@react-navigation/stack": "~6.2.1",
"@shopify/flash-list": "1.3.1",
"@shopify/flash-list": "1.4.0",
"@tanstack/react-query": "^4.19.1",
"axios": "^1.2.1",
"date-fns": "^2.29.2",
"expo": "^47.0.13",
"expo-application": "~5.0.1",
"expo-build-properties": "~0.4.1",
"expo-constants": "~14.0.2",
"expo-dev-client": "~2.0.1",
"expo-device": "~5.0.0",
"expo-file-system": "~15.1.1",
"expo-font": "~11.0.1",
"expo-linear-gradient": "~12.0.1",
"expo-localization": "~14.0.0",
"expo-splash-screen": "~0.17.5",
"expo-status-bar": "~1.4.2",
"expo-updates": "~0.15.6",
"expo": "^48.0.0",
"expo-application": "~5.1.1",
"expo-build-properties": "~0.6.0",
"expo-constants": "~14.2.1",
"expo-dev-client": "~2.2.1",
"expo-device": "~5.2.1",
"expo-file-system": "~15.2.2",
"expo-font": "~11.1.1",
"expo-linear-gradient": "~12.1.2",
"expo-localization": "~14.1.1",
"expo-splash-screen": "~0.18.2",
"expo-status-bar": "~1.4.4",
"expo-updates": "~0.16.4",
"i18n-js": "3.9.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.71.0",
"react-native-gesture-handler": "~2.8.0",
"react-native": "0.71.7",
"react-native-gesture-handler": "~2.9.0",
"react-native-gifted-chat": "^2.0.1",
"react-native-reanimated": "~2.12.0",
"react-native-safe-area-context": "4.4.1",
"react-native-screens": "~3.18.0",
"react-native-reanimated": "~2.14.4",
"react-native-safe-area-context": "4.5.0",
"react-native-screens": "~3.20.0",
"react-native-toast-message": "^2.1.5",
"react-native-web": "~0.18.7",
"reactotron-react-js": "^3.3.7",
Expand All @@ -86,7 +86,6 @@
"@types/i18n-js": "3.8.2",
"@types/jest": "^29.2.1",
"@types/react": "^18.0.27",
"@types/react-native": "^0.71.1",
"@typescript-eslint/eslint-plugin": "5.48.2",
"@typescript-eslint/parser": "5.48.2",
"babel-jest": "26.6.3",
Expand All @@ -101,17 +100,14 @@
"eslint-plugin-promise": "6.0.0",
"eslint-plugin-react": "7.30.0",
"eslint-plugin-react-native": "4.0.0",
"expo-modules-autolinking": "~1.0.0",
"expo-modules-autolinking": "~1.1.0 || ~1.2.0",
"fbjs-scripts": "3.0.1",
"husky": "^8.0.0",
"jest": "^26.6.3",
"jest-circus": "26",
"jest-environment-node": "26",
"jest-expo": "^47.0.0",
"lint-staged": "^13.1.0",
"metro-config": "0.74.0",
"metro-react-native-babel-preset": "0.73.5",
"metro-source-map": "0.71.1",
"mocha": "6",
"patch-package": "6.4.7",
"postinstall-prepare": "1.0.1",
Expand Down
Loading