├── .eslintrc.js ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── ci.yml │ └── npm-publish.yml ├── .gitignore ├── .npmignore ├── README.md ├── app.plugin.js ├── bun.lockb ├── example ├── .gitignore ├── App.tsx ├── SampleWidgetExtension │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Knights.imageset │ │ │ ├── Contents.json │ │ │ ├── Knight.png │ │ │ ├── Knight@2x.png │ │ │ └── Knight@3x.png │ │ ├── Pirates.imageset │ │ │ ├── Contents.json │ │ │ ├── Pirates.png │ │ │ ├── Pirates@2x.png │ │ │ └── Pirates@3x.png │ │ ├── WidgetBackground.colorset │ │ │ └── Contents.json │ │ └── league.imageset │ │ │ ├── Asset.png │ │ │ ├── Asset@2x.png │ │ │ ├── Asset@3x.png │ │ │ └── Contents.json │ ├── Attributes.swift │ ├── Info.plist │ ├── Module.swift │ ├── SampleWidgetExtension.intentdefinition │ ├── SampleWidgetExtension.swift │ ├── SampleWidgetExtensionBundle.swift │ └── SportsScoreLiveActivity.swift ├── app.json ├── assets │ ├── adaptive-icon.png │ ├── favicon.png │ ├── icon.png │ └── splash.png ├── babel.config.js ├── bun.lockb ├── eas.json ├── metro.config.js ├── package.json ├── tsconfig.json └── webpack.config.js ├── expo-module.config.json ├── ios ├── .gitignore └── ReactNativeWidgetExtension.podspec ├── package.json ├── plugin ├── src │ ├── index.ts │ ├── lib │ │ ├── getWidgetExtensionEntitlements.ts │ │ └── getWidgetFiles.ts │ ├── withConfig.ts │ ├── withPodfile.ts │ ├── withWidgetExtensionEntitlements.ts │ ├── withXcode.ts │ └── xcode │ │ ├── addBuildPhases.ts │ │ ├── addPbxGroup.ts │ │ ├── addProductFile.ts │ │ ├── addTargetDependency.ts │ │ ├── addToPbxNativeTargetSection.ts │ │ ├── addToPbxProjectSection.ts │ │ └── addXCConfigurationList.ts └── tsconfig.json ├── src ├── ReactNativeWidgetExtensionModule.ts └── index.ts └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ["universe/native", "universe/web"], 4 | ignorePatterns: ["build"], 5 | }; 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [bndkt] 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | schedule: 5 | interval: "weekly" 6 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | 8 | jobs: 9 | CI: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v3 14 | - name: Setup Bun 15 | uses: oven-sh/setup-bun@v1 16 | - name: Install dependencies 17 | run: bun install 18 | - name: Build react-native-widget-extension 19 | run: bun run build 20 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | publish-npm: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v3 16 | - name: Setup Bun 17 | uses: oven-sh/setup-bun@v1 18 | - name: Install dependencies 19 | run: bun install 20 | - name: Build package 21 | run: bun run build 22 | - uses: actions/setup-node@v3 23 | with: 24 | node-version: "18.x" 25 | registry-url: "https://registry.npmjs.org" 26 | - run: npm publish 27 | env: 28 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # VSCode 6 | .vscode/ 7 | jsconfig.json 8 | 9 | # Xcode 10 | # 11 | build/ 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata 21 | *.xccheckout 22 | *.moved-aside 23 | DerivedData 24 | *.hmap 25 | *.ipa 26 | *.xcuserstate 27 | project.xcworkspace 28 | 29 | # Android/IJ 30 | # 31 | .classpath 32 | .cxx 33 | .gradle 34 | .idea 35 | .project 36 | .settings 37 | local.properties 38 | android.iml 39 | 40 | # Cocoapods 41 | # 42 | example/ios/Pods 43 | 44 | # Ruby 45 | example/vendor/ 46 | 47 | # node.js 48 | # 49 | node_modules/ 50 | npm-debug.log 51 | yarn-debug.log 52 | yarn-error.log 53 | 54 | # BUCK 55 | buck-out/ 56 | \.buckd/ 57 | android/app/libs 58 | android/keystores/debug.keystore 59 | 60 | # Expo 61 | .expo/* 62 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Exclude all top-level hidden directories by convention 2 | /.*/ 3 | 4 | __mocks__ 5 | __tests__ 6 | 7 | /babel.config.js 8 | /android/src/androidTest/ 9 | /android/src/test/ 10 | /android/build/ 11 | /example/ 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-widget-extension 2 | 3 | Expo config plugin to add widgets and live activities to a React Native app 4 | 5 | The widgets still need to be written in Swift (I think there's no way around that). But you can simply add a folder with Swift files to a React Native project and then add the plugin via: 6 | 7 | ```sh 8 | npx expo install react-native-widget-extension 9 | ``` 10 | 11 | And add the following config to app.json (where `widgetsFolder` is the path to the folder with the Swift files): 12 | 13 | ```json 14 | "expo": { 15 | "name": "my-app", 16 | "plugins": [ 17 | [ 18 | "react-native-widget-extension", 19 | { "frequentUpdates": true, "widgetsFolder": "SampleWidgetExtension" }, 20 | ], 21 | ] 22 | } 23 | ``` 24 | 25 | Then in React land, you can use the following: 26 | 27 | ```typescript 28 | import { 29 | areActivitiesEnabled, 30 | startActivity, 31 | updateActivity, 32 | endActivity, 33 | } from "react-native-widget-extension"; 34 | 35 | startActivity(3, "4343", "$32.23", driverName, 47, 43); 36 | ``` 37 | 38 | ## Plugin configuration options 39 | 40 | - `frequentUpdates` (boolean, default: false): Depending on this param, NSSupportsLiveActivitiesFrequentUpdates will be set 41 | - `widgetsFolder` (string, default: "widgets"): Path from the project root to the folder containing the Swift widget files 42 | - `deploymentTarget` (string, default: "16.2"): The minimum deployment target for the app 43 | - `groupIdentifier` (string): The app group identifier which is required for communication with the main app. Must start with `group.` 44 | 48 | 49 | ## Example 50 | 51 | For a minimal example app, see the folder **example**. Example code for a widget and live activity can be found in **example/SampleWidgetExtension**. To run the example, you'll need to `bun run build` in the root directory. 52 | 53 | Some background on how the **PizzaDelivery** example works: 54 | 55 | - Assets.xcassets and Info.plist: Automatically created by Xcode when you create a widget 56 | - Attributes.swift: The ActivityAttributes for the Live Activity are defined here. By default, this file should be named "Attributes.swift". 57 | - Module.swift: This file defined the native module that can then be used from React land to start/stop/update the live activity. By default, this file should be named "Module.swift". 58 | - The rest of the folder can be Swift files that define widgets, views, etc. and can be named and divided between files however you want. 59 | 60 | ## Deployment Target 61 | 62 | By default, this module adds a minimum deployment target of iOS 16.2, because otherwise Swift compilation fails if you try to use Live Activities. If you want to support earliert versions of iOS, you can manually set the deployment target via plugin config: 63 | 64 | ```json 65 | "expo": { 66 | "name": "my-app", 67 | "plugins": [ 68 | [ 69 | "react-native-widget-extension", 70 | { "deploymentTarget": "14.0" }, 71 | ], 72 | ] 73 | } 74 | ``` 75 | 76 | If you do this and you still use Live Activities in Swift, you have to make sure to guard the code that can only run on iOS 16.2 and later like this: 77 | 78 | ```swift 79 | import SwiftUI 80 | import WidgetKit 81 | 82 | @main 83 | struct PizzaDeliveryWidgetBundle: WidgetBundle { 84 | var body: some Widget { 85 | PizzaDeliveryWidgets() 86 | 87 | if #available(iOS 16.2, *) { 88 | PizzaDeliveryLiveActivity() 89 | } 90 | } 91 | } 92 | ``` 93 | 94 | and 95 | 96 | ```swift 97 | @available(iOS 16.2, *) 98 | struct LockScreenLiveActivityView: View { 99 | ... 100 | } 101 | ``` 102 | -------------------------------------------------------------------------------- /app.plugin.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./plugin/build"); 2 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bndkt/react-native-widget-extension/f6a2437f859579ade4ba20499f580e00353a021f/bun.lockb -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | 11 | # Native 12 | *.orig.* 13 | *.jks 14 | *.p8 15 | *.p12 16 | *.key 17 | *.mobileprovision 18 | 19 | # Metro 20 | .metro-health-check* 21 | 22 | # debug 23 | npm-debug.* 24 | yarn-debug.* 25 | yarn-error.* 26 | 27 | # macOS 28 | .DS_Store 29 | *.pem 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | 37 | ios/ 38 | -------------------------------------------------------------------------------- /example/App.tsx: -------------------------------------------------------------------------------- 1 | import { Button, StyleSheet, View } from "react-native"; 2 | import * as ReactNativeWidgetExtension from "react-native-widget-extension"; 3 | 4 | export default function App() { 5 | const quarter = 1; 6 | const scoreLeft = 2; 7 | const scoreRight = 3; 8 | const bottomText = "Hello, world!"; 9 | 10 | return ( 11 | 12 | {ReactNativeWidgetExtension.areActivitiesEnabled() ? ( 13 | <> 14 |