expo-widgets

repository·main·Indexed 43 days ago

https://github.com/mike-stewart-dev/expo-widgets

An Expo module and Config Plugin that enables the creation and management of native widgets for iOS and Android. It provides automated Xcode target creation for iOS, Kotlin support for Android, and tools for managing App Group permissions and native source files within an Expo project. Supports version 3.0.1, with v1 for Expo 49 and v2 for Expo 51+.

Tokens
5.6K
Snippets
19
Records
23
Agent score
55%

What's inside expo-widgets

  1. Set up iOS widget files

    main

    Within your designated iOS widget source folder (ios.src), you must create the following files:

    • Module.swift
    • A Widget Bundle
    • Assets.xcassets
    • Widget Swift files

    If you have Swift files (like data models) used by both the main module and the widget, add them to the moduleDependencies array in your plugin configuration to ensure they are included.

  2. Set up Android widget files

    main

    The Android source folder (android.src) must mimic a standard Android Studio project structure. It requires two main subfolder paths:

    1. /android/main/java/package_name: Place your .kt (Kotlin) widget files here.
    2. /android/res/: Place your assets here (the same as in Android Studio).

    Configuration requirements:

    • widgets array: Define your providers. The resourceName must point to your XML file in the res folder (e.g., @xml/my_widget_info for /res/xml/my_widget_info.xml).
    • distPlaceholder: For apps requiring multiple distributions with different package names, set this to a string (e.g., com.company.app). The plugin will replace all instances of this placeholder in your widget source files with the actual package name from your config during build.
  3. Configure the expo-widgets plugin in app.config

    main

    To enable native widgets, add @bittingz/expo-widgets to your plugins array in app.config.{js/ts} or app.json. You can configure iOS and Android separately. If you only support one platform, you can omit the other.

    [
        "@bittingz/expo-widgets",
        {
            ios: {
                src: "./src/my/path/to/ios/widgets/folder",
                devTeamId: "your apple dev team ID",
                mode: "production",                        
                moduleDependencies: [],
                useLiveActivities: false,
                frequentUpdates: false,
                entitlements: {
                    "any xcode entitlement the widget needs": "entitlement value"
                }
            },
            android: {
                src: "./src/my/path/to/android/widgets/folder",
                widgets: [
                    {
                        "name": "MyWidgetProvider",
                        "resourceName": "@xml/my_widget_info"
                    }
                ],
                distPlaceholder: "optional.placeholder"
            }                      
        }
    ],
  4. Override Xcode options for iOS widgets

    main

    You can customize Xcode build settings by adding an xcode object to your iOS configuration in app.json or app.config.js. All properties are optional.

    • appExtAPI: A boolean that sets APP_EXTENSION_API_ONLY in the Podfile.
    • configOverrides: An object of key-value pairs representing xcodeproj values. Keys must match the case exactly (e.g., SWIFT_VERSION: '5.0').
    • entitlements: An object of key-value pairs for widget entitlements.
    "@bittingz/expo-widgets",
    {
        "ios": {
            "xcode": {
                "appExtAPI": true,
                "configOverrides": {
                    "SWIFT_VERSION": "5.0"
                },
                "entitlements": {
                    "com.apple.developer.widget-extension": true
                }
            }
        }
    }
  5. Troubleshoot Android 'unresolved reference R' error

    main

    If you use R in your Kotlin widget files to access layouts and encounter an unresolved reference R error:

    1. Add the explicit package import: package your.appconfig.packageid.R.
    2. Delete your android folder.
    3. Rebuild the project.
  6. Configure Metro for monorepo resolution in expo-widgets

    main

    When using expo-widgets in a monorepo structure (where the package resides in a parent directory relative to the Expo project), you must configure metro.config.js to prevent dependency conflicts and ensure the bundler can locate the package and its dependencies.

    Key configuration steps:

    1. Exclude parent node_modules: Use config.resolver.blockList to prevent Metro from bundling react or react-native from the parent node_modules folder, which avoids version mismatch errors.
    2. Set nodeModulesPaths: Explicitly include both the local ./node_modules and the parent ../node_modules in config.resolver.nodeModulesPaths.
    3. Map extraNodeModules: Use config.resolver.extraNodeModules to map the package name @bittingz/expo-widgets to the parent directory ...
    4. Configure watchFolders: Add the parent directory to config.watchFolders so Metro tracks changes in the package source.
    const { getDefaultConfig } = require('expo/metro-config');
    const path = require('path');
    
    const config = getDefaultConfig(__dirname);
    
    // Prevent incompatible react/react-native versions from parent folder
    config.resolver.blockList = [
      ...Array.from(config.resolver.blockList ?? []),
      new RegExp(path.resolve('..', 'node_modules', 'react')),
      new RegExp(path.resolve('..', 'node_modules', 'react-native')),
    ];
    
    // Ensure Metro looks in both local and parent node_modules
    config.resolver.nodeModulesPaths = [
      path.resolve(__dirname, './node_modules'),
      path.resolve(__dirname, '../node_modules'),
    ];
    
    // Map the package to the parent directory
    config.resolver.extraNodeModules = {
      '@bittingz/expo-widgets': '..',
    };
    
    // Watch the parent directory for changes
    config.watchFolders = [path.resolve(__dirname, '..')];
    
    module.exports = config;
  7. Configure expo-widgets module mapping

    main

    The expo-module.config.json file defines the native module mappings for the expo-widgets package. It specifies which native classes are exposed to the Expo runtime on iOS and Android.

    • ios: Maps the ExpoWidgetsModule class.
    • android: Maps the expo.modules.widgets.ExpoWidgetsModule class.
    {
      "platforms": ["ios", "android"],
      "ios": {
        "modules": ["ExpoWidgetsModule"]
      },
      "android": {
        "modules": ["expo.modules.widgets.ExpoWidgetsModule"]
      }
    }
  8. Configure iOS widgets with WithExpoIOSWidgetsProps

    main

    When configuring iOS widgets, provide the following properties:

    • src: The directory containing your widget native code.
    • deploymentTarget: The deployment target for the app (defaults to 16.2 to support live activities).
    • useLiveActivities: Boolean to enable live activities (SSupportsLiveActivities).
    • frequentUpdates: Boolean to enable frequent live activity updates (NSSupportsLiveActivitiesFrequentUpdates).
    • devTeamId: Your Apple developer team ID.
    • targetName: (Optional) The target/extension name.
    • xcode: (Optional) An object to override Xcode project settings:
      • widgetBundleIdentifier: The bundle ID for the widget(s).
      • appGroupId: Optional app group ID override.
      • entitlements: Optional IOSEntitlements (a PlistObject).
      • configOverrides: A dictionary of attributeName: string to customize the Xcode configuration block.
      • appExtAPI: Boolean to control the Podfile APP_EXTENSION_API_ONLY setting.
    • moduleDependencies: (Optional) A collection of relative file paths to files your module needs (e.g., shared models).
    • widgetExtPlugins: (Optional) An array of ConfigPlugins to run after the widget extension is created.
    type WithExpoIOSWidgetsProps = {
        src: string;
        deploymentTarget: string;
        useLiveActivities: boolean;
        frequentUpdates: boolean;
        devTeamId: string;
        targetName?: string;
        xcode?: {
            widgetBundleIdentifier?: string;
            appGroupId?: string;
            entitlements?: IOSEntitlements;
            configOverrides?: { [ attributeName: string ]: string };
            appExtAPI?: boolean;
        };
        moduleDependencies: string[];
        widgetExtPlugins: ConfigPlugin<any>[];
    }
  9. Add Gson dependency to Android app build.gradle with withGsonGradle

    main

    Use the withGsonGradle ConfigPlugin to automatically add the Gson dependency to your Android app's build.gradle file. It injects implementation("com.google.code.gson:gson:2.10.1") into the dependencies { ... } block.

    import { withGsonGradle } from '@bittingz/expo-widgets';
    
    // In your app.config.js or app.json
    export default {
      expo: {
        plugins: ['withGsonGradle'],
      },
    };
  10. Configure iOS widget entitlements with withEntitlements

    main

    Use the withEntitlements config plugin to manage iOS widget entitlements, specifically for setting up App Groups and Push Notification environments. This plugin automatically creates a .entitlements file within your widget project directory.

    To use this, you must provide options containing the mode ('development' or 'production') and optionally custom xcode.entitlements.

    Requirements:

    • You must provide an appGroupId either via options.xcode.appGroupId or by ensuring ios.bundleIdentifier is set in your Expo config (the plugin will attempt to generate one using the pattern group.${bundleIdentifier}.expowidgets).
    • If ios.bundleIdentifier is missing and no appGroupId is provided, the plugin will throw an error.
    // Example usage within an Expo Config Plugin context
    withEntitlements(config, {
      mode: 'development',
      xcode: {
        appGroupId: 'group.com.your.app.id',
        entitlements: {
          // custom entitlements here
        }
      }
    });