Files
App/lib/modular/README.md

6.0 KiB

Plugin Registry and Loader System

Overview

This module provides a plugin system for the Island app with two types of plugins:

  • Raw Plugins: Extend app abilities (services, hooks, utilities)
  • Mini-Apps: Full-screen applications loaded from network with caching

File Structure

lib/
  modular/
    interface.dart           # Plugin interfaces and metadata models
    registry.dart           # PluginRegistry class for managing plugins
  pods/
    plugin_registry.dart    # Riverpod providers for plugin management

Core Components

1. Plugin Interface (lib/modular/interface.dart)

Defines the base types for plugins:

  • Plugin: Base interface for all plugins
  • RawPlugin: For plugins that extend app functionality
  • MiniApp: For full-screen apps with entry widgets
  • PluginMetadata: Common metadata structure
  • MiniAppMetadata: Metadata for mini-apps including download URL, cache path
  • MiniAppServerInfo: Server response format for mini-app listings
  • PluginLoadResult: Enum for load operation results

2. Plugin Registry (lib/modular/registry.dart)

PluginRegistry class manages plugin lifecycle:

Raw Plugins:

  • registerRawPlugin(RawPlugin plugin)
  • unregisterRawPlugin(String id)
  • getRawPlugin(String id)

Mini-Apps:

  • loadMiniApp(MiniAppMetadata metadata, {ProgressCallback? onProgress}): Loads .evc bytecode
  • unloadMiniApp(String id): Unloads and cleans up
  • getMiniApp(String id): Get loaded mini-app
  • getMiniAppCacheDirectory(): Get cache directory path
  • clearMiniAppCache(): Clear all cached mini-apps
  • dispose(): Cleanup all resources

3. Riverpod Providers (lib/pods/plugin_registry.dart)

Providers:

  • pluginRegistryProvider: Main registry provider with keepAlive: true
  • miniAppsProvider: List of loaded mini-apps
  • rawPluginsProvider: Map of raw plugins

Methods (via ref.read(pluginRegistryProvider.notifier):

  • syncMiniAppsFromServer(apiEndpoint): Sync with server, returns MiniAppSyncResult
  • downloadMiniApp(id, downloadUrl, {ProgressCallback? onProgress}): Download and cache
  • updateMiniApp(id, {ProgressCallback? onProgress}): Update to latest version
  • enableMiniApp(id, enabled): Enable/disable mini-app
  • deleteMiniApp(id, {deleteCache}): Remove mini-app
  • clearMiniAppCache(): Clear cache
  • getMiniApp(id): Get specific mini-app
  • getLastSyncTime(): Get last sync timestamp

Storage

SharedPreferences Keys:

  • kMiniAppsRegistryKey: JSON array of MiniAppMetadata
  • kMiniAppsLastSyncKey: ISO8601 timestamp of last sync

Cache Structure:

{applicationDocuments}/mini_apps/
  ├── {app_id}.evc           # Compiled bytecode
  └── {app_id}_metadata.json  # Metadata backup (optional)

Usage Examples

Registering a Raw Plugin

class MyRawPlugin extends RawPlugin {
  @override
  PluginMetadata get metadata => PluginMetadata(
    id: 'my_plugin',
    name: 'My Plugin',
    version: '1.0.0',
    description: 'Extends app with new features',
  );
}

final container = ProviderContainer();
container.read(pluginRegistryProvider.notifier).registerRawPlugin(MyRawPlugin());

Syncing Mini-Apps from Server

final syncResult = await ref.read(pluginRegistryProvider.notifier).syncMiniAppsFromServer(
  'https://api.example.com/mini-apps',
);

if (syncResult.success) {
  print('Added: ${syncResult.added}');
  print('Updated: ${syncResult.updated}');
}

Downloading and Loading a Mini-App

await ref.read(pluginRegistryProvider.notifier).downloadMiniApp(
  'com.example.miniapp',
  'https://cdn.example.com/mini-apps/v1/app.evc',
  onProgress: (progress, message) {
    print('$progress: $message');
  },
);

Using a Loaded Mini-App Entry

class MiniAppScreen extends ConsumerWidget {
  final String appId;

  const MiniAppScreen({required this.appId, super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final miniApp = await ref.read(pluginRegistryProvider.notifier).getMiniApp(appId);
    
    if (miniApp == null) {
      return const Center(child: Text('Mini-app not loaded'));
    }

    return Scaffold(
      body: miniApp.buildEntry(),
    );
  }
}

Server API Response Format

{
  "mini_apps": [
    {
      "id": "com.example.miniapp",
      "name": "Example Mini-App",
      "version": "1.2.0",
      "description": "An example mini-application",
      "author": "Example Corp",
      "iconUrl": "https://cdn.example.com/icons/miniapp.png",
      "downloadUrl": "https://cdn.example.com/mini-apps/v1/app.evc",
      "updatedAt": "2026-01-18T00:00:00Z",
      "sizeBytes": 524288
    }
  ]
}

Mini-App Development

A mini-app should export a buildEntry() function:

// mini_app/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

Widget buildEntry() {
  return MaterialApp(
    title: 'My Mini-App',
    home: Scaffold(
      appBar: AppBar(title: const Text('My Mini-App')),
      body: const Center(
        child: Text('Hello from Mini-App!'),
      ),
    ),
  );
}

Compile to .evc using flutter_eval toolchain before uploading to server.

FlutterEval Integration

Currently, a stub Runtime class is provided in lib/modular/registry.dart for compilation. To enable full flutter_eval functionality:

  1. Resolve dependency conflicts with analyzer package
  2. Replace stub Runtime class with actual flutter_eval import
  3. Test with actual .evc bytecode files

Notes

  • Registry uses keepAlive: true to persist across app lifecycle
  • All operations are async and return appropriate results
  • Progress callbacks provide real-time feedback for download/load operations
  • Error handling includes talker logging for debugging
  • SharedPreferences persistence survives app restarts

Future Enhancements

  • Full flutter_eval integration
  • Mini-app permissions and security model
  • Version comparison and auto-update
  • Dependency resolution for mini-apps
  • Mini-app marketplace UI
  • Hot-swapping without app restart