npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

android-glance-widget-expo

v0.2.0

Published

Module to control android glance widget

Readme

Expo Android Glance Widget

⚠️ Warning

This is highly experimental and not part of any official Expo workflow.

An experimental Expo Config Plugin that enables developers to create and manage Android Glance widgets in their React Native Expo applications. This plugin automates the setup process, generates boilerplate code, and provides a convenient API for widget data management.

Features

  • 🚀 Automatic Setup: Automatically configures Android Glance dependencies and build settings
  • 📱 Widget Generation: Generates boilerplate widget code with Kotlin and Glance
  • 🔧 Configuration Management: Handles widget provider info, manifest entries, and permissions
  • 💾 Data Management: Provides a simple API for widget data storage and updates
  • 🎨 Asset Handling: Automatically copies widget assets and layouts
  • 📋 Configuration Activities: Supports widget configuration screens
  • 🔄 Live Updates: Update widgets from your React Native app
  • 🛠️ Template Generation: Creates example widget files based on your configuration

Installation

npm install android-glance-widget-expo

Configuration

1. Add the plugin to your app.json or app.config.js

{
  "expo": {
    "plugins": [
      [
        "android-glance-widget-expo",
        {
          "glanceVersion": "1.1.1", // Optional, defaults to "1.1.1"
          "kotlinVersion": "2.0.0", // Optional, defaults to "2.0.0"
          "widgets": [
            {
              "widgetClassName": "Home",
              "configurationActivity": "HomeWidgetConfigurationActivity",
              "widgetProviderInfo": {
                "description": "Home Widget",
                "updatePeriodMillis": 1000,
                "minWidth": "100dp",
                "minHeight": "100dp",
                "targetCellWidth": "100",
                "targetCellHeight": "100",
                "maxResizeWidth": "100dp",
                "maxResizeHeight": "100dp",
                "resizeMode": "horizontal",
                "widgetCategory": "home_screen"
              }
            }
          ]
        }
      ]
    ]
  }
}

2. Widget files

The plugin creates a widgets folder in your project root with starter code based on your configuration:

widgets/
├── Home.kt
├── HomeReceiver.kt
├── HomeWidgetConfigurationActivity.kt (if configured)
└── res/
    ├── layout/
    │   ├── home_initial_layout.xml
    │   └── home_preview_layout.xml
    └── drawables/
        └── your_assets.png

Note: These files are generated automatically as starter code that you can customize!

3. Run prebuild

npx expo prebuild --platform android --clean

What the Plugin Does

During prebuild, the plugin automatically:

  1. Adds Dependencies: Configures Android Glance dependencies in android/app/build.gradle
  2. Generates Starter Code: Creates working widget files in widgets/ folder for each configured widget if they don't already exist
  3. Copies Widget Code: Transfers the widget files from widgets/ to the Android project
  4. Generates Provider Info: Creates XML widget provider info files
  5. Updates Manifest: Adds widget receivers and activities to AndroidManifest.xml
  6. Copies Assets: Transfers widget assets and layouts to appropriate Android directories

Generated Widget Files

For each widget configured in your app.json, the plugin creates files in the widgets/ folder:

Widget Class (YourWidget.kt)

  • Basic Glance widget implementation
  • Scaffold with "Hello Widget" text
  • Preview composable for Android Studio

Widget Receiver (YourWidgetReceiver.kt)

  • Extends GlanceAppWidgetReceiver
  • Links to your widget class

Configuration Activity (YourWidgetConfigurationActivity.kt)

  • Optional configuration screen
  • Only created if configurationActivity is specified

Layout Files

  • your_widget_preview_layout.xml - Preview layout
  • your_widget_initial_layout.xml - Initial layout

These files are working starter code that you can customize directly in the widgets/ folder.

Sharing Data between App and Widget

Use the WidgetStorage API to communicate with your widgets:

App

import { WidgetStorage } from 'android-glance-widget-expo';

// Set data for widgets
WidgetStorage.set('message', 'Hello World!');
WidgetStorage.set('count', 42);
WidgetStorage.set('isActive', true);
WidgetStorage.set('user', { name: 'John', age: 30 });

// Get data (useful for reading back stored values)
const message = WidgetStorage.get('message') as string;
const count = WidgetStorage.get('count') as number;

Widget

The WidgetStorage API stores data in Android SharedPreferences. Access this data in your widget code using:

// In your widget receiver or widget class
val sharedPrefs = context.getSharedPreferences(
    context.packageName + ".glance_widget",
    Context.MODE_PRIVATE
)

val messageFromApp = sharedPrefs.getString("message", "No Message") ?: "No Message"
val countFromApp = sharedPrefs.getInt("count", 0)
val isActiveFromApp = sharedPrefs.getBoolean("isActive", false)

// For objects and arrays, data is stored as JSON strings
val userJson = sharedPrefs.getString("user", "{}")
// You'll need to parse JSON manually in your widget code
// Example: val user = Gson().fromJson(userJson, User::class.java)
  • Storage Location: Android SharedPreferences with name {packageName}.glance_widget
  • Data Types: Supports strings, numbers, booleans, objects (as JSON), and arrays.

