From f52f40cfcfcf3d6b832a88a5f18b1cf78ff2ebee Mon Sep 17 00:00:00 2001 From: Mazen Chami Date: Fri, 28 Apr 2023 16:38:01 -0400 Subject: [PATCH] (feat) OTA with `expo-updates` (#183) --- README.md | 9 +- app.config.ts | 3 + app/app.tsx | 62 +- app/components/Modal.tsx | 79 + app/i18n/en.ts | 8 + app/screens/DebugScreen.tsx | 51 +- package.json | 46 +- yarn.lock | 4582 +++++++++++++++++------------------ 8 files changed, 2404 insertions(+), 2436 deletions(-) create mode 100644 app/components/Modal.tsx diff --git a/README.md b/README.md index dc00a261..6115a2bf 100644 --- a/README.md +++ b/README.md @@ -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
+ a. For a `preview` build, this can just be done locally (with uncommitted changes) or from another git branch
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_ @@ -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 ``` diff --git a/app.config.ts b/app.config.ts index e3cef897..addaeb9a 100644 --- a/app.config.ts +++ b/app.config.ts @@ -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", @@ -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" }], + ["expo-localization"], ], }) diff --git a/app/app.tsx b/app/app.tsx index 7f76d7e4..c753b4b3 100644 --- a/app/app.tsx +++ b/app/app.tsx @@ -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" @@ -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 @@ -87,6 +91,61 @@ const CustomToast = () => { return } +// Setting up our OTA Updates component +const OTAUpdates = () => { + const [isModalVisible, setIsModalVisible] = useState(false) + const [isUpdating, setIsUpdating] = useState(false) + + async function fetchAndRestartApp() { + const fetchUpdate = await Updates.fetchUpdateAsync() + if (fetchUpdate.isNew) { + await Updates.reloadAsync() + } else { + setIsModalVisible(false) + setIsUpdating(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 ( + { + setIsUpdating(true) + await fetchAndRestartApp() + }, + label: isUpdating ? "ota.confirmLabelUpdating" : "ota.confirmLabel", + disabled: isUpdating, + }} + cancelOnPress={{ + cta: () => { + setIsModalVisible(false) + }, + label: "ota.cancelLabel", + }} + isVisible={isModalVisible} + /> + ) +} + /** * This is the root component of our app. */ @@ -129,6 +188,7 @@ function App(props: AppProps) { onStateChange={onNavigationStateChange} /> + diff --git a/app/components/Modal.tsx b/app/components/Modal.tsx new file mode 100644 index 00000000..b8793804 --- /dev/null +++ b/app/components/Modal.tsx @@ -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, ButtonProps } from "./Button" +import { Text } from "./Text" + +interface OnPressProps extends ButtonProps { + 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) => ( + + + + + {subtitle ? : null} +