Dynamic Widgets

Experimental Feature

Dynamic widgets are experimental. Please report any issues you find.

Dynamic Widgets let your widget react to the current device state on Android. Declare them in app.json with a stable id and an explicit entry, then default-export the widget from that file.

Your widget can react to:

  • env.widgetFamily
  • env.colorScheme
  • env.locale
  • env.configuration
  • AndroidDynamicColors tokens, which resolve to the current Material You palette natively

When you change app.json, run Expo Prebuild or Voltra Apply so the updated widget configuration is available on device. If you change only the widget JS, reopen the app in development and the widget updates automatically.

How to use it

  1. Add an Android widget declaration to app.json with an id, an entry, and any widget metadata you need.
  2. Default-export the widget function or component from the module named by entry.
  3. Use initialStatePath if you want a pre-rendered first view.
  4. Re-run Expo Prebuild or Voltra Apply after updating app.json.
  5. Keep Android and iOS widget declarations separate; the same id can exist on both platforms because each platform is configured separately.
import { AndroidDynamicColors, VoltraAndroid, type WidgetEnvironment } from '@use-voltra/android'

export default function WeatherWidget(_props: object, env: WidgetEnvironment = {} as WidgetEnvironment) {

  const renderedAt = env.date.toLocaleTimeString('en-US', {
    hour: '2-digit',
    minute: '2-digit',
  })

  return (
    <VoltraAndroid.Column
      style={{ width: '100%', height: '100%', padding: 16, backgroundColor: AndroidDynamicColors.surface }}
      verticalAlignment="center-vertically"
    >
      <VoltraAndroid.Text style={{ color: AndroidDynamicColors.onSurface, fontSize: 18 }}>
        Weather Widget
      </VoltraAndroid.Text>
      <VoltraAndroid.Text style={{ color: AndroidDynamicColors.onSurfaceVariant }}>
        Size: {env.widgetFamily}
      </VoltraAndroid.Text>
      <VoltraAndroid.Text style={{ color: AndroidDynamicColors.onSurfaceVariant }}>
        Scheme: {env.colorScheme ?? 'light'}
      </VoltraAndroid.Text>
      <VoltraAndroid.Text style={{ color: AndroidDynamicColors.onSurfaceVariant }}>
        Rendered: {renderedAt}
      </VoltraAndroid.Text>
    </VoltraAndroid.Column>
  )
}

Example plugin config:

{
  "expo": {
    "plugins": [
      [
        "@use-voltra/android-client",
        {
          "widgets": [
            {
              "id": "weather_widget",
              "entry": "./widgets/android/weather-widget.tsx",
              "displayName": "Weather Widget",
              "description": "A Dynamic Widget that reacts to live device state",
              "targetCellWidth": 2,
              "targetCellHeight": 2,
              "initialStatePath": "./widgets/android/weather-widget.tsx"
            }
          ]
        }
      ]
    ]
  }
}

If you need user-controlled values, add appIntent.parameters and update them in-app with setWidgetConfiguration(widgetId, key, value).

Notes

  • There is no export field in app.json for Dynamic Widgets.
  • The default-exported function or component name does not need to match the widget id.
  • Use a real device to verify release rendering.
  • initialStatePath gives the widget a pre-rendered first view.

Need React or React Native expertise you can count on?