├── app.plugin.js ├── android ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── expo │ │ └── modules │ │ └── widgetsync │ │ └── ReactNativeWidgetSyncModule.kt └── build.gradle ├── plugin ├── jest.config.js ├── src │ ├── @types │ │ └── index.d.ts │ ├── ios │ │ ├── static │ │ │ ├── Assets.xcassets │ │ │ │ ├── Contents.json │ │ │ │ ├── AccentColor.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── WidgetBackground.colorset │ │ │ │ │ └── Contents.json │ │ │ │ └── AppIcon.appiconset │ │ │ │ │ └── Contents.json │ │ │ └── widget.swift │ │ ├── withWidgetPlist.ts │ │ ├── withWidgetIos.ts │ │ ├── withWidgetEntitlements.ts │ │ ├── withWidgetSourceFiles.ts │ │ └── withWidgetXcodeTarget.ts │ ├── android │ │ ├── static │ │ │ ├── res │ │ │ │ ├── drawable-nodpi │ │ │ │ │ └── example_appwidget_preview.png │ │ │ │ ├── values │ │ │ │ │ ├── strings_widget.xml │ │ │ │ │ ├── colors_widget.xml │ │ │ │ │ ├── attrs_widget.xml │ │ │ │ │ ├── dimens_widget.xml │ │ │ │ │ ├── styles_widget.xml │ │ │ │ │ └── themes_widget.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ ├── app_widget_background.xml │ │ │ │ │ └── app_widget_inner_view_background.xml │ │ │ │ ├── values-night-v31 │ │ │ │ │ └── themes_widget.xml │ │ │ │ ├── values-v31 │ │ │ │ │ ├── themes_widget.xml │ │ │ │ │ └── styles_widget.xml │ │ │ │ ├── xml │ │ │ │ │ └── sample_widget_info.xml │ │ │ │ ├── values-v21 │ │ │ │ │ └── styles_widget.xml │ │ │ │ └── layout │ │ │ │ │ └── sample_widget.xml │ │ │ └── java │ │ │ │ └── package_name │ │ │ │ └── SampleWidget.kt │ │ ├── withWidgetProjectBuildGradle.ts │ │ ├── withWidgetAndroid.ts │ │ ├── withWidgetAppBuildGradle.ts │ │ ├── withWidgetManifest.ts │ │ └── withWidgetSourceCodes.ts │ ├── scripts │ │ └── copy.sh │ └── index.ts └── tsconfig.json ├── example ├── assets │ ├── icon.png │ ├── splash.png │ ├── favicon.png │ └── adaptive-icon.png ├── tsconfig.json ├── babel.config.js ├── webpack.config.js ├── .gitignore ├── package.json ├── metro.config.js ├── app.json └── App.tsx ├── .eslintrc.js ├── .npmignore ├── expo-module.config.json ├── tsconfig.json ├── src ├── ReactNativeWidgetSyncModule.ts └── index.ts ├── e2e ├── android.yml └── ios.yml ├── .gitignore ├── ios ├── ReactNativeWidgetSync.podspec └── ReactNativeWidgetSyncModule.swift ├── package.json └── README.md /app.plugin.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./plugin/build"); 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /plugin/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require("expo-module-scripts/jest-preset-plugin"); 2 | -------------------------------------------------------------------------------- /example/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pafry7/react-native-widget-sync/HEAD/example/assets/icon.png -------------------------------------------------------------------------------- /example/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pafry7/react-native-widget-sync/HEAD/example/assets/splash.png -------------------------------------------------------------------------------- /example/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pafry7/react-native-widget-sync/HEAD/example/assets/favicon.png -------------------------------------------------------------------------------- /plugin/src/@types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "xcode"; 2 | 3 | type WithWidgetProps = { 4 | devTeamId: string; 5 | }; 6 | -------------------------------------------------------------------------------- /example/assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pafry7/react-native-widget-sync/HEAD/example/assets/adaptive-icon.png -------------------------------------------------------------------------------- /plugin/src/ios/static/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "author": "xcode", 4 | "version": 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['universe/native', 'universe/web'], 4 | ignorePatterns: ['build'], 5 | }; 6 | -------------------------------------------------------------------------------- /plugin/src/android/static/res/drawable-nodpi/example_appwidget_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pafry7/react-native-widget-sync/HEAD/plugin/src/android/static/res/drawable-nodpi/example_appwidget_preview.png -------------------------------------------------------------------------------- /.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 | e2e/ -------------------------------------------------------------------------------- /plugin/src/ios/static/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "idiom": "universal" 5 | } 6 | ], 7 | "info": { 8 | "author": "xcode", 9 | "version": 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /plugin/src/ios/static/Assets.xcassets/WidgetBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "idiom": "universal" 5 | } 6 | ], 7 | "info": { 8 | "author": "xcode", 9 | "version": 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /expo-module.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": ["ios", "android", "web"], 3 | "ios": { 4 | "modules": ["ReactNativeWidgetSyncModule"] 5 | }, 6 | "android": { 7 | "modules": ["expo.modules.widgetsync.ReactNativeWidgetSyncModule"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo-module-scripts/tsconfig.plugin", 3 | "compilerOptions": { 4 | "outDir": "build", 5 | "rootDir": "src" 6 | }, 7 | "include": ["./src"], 8 | "exclude": ["**/__mocks__/*", "**/__tests__/*"] 9 | } 10 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true, 5 | "paths": { 6 | "react-native-widget-sync": ["../src/index"], 7 | "react-native-widget-sync/*": ["../src/*"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /plugin/src/android/static/res/values/strings_widget.xml: -------------------------------------------------------------------------------- 1 | 2 | EXAMPLE 3 | Add widget 4 | This is an app widget description 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // @generated by expo-module-scripts 2 | { 3 | "extends": "expo-module-scripts/tsconfig.base", 4 | "compilerOptions": { 5 | "outDir": "./build" 6 | }, 7 | "include": ["./src"], 8 | "exclude": ["**/__mocks__/*", "**/__tests__/*", "**/__stories__/*"] 9 | } 10 | -------------------------------------------------------------------------------- /plugin/src/android/static/res/values/colors_widget.xml: -------------------------------------------------------------------------------- 1 | 2 | #FFE1F5FE 3 | #FF81D4FA 4 | #FF039BE5 5 | #FF01579B 6 | -------------------------------------------------------------------------------- /plugin/src/scripts/copy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ios files 4 | for FILE in ./plugin/src/ios/static 5 | do 6 | cp -R $FILE ./plugin/build/ios/static/ 7 | done 8 | 9 | # android files 10 | for FILE in ./plugin/src/android/static 11 | do 12 | cp -R $FILE ./plugin/build/android/static/ 13 | done -------------------------------------------------------------------------------- /src/ReactNativeWidgetSyncModule.ts: -------------------------------------------------------------------------------- 1 | import { requireNativeModule } from 'expo-modules-core'; 2 | 3 | // It loads the native module object from the JSI or falls back to 4 | // the bridge module (from NativeModulesProxy) if the remote debugger is on. 5 | export default requireNativeModule('ReactNativeWidgetSync'); 6 | -------------------------------------------------------------------------------- /plugin/src/android/static/res/values/attrs_widget.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /plugin/src/android/static/res/values/dimens_widget.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 0dp 9 | 10 | -------------------------------------------------------------------------------- /plugin/src/android/static/res/drawable-v21/app_widget_background.xml: -------------------------------------------------------------------------------- 1 | 5 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /plugin/src/android/static/res/drawable-v21/app_widget_inner_view_background.xml: -------------------------------------------------------------------------------- 1 | 5 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /e2e/android.yml: -------------------------------------------------------------------------------- 1 | appId: "expo.modules.widgetsync.example" 2 | --- 3 | - longPressOn: "react-native-widget-sync-example" 4 | - tapOn: "Widżety" 5 | - longPressOn: 6 | id: "com.google.android.apps.nexuslauncher:id/widget_preview" 7 | - launchApp: "expo.modules.widgetsync.example" 8 | - tapOn: "Set value" 9 | - pressKey: "Home" 10 | - assertVisible: "Hello from App.tsx" 11 | - launchApp: "expo.modules.widgetsync.example" 12 | - tapOn: "Clear" 13 | - pressKey: "Home" 14 | - assertNotVisible: "Hello from App.tsx" 15 | 16 | 17 | -------------------------------------------------------------------------------- /e2e/ios.yml: -------------------------------------------------------------------------------- 1 | appId: "expo.modules.widgetsync.example" 2 | --- 3 | - longPressOn: 4 | id: "Home screen icons" 5 | - tapOn: "Add Widget" 6 | - tapOn: "Search Widgets" 7 | - inputText: "react-native" 8 | - tapOn: "react-native-widget-sync-example" 9 | - tapOn: " Add Widget" 10 | - launchApp: "expo.modules.widgetsync.example" 11 | - tapOn: "Set value" 12 | - pressKey: "Home" 13 | - assertVisible: "Hello from App.tsx" 14 | - launchApp: "expo.modules.widgetsync.example" 15 | - tapOn: "Clear" 16 | - pressKey: "Home" 17 | - assertNotVisible: "Hello from App.tsx" 18 | -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | module.exports = function (api) { 3 | api.cache(true); 4 | return { 5 | presets: ['babel-preset-expo'], 6 | plugins: [ 7 | [ 8 | 'module-resolver', 9 | { 10 | extensions: ['.tsx', '.ts', '.js', '.json'], 11 | alias: { 12 | // For development, we want to alias the library to the source 13 | 'react-native-widget-sync': path.join(__dirname, '..', 'src', 'index.ts'), 14 | }, 15 | }, 16 | ], 17 | ], 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | const createConfigAsync = require('@expo/webpack-config'); 2 | const path = require('path'); 3 | 4 | module.exports = async (env, argv) => { 5 | const config = await createConfigAsync( 6 | { 7 | ...env, 8 | babel: { 9 | dangerouslyAddModulePathsToTranspile: ['react-native-widget-sync'], 10 | }, 11 | }, 12 | argv 13 | ); 14 | config.resolve.modules = [ 15 | path.resolve(__dirname, './node_modules'), 16 | path.resolve(__dirname, '../node_modules'), 17 | ]; 18 | 19 | return config; 20 | }; 21 | -------------------------------------------------------------------------------- /plugin/src/android/static/res/values-night-v31/themes_widget.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 10 | -------------------------------------------------------------------------------- /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 | ios/ 20 | android/ 21 | 22 | # Metro 23 | .metro-health-check* 24 | 25 | # debug 26 | npm-debug.* 27 | yarn-debug.* 28 | yarn-error.* 29 | 30 | # macOS 31 | .DS_Store 32 | *.pem 33 | 34 | # local env files 35 | .env*.local 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | -------------------------------------------------------------------------------- /plugin/src/index.ts: -------------------------------------------------------------------------------- 1 | import { ConfigPlugin, withPlugins } from "@expo/config-plugins"; 2 | 3 | import { withWidgetAndroid } from "./android/withWidgetAndroid"; 4 | import { withWidgetIos } from "./ios/withWidgetIos"; 5 | 6 | export interface Props { 7 | widgetName: string; 8 | ios: { 9 | devTeamId: string; 10 | appGroupIdentifier: string; 11 | topLevelFiles?: string[]; 12 | }; 13 | } 14 | 15 | const withAppConfigs: ConfigPlugin = (config, options) => { 16 | return withPlugins(config, [ 17 | [withWidgetAndroid, options], 18 | [withWidgetIos, options], 19 | ]); 20 | }; 21 | 22 | export default withAppConfigs; 23 | -------------------------------------------------------------------------------- /plugin/src/android/static/res/values/styles_widget.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /plugin/src/android/static/res/values-v31/themes_widget.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | -------------------------------------------------------------------------------- /plugin/src/android/static/res/xml/sample_widget_info.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /plugin/src/android/static/res/values-v21/styles_widget.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-widget-sync-example", 3 | "version": "1.0.0", 4 | "main": "node_modules/expo/AppEntry.js", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo run:android", 8 | "ios": "expo run:ios", 9 | "web": "expo start --web" 10 | }, 11 | "dependencies": { 12 | "expo": "~49.0.5", 13 | "react": "18.2.0", 14 | "react-native": "0.72.3", 15 | "expo-splash-screen": "~0.20.4", 16 | "expo-status-bar": "~1.6.0" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.20.0", 20 | "@types/react": "~18.0.14", 21 | "typescript": "^5.1.3" 22 | }, 23 | "private": true, 24 | "expo": { 25 | "autolinking": { 26 | "nativeModulesDir": ".." 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plugin/src/android/withWidgetProjectBuildGradle.ts: -------------------------------------------------------------------------------- 1 | import { ConfigPlugin, withProjectBuildGradle } from "@expo/config-plugins"; 2 | 3 | /** 4 | * Add configuration of kotlin-gradle-plugin 5 | * @param config 6 | * @returns 7 | */ 8 | export const withWidgetProjectBuildGradle: ConfigPlugin = (config) => { 9 | return withProjectBuildGradle(config, async (newConfig) => { 10 | const buildGradle = newConfig.modResults.contents; 11 | 12 | const search = /dependencies\s?{/; 13 | const replace = `dependencies { 14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:\${project.ext.kotlinVersion}"`; 15 | const newBuildGradle = buildGradle.replace(search, replace); 16 | newConfig.modResults.contents = newBuildGradle; 17 | return newConfig; 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /.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 | android/app/libs 40 | android/keystores/debug.keystore 41 | 42 | # Cocoapods 43 | # 44 | example/ios/Pods 45 | 46 | # Ruby 47 | example/vendor/ 48 | 49 | # node.js 50 | # 51 | node_modules/ 52 | npm-debug.log 53 | yarn-debug.log 54 | yarn-error.log 55 | 56 | # Expo 57 | .expo/* 58 | -------------------------------------------------------------------------------- /plugin/src/android/static/res/values/themes_widget.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 17 | -------------------------------------------------------------------------------- /plugin/src/android/withWidgetAndroid.ts: -------------------------------------------------------------------------------- 1 | import { ConfigPlugin } from "@expo/config-plugins"; 2 | 3 | import { withWidgetAppBuildGradle } from "./withWidgetAppBuildGradle"; 4 | import { withWidgetManifest } from "./withWidgetManifest"; 5 | import { withWidgetProjectBuildGradle } from "./withWidgetProjectBuildGradle"; 6 | import { withWidgetSourceCodes } from "./withWidgetSourceCodes"; 7 | import { Props } from ".."; 8 | 9 | export const withWidgetAndroid: ConfigPlugin = ( 10 | config, 11 | { widgetName, ios: { appGroupIdentifier } } 12 | ) => { 13 | config = withWidgetManifest(config, { widgetName }); 14 | config = withWidgetProjectBuildGradle(config); 15 | config = withWidgetAppBuildGradle(config); 16 | config = withWidgetSourceCodes(config, { 17 | widgetName, 18 | appGroupName: appGroupIdentifier, 19 | }); 20 | return config; 21 | }; 22 | -------------------------------------------------------------------------------- /plugin/src/android/static/res/values-v31/styles_widget.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 16 | -------------------------------------------------------------------------------- /plugin/src/android/static/res/layout/sample_widget.xml: -------------------------------------------------------------------------------- 1 | 6 | 18 | -------------------------------------------------------------------------------- /plugin/src/android/withWidgetAppBuildGradle.ts: -------------------------------------------------------------------------------- 1 | import { ConfigPlugin, withAppBuildGradle } from "@expo/config-plugins"; 2 | 3 | /** 4 | * Add "apply plugin: kotlin-android" to app build.gradle 5 | * @param config 6 | * @returns 7 | */ 8 | export const withWidgetAppBuildGradle: ConfigPlugin = (config) => { 9 | return withAppBuildGradle(config, async (newConfig) => { 10 | const buildGradle = newConfig.modResults.contents; 11 | const search = /(apply plugin: "com\.android\.application"\n)/gm; 12 | const replace = `$1apply plugin: "kotlin-android"\n`; 13 | let newBuildGradle = buildGradle.replace(search, replace); 14 | 15 | newBuildGradle = newBuildGradle.replace( 16 | /dependencies\s?{/, 17 | `dependencies { 18 | implementation 'com.google.code.gson:gson:2.10.1'` 19 | ); 20 | 21 | newConfig.modResults.contents = newBuildGradle; 22 | return newConfig; 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /ios/ReactNativeWidgetSync.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json'))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = 'ReactNativeWidgetSync' 7 | s.version = package['version'] 8 | s.summary = package['description'] 9 | s.description = package['description'] 10 | s.license = package['license'] 11 | s.author = package['author'] 12 | s.homepage = package['homepage'] 13 | s.platform = :ios, '13.0' 14 | s.swift_version = '5.4' 15 | s.source = { git: 'https://github.com/pafry7/react-native-widget-sync' } 16 | s.static_framework = true 17 | 18 | s.dependency 'ExpoModulesCore' 19 | 20 | # Swift/Objective-C compatibility 21 | s.pod_target_xcconfig = { 22 | 'DEFINES_MODULE' => 'YES', 23 | 'SWIFT_COMPILATION_MODE' => 'wholemodule' 24 | } 25 | 26 | s.source_files = "**/*.{h,m,swift}" 27 | end 28 | -------------------------------------------------------------------------------- /example/metro.config.js: -------------------------------------------------------------------------------- 1 | // Learn more https://docs.expo.io/guides/customizing-metro 2 | const { getDefaultConfig } = require('expo/metro-config'); 3 | const path = require('path'); 4 | 5 | const config = getDefaultConfig(__dirname); 6 | 7 | // npm v7+ will install ../node_modules/react-native because of peerDependencies. 8 | // To prevent the incompatible react-native bewtween ./node_modules/react-native and ../node_modules/react-native, 9 | // excludes the one from the parent folder when bundling. 10 | config.resolver.blockList = [ 11 | ...Array.from(config.resolver.blockList ?? []), 12 | new RegExp(path.resolve('..', 'node_modules', 'react-native')), 13 | ]; 14 | 15 | config.resolver.nodeModulesPaths = [ 16 | path.resolve(__dirname, './node_modules'), 17 | path.resolve(__dirname, '../node_modules'), 18 | ]; 19 | 20 | config.watchFolders = [path.resolve(__dirname, '..')]; 21 | 22 | config.transformer.getTransformOptions = async () => ({ 23 | transform: { 24 | experimentalImportSupport: false, 25 | inlineRequires: true, 26 | }, 27 | }); 28 | 29 | module.exports = config; -------------------------------------------------------------------------------- /plugin/src/ios/withWidgetPlist.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ConfigPlugin, 3 | InfoPlist, 4 | withDangerousMod, 5 | } from "@expo/config-plugins"; 6 | import plist from "@expo/plist"; 7 | import * as fs from "fs"; 8 | import * as path from "path"; 9 | 10 | interface Props { 11 | targetName: string; 12 | } 13 | 14 | export const withWidgetPlist: ConfigPlugin = ( 15 | config, 16 | { targetName } 17 | ) => { 18 | return withDangerousMod(config, [ 19 | "ios", 20 | async (config) => { 21 | const extensionRootPath = path.join( 22 | config.modRequest.platformProjectRoot, 23 | targetName 24 | ); 25 | const extensionPlistPath = path.join(extensionRootPath, "Info.plist"); 26 | 27 | const extensionPlist: InfoPlist = { 28 | NSExtension: { 29 | NSExtensionPointIdentifier: "com.apple.widgetkit-extension", 30 | }, 31 | }; 32 | 33 | await fs.promises.mkdir(path.dirname(extensionPlistPath), { 34 | recursive: true, 35 | }); 36 | await fs.promises.writeFile( 37 | extensionPlistPath, 38 | plist.build(extensionPlist) 39 | ); 40 | 41 | return config; 42 | }, 43 | ]); 44 | }; 45 | -------------------------------------------------------------------------------- /plugin/src/ios/withWidgetIos.ts: -------------------------------------------------------------------------------- 1 | import { ConfigPlugin } from "@expo/config-plugins"; 2 | import { withWidgetEntitlements } from "./withWidgetEntitlements"; 3 | import { withWidgetSourceFiles } from "./withWidgetSourceFiles"; 4 | import { withWidgetPlist } from "./withWidgetPlist"; 5 | import { withWidgetXcodeTarget } from "./withWidgetXcodeTarget"; 6 | import { Props } from ".."; 7 | 8 | // make defaults 9 | export const DEFAULT_WIDGET_TARGET_NAME = "widget"; 10 | export const DEFAULT_TOP_LEVEL_FILES = ["Assets.xcassets", "widget.swift"]; 11 | 12 | export const withWidgetIos: ConfigPlugin = ( 13 | config, 14 | { widgetName, ios } 15 | ) => { 16 | const { appGroupIdentifier, devTeamId } = ios; 17 | const targetName = widgetName ?? DEFAULT_WIDGET_TARGET_NAME; 18 | const topLevelFiles = ios.topLevelFiles ?? DEFAULT_TOP_LEVEL_FILES; 19 | 20 | config = withWidgetEntitlements(config, { targetName, appGroupIdentifier }); 21 | config = withWidgetSourceFiles(config, { targetName, appGroupIdentifier }); 22 | config = withWidgetPlist(config, { targetName }); 23 | config = withWidgetXcodeTarget(config, { 24 | devTeamId, 25 | targetName, 26 | topLevelFiles, 27 | }); 28 | return config; 29 | }; 30 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // Import the native module. On web, it will be resolved to RefreshWidget.web.ts 2 | // and on native platforms to RefreshWidget.ts 3 | import RefreshWidgetModule from "./ReactNativeWidgetSyncModule"; 4 | 5 | export function reloadAll(): void { 6 | return RefreshWidgetModule.reloadAll(); 7 | } 8 | export function setItem(appGroup: string, key: string, value: any): void; 9 | export function setItem( 10 | appGroup: string, 11 | key?: string, 12 | value?: any 13 | ): (key: string, value: any) => void; 14 | 15 | export function setItem(appGroup: string, key?: string, value?: any) { 16 | if (typeof key !== "undefined" && typeof value !== "undefined") { 17 | return RefreshWidgetModule.setItem(value, key, appGroup); 18 | } 19 | return (key: string, value: any) => 20 | RefreshWidgetModule.setItem(value, key, appGroup); 21 | } 22 | 23 | export function getItem(appGroup: string, key: string): string; 24 | export function getItem( 25 | appGroup: string, 26 | key?: string 27 | ): (key: string) => string; 28 | 29 | export function getItem(appGroup: string, key?: string) { 30 | if (typeof key !== "undefined") { 31 | return RefreshWidgetModule.getItem(key, appGroup); 32 | } 33 | return (key: string) => RefreshWidgetModule.getItem(key, appGroup); 34 | } 35 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "react-native-widget-sync-example", 4 | "slug": "react-native-widget-sync-example", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "splash": { 10 | "image": "./assets/splash.png", 11 | "resizeMode": "contain", 12 | "backgroundColor": "#ffffff" 13 | }, 14 | "assetBundlePatterns": ["**/*"], 15 | "ios": { 16 | "supportsTablet": true, 17 | "bundleIdentifier": "expo.modules.widgetsync.example", 18 | "entitlements": { 19 | "com.apple.security.application-groups": [ 20 | "group.expo.modules.widgetsync.example" 21 | ] 22 | } 23 | }, 24 | "android": { 25 | "adaptiveIcon": { 26 | "foregroundImage": "./assets/adaptive-icon.png", 27 | "backgroundColor": "#ffffff" 28 | }, 29 | "package": "expo.modules.widgetsync.example" 30 | }, 31 | "web": { 32 | "favicon": "./assets/favicon.png" 33 | }, 34 | "plugins": [ 35 | [ 36 | "../app.plugin.js", 37 | { 38 | "widgetName": "widget", 39 | "ios": { 40 | "devTeamId": "", 41 | "appGroupIdentifier": "group.expo.modules.widgetsync.example" 42 | } 43 | } 44 | ] 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-widget-sync", 3 | "version": "0.1.0", 4 | "description": "The module allows refreshing and sharing of data between the main application and the widget.", 5 | "main": "build/index.js", 6 | "types": "build/index.d.ts", 7 | "scripts": { 8 | "clean": "expo-module clean plugin", 9 | "lint": "expo-module lint", 10 | "test": "expo-module test", 11 | "prepublishOnly": "expo-module prepublishOnly", 12 | "expo-module": "expo-module", 13 | "open:ios": "open -a \"Xcode\" example/ios", 14 | "open:android": "open -a \"Android Studio\" example/android", 15 | "build:plugin": "yarn clean && EXPO_NONINTERACTIVE=1 expo-module build plugin && ./plugin/src/scripts/copy.sh", 16 | "prepare": "yarn build:plugin" 17 | }, 18 | "keywords": [ 19 | "react-native", 20 | "expo", 21 | "widget", 22 | "react-native-widget-sync", 23 | "ReactNativeWidgetSync" 24 | ], 25 | "repository": "https://github.com/pafry7/react-native-widget-sync", 26 | "bugs": { 27 | "url": "https://github.com/pafry7/react-native-widget-sync/issues" 28 | }, 29 | "author": "Patryk Fryda ()", 30 | "license": "MIT", 31 | "homepage": "https://github.com/pafry7/react-native-widget-sync#readme", 32 | "devDependencies": { 33 | "@types/react": "^18.0.25", 34 | "expo-module-scripts": "^3.0.11", 35 | "expo-modules-core": "^1.5.7" 36 | }, 37 | "peerDependencies": { 38 | "expo": "*", 39 | "react": "*", 40 | "react-native": "*" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /plugin/src/android/withWidgetManifest.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AndroidConfig, 3 | ConfigPlugin, 4 | withAndroidManifest, 5 | } from "@expo/config-plugins"; 6 | 7 | export const withWidgetManifest: ConfigPlugin<{ widgetName: string }> = ( 8 | config, 9 | { widgetName } 10 | ) => { 11 | return withAndroidManifest(config, async (newConfig) => { 12 | const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow( 13 | newConfig.modResults 14 | ); 15 | const widgetReceivers = await buildWidgetsReceivers(widgetName); 16 | mainApplication.receiver = widgetReceivers; 17 | 18 | AndroidConfig.Manifest.addMetaDataItemToMainApplication( 19 | mainApplication, 20 | "WIDGET_NAME", 21 | widgetName 22 | ); 23 | 24 | return newConfig; 25 | }); 26 | }; 27 | 28 | async function buildWidgetsReceivers(widgetName: string) { 29 | return [ 30 | { 31 | $: { 32 | "android:name": `.${widgetName}`, 33 | "android:exported": "false" as const, 34 | }, 35 | "intent-filter": [ 36 | { 37 | action: [ 38 | { 39 | $: { 40 | "android:name": "android.appwidget.action.APPWIDGET_UPDATE", 41 | }, 42 | }, 43 | ], 44 | }, 45 | ], 46 | "meta-data": [ 47 | { 48 | $: { 49 | "android:name": "android.appwidget.provider", 50 | "android:resource": "@xml/sample_widget_info", 51 | }, 52 | }, 53 | ], 54 | }, 55 | ]; 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > The development of the npm module has been temporarily postponed. 2 | 3 | # react-native-widget-sync 4 | 5 | The module allows refreshing and sharing of data between the main application and the widget. 6 | 7 | ## Usage in expo managed project 8 | 9 | See https://github.com/pafry7/widget-expo-example 10 | 11 | ## Setup 12 | 13 | 1. Clone repo 14 | ``` 15 | git clone git@github.com:pafry7/react-native-widget-sync.git 16 | ``` 17 | 18 | 2. Install dependencies 19 | 20 | ``` 21 | cd react-native-widget-sync 22 | yarn 23 | cd example 24 | yarn 25 | ``` 26 | 27 | 3. Configure expo plugin in `/example` 28 | Add in `app.json` 29 | 30 | ``` 31 | "ios": { 32 | ... 33 | "entitlements": { 34 | "appGroupIdentifier": 35 | }, 36 | }, 37 | "plugins": { 38 | [ 39 | "react-native-widget-sync", 40 | { 41 | "widgetName": 42 | "ios": { 43 | "devTeamId": 44 | "appGroupIdentifier": " 45 | } 46 | } 47 | ], 48 | } 49 | 50 | ``` 51 | ## Running the example 52 | 53 | Run 54 | 55 | ``` 56 | npx expo prebuild 57 | ``` 58 | 59 | and then 60 | 61 | ``` 62 | npx expo run:[ios|android] 63 | ``` 64 | 65 | ## How does it work 66 | 67 | When you run expo prebuild for the first time, it generates a folder containing the source files for widgets and then copies them to both the ios and android directories. Remember to run prebuild each time you make changes to the widget's source files. 68 | 69 | ## Video 70 | 71 | https://github.com/pafry7/react-native-widget-sync/assets/41058200/f0dcf601-31a3-4ead-9eaf-0ae8a67346c6 72 | 73 | 74 | ## Sources 75 | - https://github.com/gaishimo/eas-widget-example 76 | - https://github.com/matallui/demo-screen-capture 77 | 78 | -------------------------------------------------------------------------------- /plugin/src/ios/withWidgetEntitlements.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ConfigPlugin, 3 | InfoPlist, 4 | withXcodeProject, 5 | } from "@expo/config-plugins"; 6 | import plist from "@expo/plist"; 7 | import * as fs from "fs"; 8 | import * as path from "path"; 9 | 10 | interface Props { 11 | appGroupIdentifier: string; 12 | targetName: string; 13 | } 14 | 15 | export const withWidgetEntitlements: ConfigPlugin = ( 16 | config, 17 | { appGroupIdentifier, targetName } 18 | ) => { 19 | return withXcodeProject(config, async (config) => { 20 | const entitlementsFilename = `${targetName}.entitlements`; 21 | const extensionRootPath = path.join( 22 | config.modRequest.platformProjectRoot, 23 | targetName 24 | ); 25 | const entitlementsPath = path.join(extensionRootPath, entitlementsFilename); 26 | 27 | const extensionEntitlements: InfoPlist = { 28 | "com.apple.security.application-groups": [appGroupIdentifier], 29 | }; 30 | 31 | // create file 32 | await fs.promises.mkdir(path.dirname(entitlementsPath), { 33 | recursive: true, 34 | }); 35 | await fs.promises.writeFile( 36 | entitlementsPath, 37 | plist.build(extensionEntitlements) 38 | ); 39 | 40 | // add file to extension group 41 | const proj = config.modResults; 42 | const targetUuid = proj.findTargetKey(targetName); 43 | const groupUuid = proj.findPBXGroupKey({ name: targetName }); 44 | 45 | proj.addFile(entitlementsFilename, groupUuid, { 46 | target: targetUuid, 47 | lastKnownFileType: "text.plist.entitlements", 48 | }); 49 | 50 | // update build properties 51 | proj.updateBuildProperty( 52 | "CODE_SIGN_ENTITLEMENTS", 53 | `${targetName}/${entitlementsFilename}`, 54 | null, 55 | targetName 56 | ); 57 | 58 | return config; 59 | }); 60 | }; 61 | -------------------------------------------------------------------------------- /ios/ReactNativeWidgetSyncModule.swift: -------------------------------------------------------------------------------- 1 | import ExpoModulesCore 2 | import WidgetKit 3 | 4 | internal class VersionException: Exception { 5 | override var reason: String { 6 | "Function is available only on iOS 14 and higher" 7 | } 8 | } 9 | 10 | public class ReactNativeWidgetSyncModule: Module { 11 | // Each module class must implement the definition function. The definition consists of components 12 | // that describes the module's functionality and behavior. 13 | // See https://docs.expo.dev/modules/module-api for more details about available components. 14 | public func definition() -> ModuleDefinition { 15 | // Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument. 16 | // Can be inferred from module's class name, but it's recommended to set it explicitly for clarity. 17 | // The module will be accessible from `requireNativeModule('RefreshWidget')` in JavaScript. 18 | Name("ReactNativeWidgetSync") 19 | 20 | Function("reloadAll") { () -> Void in 21 | if #available(iOS 14, *) { 22 | WidgetCenter.shared.reloadAllTimelines() 23 | } else { 24 | throw VersionException() 25 | } 26 | } 27 | 28 | Function("setItem") { (value: String, key: String, appGroup: String) -> Void in 29 | if let userDefaults = UserDefaults(suiteName: appGroup){ 30 | userDefaults.set(value, forKey: key ) 31 | } 32 | } 33 | 34 | Function("getItem") { ( key: String, appGroup: String) -> String? in 35 | if let userDefaults = UserDefaults(suiteName: appGroup){ 36 | return userDefaults.string(forKey: key) 37 | } 38 | return nil 39 | } 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /plugin/src/ios/static/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "iphone", 5 | "scale": "2x", 6 | "size": "20x20" 7 | }, 8 | { 9 | "idiom": "iphone", 10 | "scale": "3x", 11 | "size": "20x20" 12 | }, 13 | { 14 | "idiom": "iphone", 15 | "scale": "2x", 16 | "size": "29x29" 17 | }, 18 | { 19 | "idiom": "iphone", 20 | "scale": "3x", 21 | "size": "29x29" 22 | }, 23 | { 24 | "idiom": "iphone", 25 | "scale": "2x", 26 | "size": "40x40" 27 | }, 28 | { 29 | "idiom": "iphone", 30 | "scale": "3x", 31 | "size": "40x40" 32 | }, 33 | { 34 | "idiom": "iphone", 35 | "scale": "2x", 36 | "size": "60x60" 37 | }, 38 | { 39 | "idiom": "iphone", 40 | "scale": "3x", 41 | "size": "60x60" 42 | }, 43 | { 44 | "idiom": "ipad", 45 | "scale": "1x", 46 | "size": "20x20" 47 | }, 48 | { 49 | "idiom": "ipad", 50 | "scale": "2x", 51 | "size": "20x20" 52 | }, 53 | { 54 | "idiom": "ipad", 55 | "scale": "1x", 56 | "size": "29x29" 57 | }, 58 | { 59 | "idiom": "ipad", 60 | "scale": "2x", 61 | "size": "29x29" 62 | }, 63 | { 64 | "idiom": "ipad", 65 | "scale": "1x", 66 | "size": "40x40" 67 | }, 68 | { 69 | "idiom": "ipad", 70 | "scale": "2x", 71 | "size": "40x40" 72 | }, 73 | { 74 | "idiom": "ipad", 75 | "scale": "2x", 76 | "size": "76x76" 77 | }, 78 | { 79 | "idiom": "ipad", 80 | "scale": "2x", 81 | "size": "83.5x83.5" 82 | }, 83 | { 84 | "idiom": "ios-marketing", 85 | "scale": "1x", 86 | "size": "1024x1024" 87 | } 88 | ], 89 | "info": { 90 | "author": "xcode", 91 | "version": 1 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /plugin/src/ios/static/widget.swift: -------------------------------------------------------------------------------- 1 | // widget.swift 2 | // widget 3 | // 4 | 5 | import WidgetKit 6 | import SwiftUI 7 | import Foundation 8 | 9 | struct Provider: TimelineProvider { 10 | func placeholder(in context: Context) -> SimpleEntry { 11 | SimpleEntry(date: Date(), text: "Placeholder") 12 | } 13 | 14 | func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) { 15 | let entry = SimpleEntry(date: Date(), text: "Snapshot") 16 | completion(entry) 17 | } 18 | 19 | func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { 20 | let text = getItem() 21 | 22 | let entry = SimpleEntry(date: Date(), text: text) 23 | 24 | let timeline = Timeline(entries: [entry], policy: .never) 25 | completion(timeline) 26 | } 27 | 28 | 29 | private func getItem() -> String { 30 | let userDefaults = UserDefaults(suiteName: "group.com.example.widget") 31 | return userDefaults?.string(forKey: "savedData") ?? "" 32 | } 33 | 34 | } 35 | 36 | struct SimpleEntry: TimelineEntry { 37 | let date: Date 38 | let text: String 39 | } 40 | 41 | struct widgetEntryView : View { 42 | var entry: Provider.Entry 43 | 44 | var body: some View { 45 | Text(entry.text) 46 | } 47 | 48 | 49 | } 50 | 51 | @main 52 | struct widget: Widget { 53 | let kind: String = "widget" 54 | 55 | var body: some WidgetConfiguration { 56 | StaticConfiguration(kind: kind, provider: Provider()) { entry in 57 | widgetEntryView(entry: entry) 58 | } 59 | .configurationDisplayName("Widget name") 60 | .description("Widget description") 61 | .supportedFamilies([.systemSmall]) 62 | } 63 | } 64 | 65 | struct widget_Previews: PreviewProvider { 66 | static var previews: some View { 67 | widgetEntryView(entry: SimpleEntry(date: Date(), text: "Preview")) 68 | .previewContext(WidgetPreviewContext(family: .systemSmall)) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /plugin/src/android/static/java/package_name/SampleWidget.kt: -------------------------------------------------------------------------------- 1 | package com.example.app 2 | 3 | import android.appwidget.AppWidgetManager 4 | import android.appwidget.AppWidgetProvider 5 | import android.content.Context 6 | import android.widget.RemoteViews 7 | import android.content.SharedPreferences 8 | import android.os.SystemClock 9 | import android.content.ComponentName; 10 | import android.content.Intent; 11 | import android.app.PendingIntent; 12 | import android.net.Uri; 13 | 14 | /** 15 | * Implementation of App Widget functionality. 16 | */ 17 | class SampleWidget : AppWidgetProvider() { 18 | override fun onUpdate( 19 | context: Context, 20 | appWidgetManager: AppWidgetManager, 21 | appWidgetIds: IntArray 22 | ) { 23 | // There may be multiple widgets active, so update all of them 24 | for (appWidgetId in appWidgetIds) { 25 | updateAppWidget(context, appWidgetManager, appWidgetId) 26 | } 27 | } 28 | 29 | override fun onEnabled(context: Context) { 30 | // Enter relevant functionality for when the first widget is created 31 | } 32 | 33 | override fun onDisabled(context: Context) { 34 | // Enter relevant functionality for when the last widget is disabled 35 | } 36 | } 37 | 38 | internal fun updateAppWidget( 39 | context: Context, 40 | appWidgetManager: AppWidgetManager, 41 | appWidgetId: Int 42 | ) { 43 | // change this to group value passed in app.json 44 | val text = getItem(context, "savedData", "group.com.example.widget") ?: "" 45 | 46 | // Construct the RemoteViews object 47 | val views = RemoteViews(context.packageName, R.layout.sample_widget) 48 | views.setTextViewText(R.id.appwidget_text, text) 49 | 50 | // Instruct the widget manager to update the widget 51 | appWidgetManager.updateAppWidget(appWidgetId, views) 52 | } 53 | 54 | internal fun getItem( 55 | context: Context, 56 | key: String, 57 | preferenceName: String 58 | ): String? { 59 | val preferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE) 60 | return preferences.getString(key, null) 61 | } 62 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'maven-publish' 4 | 5 | group = 'expo.modules.widgetsync' 6 | version = '0.1.0' 7 | 8 | buildscript { 9 | def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") 10 | if (expoModulesCorePlugin.exists()) { 11 | apply from: expoModulesCorePlugin 12 | applyKotlinExpoModulesCorePlugin() 13 | } 14 | 15 | // Simple helper that allows the root project to override versions declared by this library. 16 | ext.safeExtGet = { prop, fallback -> 17 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 18 | } 19 | 20 | // Ensures backward compatibility 21 | ext.getKotlinVersion = { 22 | if (ext.has("kotlinVersion")) { 23 | ext.kotlinVersion() 24 | } else { 25 | ext.safeExtGet("kotlinVersion", "1.8.10") 26 | } 27 | } 28 | 29 | repositories { 30 | mavenCentral() 31 | } 32 | 33 | dependencies { 34 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}") 35 | } 36 | } 37 | 38 | afterEvaluate { 39 | publishing { 40 | publications { 41 | release(MavenPublication) { 42 | from components.release 43 | } 44 | } 45 | repositories { 46 | maven { 47 | url = mavenLocal().url 48 | } 49 | } 50 | } 51 | } 52 | 53 | android { 54 | compileSdkVersion safeExtGet("compileSdkVersion", 33) 55 | 56 | compileOptions { 57 | sourceCompatibility JavaVersion.VERSION_11 58 | targetCompatibility JavaVersion.VERSION_11 59 | } 60 | 61 | kotlinOptions { 62 | jvmTarget = JavaVersion.VERSION_11.majorVersion 63 | } 64 | 65 | namespace "expo.modules.widgetsync" 66 | defaultConfig { 67 | minSdkVersion safeExtGet("minSdkVersion", 21) 68 | targetSdkVersion safeExtGet("targetSdkVersion", 33) 69 | versionCode 1 70 | versionName "0.1.0" 71 | } 72 | lintOptions { 73 | abortOnError false 74 | } 75 | publishing { 76 | singleVariant("release") { 77 | withSourcesJar() 78 | } 79 | } 80 | } 81 | 82 | repositories { 83 | mavenCentral() 84 | } 85 | 86 | dependencies { 87 | implementation project(':expo-modules-core') 88 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" 89 | } 90 | -------------------------------------------------------------------------------- /example/App.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { Pressable, StyleSheet, Text, View } from "react-native"; 3 | import { getItem, reloadAll, setItem } from "react-native-widget-sync"; 4 | 5 | const GROUP_NAME = "group.expo.modules.widgetsync.example"; 6 | 7 | const getSharedData = getItem(GROUP_NAME); 8 | const setSharedData = setItem(GROUP_NAME); 9 | 10 | function Button({ onPress, title }: { title: string; onPress: () => void }) { 11 | return ( 12 | 13 | {title} 14 | 15 | ); 16 | } 17 | 18 | export default function App() { 19 | const [value, setValue] = useState(getSharedData(GROUP_NAME) ?? ""); 20 | 21 | useEffect(() => { 22 | setSharedData("savedData", value); 23 | reloadAll(); 24 | }, [value]); 25 | 26 | const onPress = () => { 27 | setValue("Hello from App.tsx"); 28 | }; 29 | 30 | const clear = () => { 31 | setValue(""); 32 | }; 33 | 34 | return ( 35 | 36 | 37 | Current value 38 | {value} 39 | 40 | 41 |