Skip to content

Instantly share code, notes, and snippets.

@nandorojo
Last active December 6, 2024 03:16
Show Gist options
  • Save nandorojo/4d464e8bc9864a13caade86bbdf5ce0d to your computer and use it in GitHub Desktop.
Save nandorojo/4d464e8bc9864a13caade86bbdf5ce0d to your computer and use it in GitHub Desktop.
How to create an iOS Widget with React Native (Expo / EAS)

First, copy the config plugin from this repo: https://github.com/gaishimo/eas-widget-example

You can reference my PRs there too (which, at the time of writing, aren't merged).

After adding the config plugin (see app.json) with your dev team ID, as well as a bundle ID, you can edit the widget folder to edit your code. Then npx expo run:ios (or npx expo run:android).

Workflow

After npx expo run:ios, open the ios folder, and open the file that ends in .xcworkspace in XCode. Make sure you have the latest macOS and XCode versions. If you don't, everything will break.

At the top of XCode, to the right of the play button, click the dropdown (likely to the left of a Simulator or your device) change the target to be widget.

Next, in the left panel, click into your app and open widget/widget.swift. Edit the file, click run, and it'll open on the simulator with the widget.

I watched ten minutes of this YouTube video to learn how widgets work: https://www.youtube.com/results?search_query=swiftui+widget

I also used GPT 4 and read the docs from Apple. Pretty simple.

The data fetching part happens in the TimelineProvider btw.

TLDR: Widgets are like a server-side rendered, static app. All data fetching / image fetching happens in TimelineProvider, which is like getStaticProps from Next.js, except that it runs on a schedule determined by Apple. The result gets passed to your entry view as an entry. You have to create the types for your entry (like TypeScript types, but evaluated at runtime.)

How to share data between React Native and your widget

First, we need to set up app groups in app.json:

{
  "ios": {
      "bundleIdentifier": "com.myapp.app",
      "supportsTablet": true,
      "entitlements": {
        "com.apple.security.application-groups": [
          "group.com.myapp.app.widget"
        ]
      }
    }
}

The entitlements should be an array with IDs. The ID here is important. It must start with group. for iOS. Here, I made it group.${bundleIdentifier}.widget. The text after group. is considered your groupName by Apple.

Next, you'll need a library to call the group functions. You can try this out, hopefully it works: https://t.co/UCgtzSR38g

I'm using this since I only need to target iOS for now: https://github.com/mobiletechvn/react-native-widget-bridge

Should be easy to turn that into an Expo Module if you're feeling creative and want to help out.

import React from "react"
import Constants from "expo-constants"
import { StyleSheet, Text, View } from "react-native"
// @ts-ignore
import Preferences from "react-native-shared-group-preferences"

const appGroupIdentifier = `group.${Constants.expoConfig?.ios?.bundleIdentifier}.widget`

export default function App() {
  return (
    <View style={styles.container}>
      <Text
        onPress={async () => {
          try {
            // apparently android needs to check permissions first
            // but i ommitted that
            await Preferences.setItem(
              "test",
              {
                username: "test",
              },
              appGroupIdentifier,
            )
          } catch (errorCode) {
            // errorCode 0 = There is no suite with that name
            console.log(errorCode)
          }
        }}
      >
        Send data
      </Text>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
})
@SarimGXR
Copy link

Hye, @nandorojo I have used your library

"@bittingz/expo-widgets": "^0.6.19"

But I got this Error

get_default_flags is deprecated. Please remove the keys from the use_react_native! function
[INSTALL_PODS] if you are using the default already and pass the value you need in case you don't want the default
[INSTALL_PODS] [!] Invalid Podfile file: undefined local variable or method `flipper_config' for #Pod::Podfile:0x000000013e8c04a8.
[INSTALL_PODS] # from /private/var/folders/b1/_257_kxx0t3cx8l7jd_64g040000gn/T/eas-build-local-nodejs/3f559959-be84-456d-83b8-533c803bf77e/build/ios/Podfile:56
[INSTALL_PODS] # -------------------------------------------
[INSTALL_PODS] # # Note that if you have use_frameworks! enabled, Flipper will not work if enabled
[INSTALL_PODS] > :flipper_configuration => flipper_config
[INSTALL_PODS] # )
[INSTALL_PODS] # -------------------------------------------
[INSTALL_PODS]
Error: pod install exited with non-zero code: 1
at ChildProcess.completionListener (/Users/sarimahmad/.npm/_npx/93986e4ba4ec11e8/node_modules/@expo/spawn-async/build/spawnAsync.js:42:23)
at Object.onceWrapper (node:events:633:26)
at ChildProcess.emit (node:events:518:28)
at maybeClose (node:internal/child_process:1105:16)
at Socket. (node:internal/child_process:457:11)
at Socket.emit (node:events:518:28)
at Pipe. (node:net:337:12)

@SarimGXR
Copy link

@nandorojo How to share data in Android ? I am able to successfully Launched Widget on IOS and Android using this repositary

https://github.com/gaishimo/eas-widget-example

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment