react-native-android-widget

repository·master·Indexed 21 days ago

https://github.com/saleksovski/react-native-android-widget

A library for building and managing Android Home Screen Widgets using React Native. It provides APIs to register widget task handlers for lifecycle events (WIDGET_ADDED, WIDGET_UPDATE, WIDGET_RESIZED, WIDGET_DELETED, WIDGET_CLICK), define widget configuration screens via registerWidgetConfigurationScreen, and trigger widget updates using requestWidgetUpdate and requestWidgetUpdateById. The library supports both standard and New Architecture (Turbo Modules) and includes specific integration guides for Expo apps.

Tokens
29.1K
Snippets
79
Records
106
Agent score
74%

What's inside react-native-android-widget

  1. Design principles for Android Widgets

    master

    When building widgets with react-native-android-widget, you must follow specific constraints because widgets are rendered by the Android system, not the React Native runtime.

    Core Constraints

    • No Hooks: Widget components must not use any React hooks (e.g., useState, useEffect).
    • Primitive-only UI: Widgets must be functions that return only the library's provided primitives. You cannot use standard React Native components like View, Text, or Image inside a widget.
    • Synchronous Execution: You can use standard JSX, conditions, and loops (for, map), but the component function cannot be async.

    Supported Primitives

    The library provides the following primitives to build your UI:

    • FlexWidget
    • OverlapWidget
    • ListWidget
    • TextWidget
    • ImageWidget
    • IconWidget
    • SvgWidget
  2. Understand how React Native views are rendered to widgets

    master

    This library does not render React Native views directly to the Android widget. Instead, it follows a two-step process:

    1. It renders the React Native views into an image.
    2. It displays that image within the Android widget.

    Because of this approach, the quality and fit of the widget depend heavily on knowing the exact size of the widget to ensure the generated image matches the widget dimensions correctly.

  3. Implement Dark Mode support for widgets

    master

    The library allows you to provide separate React element trees for light and dark system themes. When rendering or updating a widget, you pass an object containing light and/or dark keys. The Android system automatically selects the appropriate tree based on the current system theme. If the dark key is omitted, the light variant serves as the fallback for both modes.

    // Example of the object structure passed to renderWidget
    {
      light: <MyWidget theme="light" />,
      dark: <MyWidget theme="dark" />
    }
  4. Use ListWidget to create scrollable lists

    master

    The ListWidget is a container component used to display a scrollable list of multiple items.

    Critical Constraint: Each individual list item must have a height that is equal to or less than the total height of the ListWidget container to ensure proper rendering and scrolling behavior.

    import {
      ListWidget,
      FlexWidget,
      TextWidget,
    } from 'react-native-android-widget';
    
    export function MyWidget() {
      return (
        <ListWidget
          style={{
            height: 'match_parent',
            width: 'match_parent',
            backgroundColor: '#1F3529',
          }}
        >
          {Array.from({ length: 15 }).map((_, i) => (
            <FlexWidget
              key={i}
              style={{
                width: 'match_parent',
                alignItems: 'center',
                flexDirection: 'row',
                justifyContent: 'center',
                padding: 8,
              }}
              clickAction="OPEN_URI"
              clickActionData={{
                uri: `androidwidgetexample://list/list-demo/${i + 1}`,
              }}
            >
              <TextWidget text={`React Native Android Widget Release 0.${i + 1}`} />
            </FlexWidget>
          ))}
        </ListWidget>
      );
    }
  5. Register the widget task handler in Expo with Expo Router

    master

    If your Expo project uses Expo Router, you must redirect the entry point to a custom file to allow widget registration.

    1. Update the main field in package.json from expo-router/entry to index.ts.
    2. Create an index.ts file that imports expo-router/entry and then calls registerWidgetTaskHandler with your handler function.
    // package.json
    {
      "name": "my-expo-app",
      "main": "index.ts"
    }
    // index.ts
    import 'expo-router/entry';
    import { widgetTaskHandler } from './widget-task-handler';
    
    registerWidgetTaskHandler(widgetTaskHandler);
  6. Schedule automatic widget updates with updatePeriodMillis

    master

    You can schedule periodic updates using the updatePeriodMillis option (or android:updatePeriodMillis in native Android configurations).

    When a scheduled update occurs, the widgetTaskHandler function is triggered with widgetAction set to 'WIDGET_UPDATE'. You should handle this case by calling props.renderWidget() with your widget component.

    Important Limitation: Android restricts these scheduled updates; they will not be delivered more than once every 30 minutes.

    export async function widgetTaskHandler(props: WidgetTaskHandlerProps) {
      switch (props.widgetAction) {
        case 'WIDGET_UPDATE':
          props.renderWidget(<Widget />);
          break;
        // ... other cases
      }
    }
  7. Handle widget clicks in the task handler

    master

    When a user clicks a primitive, the registerWidgetTaskHandler is triggered with a widgetAction of 'WIDGET_CLICK'. You can identify which specific action was triggered by checking props.clickAction against the string you provided in the widget definition. Any additional data passed via clickActionData is available in props.clickActionData.

    export async function widgetTaskHandler(props: WidgetTaskHandlerProps) {
      switch (props.widgetAction) {
        // ... other cases
    
        case 'WIDGET_CLICK':
          if (props.clickAction === 'MY_ACTION') {
            // Do stuff when primitive with `clickAction="MY_ACTION"` is clicked
            // props.clickActionData === { id: 0 }
          }
          break;
    
        default:
          break;
      }
    }
  8. Configure AndroidManifest.xml for widgets

    master

    To make your widget functional, you must register both a shared collection service and a specific receiver in your AndroidManifest.xml.

    1. Add RNWidgetCollectionService

    If you are using ListWidget, you must add this service under the <application> section. This service is shared across all widgets and only needs to be added once.

    2. Add Widget Receiver

    Add a <receiver> for your specific widget.

    • android:name: Must match the package path of your RNWidgetProvider class (relative to the app's package name).
    • android:label: The name displayed in the widget picker.
    • meta-data: Must point to your widget provider XML file via android:resource.
    <manifest ...>
      <application ...>
    
          <!-- 1. Shared Service for ListWidgets -->
          <service
              android:name="com.reactnativeandroidwidget.RNWidgetCollectionService"
              android:permission="android.permission.BIND_REMOTEVIEWS" />
    
          <!-- 2. Specific Widget Receiver -->
          <receiver
              android:name=".widget.Hello"
              android:exported="false"
              android:label="My Hello Widget">
              <intent-filter>
                  <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                  <action android:name="com.yourapppackage.WIDGET_CLICK" />
              </intent-filter>
              <meta-data
                  android:name="android.appwidget.provider"
                  android:resource="@xml/widgetprovider_hello" />
          </receiver>
    
      </application>
    </manifest>
  9. Deploy the documentation website

    master

    You can deploy the documentation website using either SSH or GitHub Pages.

    Using SSH: Set the USE_SSH environment variable to true.

    Using GitHub Pages: Provide your GitHub username via the GIT_USER environment variable. This will build the site and push it to the gh-pages branch.

    # Deploy using SSH
    $ USE_SSH=true yarn deploy
    
    # Deploy to GitHub Pages
    $ GIT_USER=<Your GitHub username> yarn deploy
  10. Configure custom fonts for TextWidget in Expo

    master

    In Expo, custom fonts are managed via the react-native-android-widget config plugin.

    1. Place your font files in your assets directory (e.g., assets/fonts/Inter.ttf).
    2. Update your app.config.ts (or app.json) to include the font paths in the react-native-android-widget plugin configuration.
    export default ({ config }: ConfigContext): ExpoConfig => ({
      ...config,
      name: 'My Expo App Name',
      plugins: [
        ['react-native-android-widget', {
          fonts: ['./assets/fonts/Inter.ttf'],
          widgets: [...],
        }]
      ],
    });
  11. Create a widget task handler function

    master

    A task handler is a function responsible for managing the lifecycle of your Android widgets, including adding them to the home screen and handling user interactions like clicks.

    To implement one, create an asynchronous function that accepts WidgetTaskHandlerProps. Inside this function, you should use a switch statement on props.widgetAction to respond to different lifecycle events.

    Common widgetAction values include:

    • WIDGET_ADDED: Triggered when a new widget is placed on the home screen. Use props.renderWidget(<YourComponent />) here.
    • WIDGET_UPDATE: Triggered when the widget needs to be updated.
    • WIDGET_RESIZED: Triggered when the widget dimensions change.
    • WIDGET_DELETED: Triggered when the widget is removed.
    • WIDGET_CLICK: Triggered when the user interacts with the widget.

    You can use props.widgetInfo.widgetName to identify which specific widget is being handled, which is useful if your app supports multiple distinct widget types.

    import React from 'react';
    import type { WidgetTaskHandlerProps } from 'react-native-android-widget';
    import { HelloWidget } from './HelloWidget';
    
    const nameToWidget = {
      Hello: HelloWidget,
    };
    
    export async function widgetTaskHandler(props: WidgetTaskHandlerProps) {
      const widgetInfo = props.widgetInfo;
      const Widget = nameToWidget[widgetInfo.widgetName as keyof typeof nameToWidget];
    
      switch (props.widgetAction) {
        case 'WIDGET_ADDED':
          props.renderWidget(<Widget />);
          break;
        case 'WIDGET_UPDATE':
          break;
        case 'WIDGET_RESIZED':
          break;
        case 'WIDGET_DELETED':
          break;
        case 'WIDGET_CLICK':
          break;
        default:
          break;
      }
    }