486 lines
No EOL
14 KiB
Markdown
486 lines
No EOL
14 KiB
Markdown
# Flutter WebView Bridge Package - План реализации
|
||
|
||
## 📦 Обзор пакета
|
||
|
||
**Название**: `flutter_webview_bridge`
|
||
**Цель**: Создать Flutter пакет для двустороннего взаимодействия между Flutter-хостом и Flutter Web-приложениями через WebView
|
||
|
||
## 🏗️ Архитектура пакета
|
||
|
||
```
|
||
flutter_webview_bridge/
|
||
├── lib/
|
||
│ ├── src/
|
||
│ │ ├── bridge/
|
||
│ │ │ ├── webview_bridge.dart # Основной класс моста
|
||
│ │ │ ├── message_handler.dart # Обработка сообщений
|
||
│ │ │ ├── message_serializer.dart # Сериализация сообщений
|
||
│ │ │ └── callback_manager.dart # Управление callback'ами
|
||
│ │ ├── web/
|
||
│ │ │ ├── web_bridge.dart # Flutter Web сторона
|
||
│ │ │ └── web_utils.dart # Утилиты для Web
|
||
│ │ ├── host/
|
||
│ │ │ ├── host_bridge.dart # Flutter Host сторона
|
||
│ │ │ └── webview_controller.dart # Контроллер WebView
|
||
│ │ └── models/
|
||
│ │ ├── bridge_message.dart # Модель сообщения
|
||
│ │ └── bridge_config.dart # Конфигурация
|
||
│ ├── flutter_webview_bridge.dart # Основной экспорт
|
||
│ └── bridge_webview.dart # WebView виджет
|
||
├── web/
|
||
│ └── bridge.js # JavaScript мост
|
||
├── example/
|
||
│ ├── lib/
|
||
│ │ ├── main.dart # Пример Flutter Host
|
||
│ │ └── web_app.dart # Пример Flutter Web
|
||
│ └── web/
|
||
│ └── index.html # HTML оболочка
|
||
└── test/
|
||
└── bridge_test.dart # Тесты
|
||
```
|
||
|
||
## 📋 Структура сообщений
|
||
|
||
### Модель сообщения
|
||
```dart
|
||
class BridgeMessage {
|
||
final String type; // Тип события/запроса
|
||
final String? id; // UUID для request-response
|
||
final Map<String, dynamic>? data; // Полезная нагрузка
|
||
final DateTime? timestamp; // Временная метка
|
||
final String? source; // Источник (host/web)
|
||
}
|
||
```
|
||
|
||
### Типы сообщений
|
||
- **Request-Response**: `id` обязателен, ожидается ответ
|
||
- **Event-Notify**: `id` отсутствует, одностороннее уведомление
|
||
- **Stream-Subscription**: для подписки на обновления
|
||
|
||
## 🔧 Этапы разработки
|
||
|
||
### Этап 1: Базовая инфраструктура (1-2 дня)
|
||
|
||
#### 1.1 Создание структуры пакета
|
||
```bash
|
||
flutter create --template=package flutter_webview_bridge
|
||
```
|
||
|
||
#### 1.2 Основные модели
|
||
- `BridgeMessage` - модель сообщения
|
||
- `BridgeConfig` - конфигурация моста
|
||
- `BridgeException` - исключения моста
|
||
|
||
#### 1.3 Зависимости в pubspec.yaml
|
||
```yaml
|
||
dependencies:
|
||
flutter:
|
||
sdk: flutter
|
||
flutter_inappwebview: ^6.0.0
|
||
uuid: ^4.0.0
|
||
js: ^0.6.7
|
||
|
||
dev_dependencies:
|
||
flutter_test:
|
||
sdk: flutter
|
||
flutter_lints: ^3.0.0
|
||
```
|
||
|
||
### Этап 2: Flutter Host сторона (2-3 дня)
|
||
|
||
#### 2.1 WebView контроллер
|
||
```dart
|
||
class BridgeWebViewController {
|
||
InAppWebViewController? _webViewController;
|
||
final Map<String, Completer<BridgeMessage>> _pendingCallbacks = {};
|
||
final StreamController<BridgeMessage> _messageStream = StreamController.broadcast();
|
||
|
||
Future<void> initialize(String url);
|
||
Future<void> sendMessage(BridgeMessage message);
|
||
Stream<BridgeMessage> get messageStream;
|
||
}
|
||
```
|
||
|
||
#### 2.2 JavaScript Handler
|
||
```dart
|
||
void _setupJavaScriptHandler() {
|
||
_webViewController?.addJavaScriptHandler(
|
||
callbackName: 'fromWebApp',
|
||
callback: (args) {
|
||
final message = BridgeMessage.fromJson(args[0]);
|
||
_handleIncomingMessage(message);
|
||
},
|
||
);
|
||
}
|
||
```
|
||
|
||
#### 2.3 Отправка сообщений в WebView
|
||
```dart
|
||
Future<void> _sendToWebView(BridgeMessage message) async {
|
||
final jsCode = '''
|
||
window.dispatchEvent(new CustomEvent("fromFlutterHost", {
|
||
detail: ${jsonEncode(message.toJson())}
|
||
}));
|
||
''';
|
||
await _webViewController?.evaluateJavascript(source: jsCode);
|
||
}
|
||
```
|
||
|
||
### Этап 3: JavaScript Bridge (1-2 дня)
|
||
|
||
#### 3.1 Создание bridge.js
|
||
```javascript
|
||
// Подписка на события от Flutter Host
|
||
window.addEventListener("fromFlutterHost", (e) => {
|
||
if (window.flutterApp && window.flutterApp.receiveFromHost) {
|
||
window.flutterApp.receiveFromHost(e.detail);
|
||
}
|
||
});
|
||
|
||
// Отправка сообщений в Flutter Host
|
||
window.sendToFlutterHost = function(message) {
|
||
if (window.flutter_inappwebview) {
|
||
window.flutter_inappwebview.callHandler('fromWebApp', message);
|
||
}
|
||
};
|
||
```
|
||
|
||
#### 3.2 Интеграция в HTML
|
||
```html
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<script src="bridge.js"></script>
|
||
</head>
|
||
<body>
|
||
<div id="flutter_target"></div>
|
||
<script src="main.dart.js" type="application/javascript"></script>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
### Этап 4: Flutter Web сторона (2-3 дня)
|
||
|
||
#### 4.1 Web Bridge класс
|
||
```dart
|
||
class WebBridge {
|
||
static final WebBridge _instance = WebBridge._internal();
|
||
factory WebBridge() => _instance;
|
||
WebBridge._internal();
|
||
|
||
final Map<String, Completer<BridgeMessage>> _pendingCallbacks = {};
|
||
final StreamController<BridgeMessage> _messageStream = StreamController.broadcast();
|
||
|
||
void initialize() {
|
||
// Регистрация глобального объекта
|
||
setProperty(js.context, 'flutterApp', {
|
||
'receiveFromHost': allowInterop(_handleMessageFromHost),
|
||
});
|
||
}
|
||
|
||
Future<BridgeMessage> sendMessage(BridgeMessage message) async {
|
||
final completer = Completer<BridgeMessage>();
|
||
if (message.id != null) {
|
||
_pendingCallbacks[message.id!] = completer;
|
||
}
|
||
|
||
callMethod(js.context, 'sendToFlutterHost', [message.toJson()]);
|
||
return completer.future;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 4.2 Обработка сообщений
|
||
```dart
|
||
void _handleMessageFromHost(dynamic data) {
|
||
final message = BridgeMessage.fromJson(data);
|
||
|
||
// Проверяем, есть ли pending callback
|
||
if (message.id != null && _pendingCallbacks.containsKey(message.id)) {
|
||
_pendingCallbacks[message.id]!.complete(message);
|
||
_pendingCallbacks.remove(message.id);
|
||
} else {
|
||
// Отправляем в стрим для event-notify сообщений
|
||
_messageStream.add(message);
|
||
}
|
||
}
|
||
```
|
||
|
||
### Этап 5: Основной API пакета (1-2 дня)
|
||
|
||
#### 5.1 BridgeWebView виджет
|
||
```dart
|
||
class BridgeWebView extends StatefulWidget {
|
||
final String url;
|
||
final BridgeConfig? config;
|
||
final Function(BridgeMessage)? onMessage;
|
||
final Widget? loadingWidget;
|
||
|
||
const BridgeWebView({
|
||
required this.url,
|
||
this.config,
|
||
this.onMessage,
|
||
this.loadingWidget,
|
||
super.key,
|
||
});
|
||
|
||
@override
|
||
State<BridgeWebView> createState() => _BridgeWebViewState();
|
||
}
|
||
```
|
||
|
||
#### 5.2 Основной экспорт
|
||
```dart
|
||
// flutter_webview_bridge.dart
|
||
export 'src/bridge/webview_bridge.dart';
|
||
export 'src/models/bridge_message.dart';
|
||
export 'src/models/bridge_config.dart';
|
||
export 'bridge_webview.dart';
|
||
```
|
||
|
||
### Этап 6: Утилиты и хелперы (1 день)
|
||
|
||
#### 6.1 Message Serializer
|
||
```dart
|
||
class MessageSerializer {
|
||
static Map<String, dynamic> toJson(BridgeMessage message) {
|
||
return {
|
||
if (message.type != null) 'type': message.type,
|
||
if (message.id != null) 'id': message.id,
|
||
if (message.data != null) 'data': message.data,
|
||
'timestamp': message.timestamp.toIso8601String(),
|
||
'source': message.source,
|
||
};
|
||
}
|
||
|
||
static BridgeMessage fromJson(Map<String, dynamic> json) {
|
||
return BridgeMessage(
|
||
type: json['type'],
|
||
id: json['id'],
|
||
data: json['data'],
|
||
timestamp: DateTime.parse(json['timestamp']),
|
||
source: json['source'],
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 6.2 Callback Manager
|
||
```dart
|
||
class CallbackManager {
|
||
final Map<String, Completer<BridgeMessage>> _callbacks = {};
|
||
final Duration _timeout;
|
||
|
||
CallbackManager({this._timeout = const Duration(seconds: 30)});
|
||
|
||
String registerCallback(Completer<BridgeMessage> completer) {
|
||
final id = const Uuid().v4();
|
||
_callbacks[id] = completer;
|
||
|
||
// Автоматический таймаут
|
||
Timer(_timeout, () {
|
||
if (_callbacks.containsKey(id)) {
|
||
_callbacks[id]!.completeError(
|
||
BridgeException('Request timeout: $id')
|
||
);
|
||
_callbacks.remove(id);
|
||
}
|
||
});
|
||
|
||
return id;
|
||
}
|
||
|
||
void completeCallback(String id, BridgeMessage response) {
|
||
if (_callbacks.containsKey(id)) {
|
||
_callbacks[id]!.complete(response);
|
||
_callbacks.remove(id);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Этап 7: Примеры использования (1-2 дня)
|
||
|
||
#### 7.1 Flutter Host пример
|
||
```dart
|
||
class MyHomePage extends StatefulWidget {
|
||
@override
|
||
_MyHomePageState createState() => _MyHomePageState();
|
||
}
|
||
|
||
class _MyHomePageState extends State<MyHomePage> {
|
||
BridgeWebViewController? _bridgeController;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
appBar: AppBar(title: Text('Flutter WebView Bridge')),
|
||
body: BridgeWebView(
|
||
url: 'http://localhost:8080',
|
||
onMessage: _handleMessage,
|
||
onBridgeReady: (controller) {
|
||
_bridgeController = controller;
|
||
},
|
||
),
|
||
);
|
||
}
|
||
|
||
void _handleMessage(BridgeMessage message) {
|
||
switch (message.type) {
|
||
case 'getAppVersion':
|
||
_sendResponse(message.id!, {'version': '1.0.0'});
|
||
break;
|
||
case 'vibrate':
|
||
HapticFeedback.vibrate();
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 7.2 Flutter Web пример
|
||
```dart
|
||
void main() {
|
||
WebBridge().initialize();
|
||
runApp(MyApp());
|
||
}
|
||
|
||
class MyApp extends StatelessWidget {
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return MaterialApp(
|
||
home: MyHomePage(),
|
||
);
|
||
}
|
||
}
|
||
|
||
class MyHomePage extends StatefulWidget {
|
||
@override
|
||
_MyHomePageState createState() => _MyHomePageState();
|
||
}
|
||
|
||
class _MyHomePageState extends State<MyHomePage> {
|
||
final _bridge = WebBridge();
|
||
|
||
Future<void> _getAppVersion() async {
|
||
try {
|
||
final response = await _bridge.sendMessage(
|
||
BridgeMessage(
|
||
type: 'getAppVersion',
|
||
id: const Uuid().v4(),
|
||
source: 'web',
|
||
),
|
||
);
|
||
print('App version: ${response.data?['version']}');
|
||
} catch (e) {
|
||
print('Error: $e');
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
appBar: AppBar(title: Text('Flutter Web App')),
|
||
body: Center(
|
||
child: ElevatedButton(
|
||
onPressed: _getAppVersion,
|
||
child: Text('Get App Version'),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
```
|
||
|
||
### Этап 8: Тестирование (2-3 дня)
|
||
|
||
#### 8.1 Unit тесты
|
||
```dart
|
||
// test/bridge_test.dart
|
||
void main() {
|
||
group('BridgeMessage', () {
|
||
test('should serialize and deserialize correctly', () {
|
||
final message = BridgeMessage(
|
||
type: 'test',
|
||
id: '123',
|
||
data: {'key': 'value'},
|
||
source: 'host',
|
||
);
|
||
|
||
final json = MessageSerializer.toJson(message);
|
||
final deserialized = MessageSerializer.fromJson(json);
|
||
|
||
expect(deserialized.type, equals(message.type));
|
||
expect(deserialized.id, equals(message.id));
|
||
expect(deserialized.data, equals(message.data));
|
||
});
|
||
});
|
||
}
|
||
```
|
||
|
||
#### 8.2 Integration тесты
|
||
- Тест полного цикла сообщений
|
||
- Тест таймаутов
|
||
- Тест обработки ошибок
|
||
|
||
### Этап 9: Документация (1-2 дня)
|
||
|
||
#### 9.1 README.md
|
||
- Описание пакета
|
||
- Установка и настройка
|
||
- Примеры использования
|
||
- API документация
|
||
|
||
#### 9.2 API документация
|
||
- Документирование всех публичных классов и методов
|
||
- Примеры кода
|
||
- Troubleshooting
|
||
|
||
## 🚀 План релиза
|
||
|
||
### v0.1.0 - Alpha
|
||
- Базовая функциональность
|
||
- Request-response модель
|
||
- Простые примеры
|
||
|
||
### v0.2.0 - Beta
|
||
- Event-notify модель
|
||
- Стримы для подписок
|
||
- Улучшенная обработка ошибок
|
||
|
||
### v1.0.0 - Stable
|
||
- Полная документация
|
||
- Тесты покрытие >80%
|
||
- Примеры для всех сценариев
|
||
|
||
## 📝 Чек-лист готовности
|
||
|
||
- [ ] Структура пакета создана
|
||
- [ ] Основные модели реализованы
|
||
- [ ] Flutter Host сторона работает
|
||
- [ ] JavaScript bridge интегрирован
|
||
- [ ] Flutter Web сторона работает
|
||
- [ ] Примеры созданы и работают
|
||
- [ ] Тесты написаны и проходят
|
||
- [ ] Документация готова
|
||
- [ ] pubspec.yaml настроен
|
||
- [ ] README.md написан
|
||
|
||
## 🔧 Технические детали
|
||
|
||
### Зависимости
|
||
- `flutter_inappwebview`: для WebView функциональности
|
||
- `uuid`: для генерации уникальных ID
|
||
- `js`: для взаимодействия с JavaScript в Flutter Web
|
||
|
||
### Поддерживаемые платформы
|
||
- ✅ Android
|
||
- ✅ iOS
|
||
- ✅ Web (Flutter Web в WebView)
|
||
- ❌ Desktop (не планируется)
|
||
- ❌ Linux (не планируется)
|
||
|
||
### Производительность
|
||
- Минимальная задержка сообщений
|
||
- Автоматическая очистка callback'ов
|
||
- Таймауты для предотвращения утечек памяти |