Update Widget

After changing data with WidgetStorage.set(), you need to trigger a widget update to reflect the changes. Use the updateWidget() method:

// Update data
WidgetStorage.set('message', 'Updated message!');
WidgetStorage.set('count', 42);

// Trigger widget update
WidgetStorage.updateWidget('HomeReceiver');

How Widget Updates Work

The update process follows this flow:

  1. JavaScript calls updateWidget(): Pass the receiver class name (e.g., 'HomeReceiver')
  2. Module sends broadcast: The native module constructs the full class name and sends an Android broadcast intent
  3. Widget receiver responds: Your widget receiver catches the broadcast and updates the widget

Widget Receiver Implementation

Your widget receiver should handle the update broadcast like this:

class HomeReceiver : GlanceAppWidgetReceiver() {
    override val glanceAppWidget: GlanceAppWidget = Home()

    override fun onReceive(context: Context, intent: Intent) {
        super.onReceive(context, intent)

        if (intent.action == AppWidgetManager.ACTION_APPWIDGET_UPDATE) {
            CoroutineScope(Dispatchers.IO).launch {
                val glanceIds = GlanceAppWidgetManager(context).getGlanceIds(Home::class.java)

                // Read updated data from SharedPreferences
                val sharedPrefs = context.getSharedPreferences(
                    context.packageName + ".glance_widget",
                    Context.MODE_PRIVATE
                )
                val messageFromApp = sharedPrefs.getString("message", "No Message") ?: "No Message"
                val countFromApp = sharedPrefs.getInt("count", 0)

                glanceIds.forEach { id ->
                    updateAppWidgetState(
                        context = context,
                        glanceId = id
                    ) {
                        // Update widget state with new data
                        it[stringPreferencesKey("message")] = messageFromApp
                        it[intPreferencesKey("count")] = countFromApp
                    }
                    // Trigger widget UI update
                    glanceAppWidget.update(context, id)
                }
            }
        }
    }
}

Important Notes

  • Receiver class name: Use just the class name (e.g., 'HomeReceiver'), not the full package name
  • Automatic broadcast: The module automatically constructs the full class name: {packageName}.widgets.{receiverClassName}
  • Async updates: Widget updates run in a coroutine to avoid blocking the main thread
  • Multiple instances: The receiver updates all instances of the widget if multiple are placed on the home screen

API Reference

WidgetStorage Methods

| Method | Description | |--------|-------------| | set(key: string, value: any) | Store data (string, number, boolean, object, array) | | get(key: string) | Retrieve stored data | | remove(key: string) | Remove a key | | has(key: string) | Check if key exists | | clear() | Clear all data | | getAllKeys() | Get all keys | | updateWidget(receiverClassName: string) | Trigger widget update |

Configuration Options

Plugin Configuration

| Option | Type | Required | Default | Description | |--------|------|----------|---------|-------------| | glanceVersion | string | No | "1.1.1" | AndroidX Glance library version | | kotlinVersion | string | No | "2.0.0" | Kotlin compiler version | | widgets | Widget[] | Yes | - | Array of widget configurations |

Widget Configuration

| Option | Type | Description | |--------|------|-------------| | widgetClassName | string | Name of the widget class | | configurationActivity | string | Optional configuration activity | | widgetProviderInfo | WidgetProviderInfo | Widget provider settings |

Supported WidgetProviderInfo Options

| Option | Type | Description | |--------|------|-------------| | minWidth | string | Minimum width (e.g., "100dp") (Added in API level 3) | | minHeight | string | Minimum height (e.g., "100dp") (Added in API level 3) | | updatePeriodMillis | number | Update interval in milliseconds (Added in API level 3) | | previewImageFileName | string | Preview image file name (must be present in widgets/res/drawables/) (Added in API level 11) | | resizeMode | "horizontal" | "vertical" | "none" | "horizontal\|vertical" | Resize mode options (Added in API level 12) | | minResizeWidth | string | Minimum resize width (Added in API level 14) | | minResizeHeight | string | Minimum resize height (Added in API level 14) | | widgetCategory | "home_screen" | "keyguard" | Widget category options (Added in API level 17) | | widgetFeatures | "configuration_optional" | "reconfigurable" | "hide_from_picker" | Widget features options (Added in API level 28) | | description | string | Widget description (Added in API level 31) | | targetCellWidth | string | Target cell width (Added in API level 31) | | targetCellHeight | string | Target cell height (Added in API level 31) | | maxResizeWidth | string | Maximum resize width (Added in API level 31) | | maxResizeHeight | string | Maximum resize height (Added in API level 31) |

Note: The plugin automatically creates API-level specific XML files to ensure backward compatibility. For example, if you use description (API 31), the plugin will create separate XML files for different Android versions, ensuring your widget works on older devices without newer features.

For more detailed information about these widget provider options, refer to the Android AppWidgetProviderInfo documentation.

API Reference: AppWidgetProviderInfo

Acknowledgments