新增网络可视化请求工具

This commit is contained in:
2026-07-22 09:24:48 +08:00
parent 4ff93edaab
commit d6e80df5bf
230 changed files with 92710 additions and 51 deletions
+19 -1
View File
@@ -3,10 +3,14 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_ume/flutter_ume.dart';
import 'package:flutter_ume_kit_device/components/device_info/device_info_panel.dart';
import 'package:flutter_ume_kit_dio/flutter_ume_kit_dio.dart';
import 'package:recording_tool/app/app.dart'; import 'package:recording_tool/app/app.dart';
import 'package:recording_tool/app/config/app_config.dart'; import 'package:recording_tool/app/config/app_config.dart';
import 'package:recording_tool/core/cache/app_storage.dart'; import 'package:recording_tool/core/cache/app_storage.dart';
import 'package:recording_tool/core/logging/app_logger.dart'; import 'package:recording_tool/core/logging/app_logger.dart';
import 'package:recording_tool/core/network/app_dio.dart';
import 'package:recording_tool/core/platform/app_platform_info.dart'; import 'package:recording_tool/core/platform/app_platform_info.dart';
class AppBootstrapper { class AppBootstrapper {
@@ -25,7 +29,21 @@ class AppBootstrapper {
AppLogger.debug('App started in ${AppConfig.current.environment.name}'); AppLogger.debug('App started in ${AppConfig.current.environment.name}');
runApp(const ProviderScope(child: FlutterTemplateApp())); // 注册 UME 调试插件(DioInspector 需与业务共用同一 Dio 实例)
PluginManager.instance
..register(DioInspector(dio: AppDio.instance))
..register(DeviceInfoPanel());
runApp(
UMEWidget(
enable: true,
child: Stack(
alignment: Alignment.center,
children: [const ProviderScope(child: FlutterTemplateApp())],
),
),
);
// runApp(const ProviderScope(child: FlutterTemplateApp()));
// Load native package metadata after the first frame can render. // Load native package metadata after the first frame can render.
// Awaiting MethodChannel calls before runApp() can stall the Android // Awaiting MethodChannel calls before runApp() can stall the Android
+21
View File
@@ -0,0 +1,21 @@
import 'package:dio/dio.dart';
import 'package:recording_tool/app/config/app_config.dart';
/// 应用全局 Dio 单例,供业务请求与调试插件共用。
class AppDio {
AppDio._();
static Dio? _instance;
static Dio get instance {
return _instance ??= Dio(
BaseOptions(
baseUrl: AppConfig.current.baseUrl,
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
sendTimeout: const Duration(seconds: 30),
responseType: ResponseType.json,
),
);
}
}
+21 -21
View File
@@ -2,36 +2,36 @@ import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:recording_tool/app/config/app_config.dart'; import 'package:recording_tool/app/config/app_config.dart';
import 'package:recording_tool/core/network/api_client.dart'; import 'package:recording_tool/core/network/api_client.dart';
import 'package:recording_tool/core/network/app_dio.dart';
import 'package:recording_tool/core/network/header_interceptor.dart'; import 'package:recording_tool/core/network/header_interceptor.dart';
import 'package:recording_tool/core/network/offline_queue/offline_queue_interceptor.dart'; import 'package:recording_tool/core/network/offline_queue/offline_queue_interceptor.dart';
import 'package:recording_tool/core/network/providers/network_providers.dart'; import 'package:recording_tool/core/network/providers/network_providers.dart';
import 'package:recording_tool/core/network/providers/offline_queue_providers.dart'; import 'package:recording_tool/core/network/providers/offline_queue_providers.dart';
bool _dioConfigured = false;
final dioProvider = Provider<Dio>((ref) { final dioProvider = Provider<Dio>((ref) {
final dio = Dio( final dio = AppDio.instance;
BaseOptions(
baseUrl: AppConfig.current.baseUrl,
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
sendTimeout: const Duration(seconds: 30),
responseType: ResponseType.json,
),
);
dio.interceptors.add(HeaderInterceptor()); if (!_dioConfigured) {
_dioConfigured = true;
dio.interceptors.add(HeaderInterceptor());
final monitor = ref.watch(networkMonitorProvider); final monitor = ref.read(networkMonitorProvider);
final queueManager = ref.watch(offlineQueueManagerProvider); final queueManager = ref.read(offlineQueueManagerProvider);
dio.interceptors.add( dio.interceptors.add(
OfflineQueueInterceptor( OfflineQueueInterceptor(
monitor: monitor, monitor: monitor,
manager: queueManager, manager: queueManager,
enabled: false, enabled: false,
), ),
); );
if (AppConfig.current.enableNetworkLog) { if (AppConfig.current.enableNetworkLog) {
dio.interceptors.add(LogInterceptor(requestBody: true, responseBody: true)); dio.interceptors.add(
LogInterceptor(requestBody: true, responseBody: true),
);
}
} }
return dio; return dio;
+28 -29
View File
@@ -87,37 +87,36 @@ class _AuthPageWidgetState extends ConsumerState<AuthPageWidget> {
], ],
), ),
), ),
SizedBox(height: 20.h), ..._buildTestArea(authState.isLoading),
Container(
padding: EdgeInsets.symmetric(horizontal: 20.w),
child: Column(
children: [
AppTextField(controller: _controller),
SizedBox(height: 20.h),
SizedBox(
width: double.maxFinite,
child: AppButton(
label: '获取设备码',
onPressed: () async {
final deviceCode = await DeviceUtils.deviceCode();
if (mounted) {
AppDialog.confirm(
context,
title: '设备码',
message: deviceCode,
);
}
;
},
variant: AppButtonVariant.secondary,
isLoading: authState.isLoading,
),
),
],
),
),
], ],
), ),
); );
} }
/// 测试区域
List<Widget> _buildTestArea(bool isLoading) {
return [
SizedBox(height: 20.h),
Container(
padding: EdgeInsets.symmetric(horizontal: 20.w),
child: Column(
children: [
SizedBox(
width: double.maxFinite,
child: AppButton(
label: '获取设备码',
onPressed: () async {
DeviceUtils.deviceCode().then((code) {
AppDialog.confirm(context, title: '设备码', message: code);
});
},
variant: AppButtonVariant.secondary,
isLoading: isLoading,
),
),
],
),
),
];
}
} }
+79
View File
@@ -0,0 +1,79 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
build/
*pubspec.lock
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
# Coverage
**/coverage/output/
**/coverage/new_lcov.info
+10
View File
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 02c026b03cd31dd3f867e5faeb7e104cce174c5f
channel: unknown
project_type: package
+84
View File
@@ -0,0 +1,84 @@
# Changelog
[简体中文](./CHANGELOG_cn.md)
## [1.1.2]
* #82 Fix flutter_logo.dart error in Flutter 3.0.5
## [1.1.1+1]
* Update latest dependencies.
## [1.1.1]
* #66 [fix] toolbar initial position is incorrect
## [1.1.0+3]
* Fix static analyze issues.
## [1.1.0+2]
* Fix static analyze issues.
## [1.1.0]
* #76 Introduce `UMEWidget.closeActivatedPlugin()`. Issue #35
* #75 Remove overlay entry only when it's been inserted. Issue #65
* #72 [Android] Migrate the example to the v2 embedding
## [1.0.2+1]
* Dart format.
## [1.0.2]
* Fix error in code static analysis.
## [1.0.1]
* Fix error in pubspec.yaml in example
## [1.0.0]
* Normal version with adaption of Flutter 3.
* Feature: Anywhere door (Route)
## [1.0.0-dev.0]
* Adapt Flutter 3.
## [0.3.0+1]
* Fix the version error
## [0.3.0]
* Remove static function. Use the `UMEWidget`.
* Allow insert `Widget` into Widget tree, in order to access new plugin easily.
* Fix the issue of multiple instances of FloatingWidget caused by the refresh state.
* Fix the isseue that the plugin is not displayed due to the first layout exception in AOT mode
## [0.3.0]
* 移除静态方法,更换为壳 Widget
* 允许在 Widget tree 增加自定义嵌套结构组件,从而快速接入新插件
* 修复刷新状态引发的浮窗组件出现多实例的问题
* 修复在 AOT 模式下首次布局异常导致插件不展示的问题
## [0.2.1]
* Remove the extra MaterialApp Widget
## [0.2.0-dev.0]
* Adapted Null-Safety.
## [0.1.0+1]
* Add some docs comments, modify description in pubspec.yaml.
## [0.1.0]
* Open source.
+77
View File
@@ -0,0 +1,77 @@
# Changelog
[English](./CHANGELOG.md)
## [1.1.2]
* #82 修复 flutter_logo.dart 在 Flutter 3.0.5 上的错误
## [1.1.1+1]
* 更新依赖版本
## [1.1.1]
* #66 [fix] toolbar initial position is incorrect
## [1.1.0+3]
* 修复静态分析问题
## [1.1.0+2]
* 修复静态分析问题
## [1.1.0]
* #76 新增 `UMEWidget.closeActivatedPlugin()`。 Issue #35
* #75 修复重复插入 Overlay 的问题。 Issue #65
* #72 [Android] 迁移 example 到 v2 embedding。
## [1.0.2+1]
* Dart format
## [1.0.2]
* 修复静态分析错误
## [1.0.1]
* 修复 example 工程的 pubspec.yaml 错误
## [1.0.0]
* 适配 Flutter 3 正式版
* 新功能:任意门(Route
## [1.0.0-dev.0]
* 适配 Flutter 3
## [0.3.0+1]
* 修复版本号错误
## [0.3.0]
* 移除静态方法,更换为壳 Widget
* 允许在 Widget tree 增加自定义嵌套结构组件,从而快速接入新插件
* 修复刷新状态引发的浮窗组件出现多实例的问题
* 修复在 AOT 模式下首次布局异常导致插件不展示的问题
## [0.2.1]
* 移除独立的 MaterialApp Widget
## [0.2.0-dev.0]
* 适配 null-safety
## [0.1.0+1]
* 增加一些 docs comment,修改 pubspec.yaml 的描述信息
## [0.1.0]
* 开源
+26
View File
@@ -0,0 +1,26 @@
# Changelog
[简体中文](./CHANGELOG.md)
## [0.3.0]
* Remove static function. Use the `UMEWidget`.
* Allow insert `Widget` into Widget tree, in order to access new plugin easily.
* Fix the issue of multiple instances of FloatingWidget caused by the refresh state.
* Fix the isseue that the plugin is not displayed due to the first layout exception in AOT mode
## [0.2.1]
* Remove the extra MaterialApp Widget
## [0.2.0-dev.0]
* Adapted Null-Safety.
## [0.1.0+1]
* Add some docs comments, modify description in pubspec.yaml.
## [0.1.0]
* Open source.
+79
View File
@@ -0,0 +1,79 @@
# Contributing
[简体中文](./CONTRIBUTING_cn.md)
Thank you for your interest in open source contributions.
Not only the code, but also contributions such as issues and rich documents are also welcome.
Please follow the guidelines in this article to make open source contributions to the UME project.
- [Contributing](#contributing)
- [How to contact author](#how-to-contact-author)
- [How to raise an Issue](#how-to-raise-an-issue)
- [How to raise a Pull Request](#how-to-raise-a-pull-request)
- [Commit Message specification](#commit-message-specification)
## How to contact author
**Maybe...**
- Found a bug in the code, or an error in the documentation
- Produces an exception when you use the UME
- UME is not compatible with the new version Flutter
- Have a good idea or suggestion
You can [submit an issue](#how-to-raise-an-issue) in any of the above situations。
**Maybe...**
- Communicate with the author
- Communicate with more community developers
- Cooperate with UME
Welcome to [Join the ByteDance Flutter Exchange Group](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=b07u55bb-68f0-4a4b-871d-687637766a68).
Or contact [author](mailto:sunkai.dev@bytedance.com).
## How to raise an Issue
1. Go to [Issues](https://github.com/bytedance/flutter_ume/issues).
2. Search for similar situations, if there is a match, directly feedback in it.
3. If there is not, press [New issue](https://github.com/bytedance/flutter_ume/issues/new/choose) to raise a new one.
4. Select a template.
5. Describe your situation, and fill in the template.
6. It is better to attach a demo that can reproduce the problem.
## How to raise a Pull Request
1. Fork the repository.
2. Clone your fork repository.
3. Checkout to the correct develop branch, and then create a new brnach based on the develop branch.
4. Edit code.
5. Edit test code in example project, and test it manually.
6. Edit unit test in test directory.
7. Commit your changes and push it. Please follow the [Commit Message specification](#commit-message-specification) to write the commit message.
8. Create Pull Request in GitHub, and fill in the template.
> Now, UME support null-safety and non-null-safety.
> Null-safety version corresponds to `develop_nullsafety` branch, non-null-safety version corresponds to `develop` branch.
> PR should be merged into the corresponding branch.
## Commit Message specification
1. Please use english.
2. If you have references, please attach a link.
3. Format: `[tags] description`
1. `tags` is the type of PR, such as `fix`, `feat`, `improve`.
2. `description` is used to describe changes.
The following is a standard Commit message example:
``` plaintext
[fix] README.md document syntax error
```
``` plaintext
[feat] New feature description
[https://flutter.dev/dash](https://flutter.dev/dash)
```
+79
View File
@@ -0,0 +1,79 @@
# Contributing
[English](./CONTRIBUTING.md)
感谢你对开源贡献感兴趣。
不止是代码,提 issue、补充和扩展文档等贡献也都欢迎。
请根据本文的指引,对 UME 项目进行开源贡献。
- [Contributing](#contributing)
- [如何联系开发者](#如何联系开发者)
- [如何提 Issue](#如何提-issue)
- [如何提 Pull Request](#如何提-pull-request)
- [Commit Message 规范](#commit-message-规范)
## 如何联系开发者
**可能你:**
- 发现文档错误、代码有 bug
- 使用 UME 后应用运行产生异常
- 发现新版本 Flutter 无法兼容
- 有好的点子或产品建议
上述情况均可以[提一个 issue](#how-to-issue)。
**可能你:**
- 想与开发者交流
- 想与更多 Flutter 开发者交流
- 想与 UME 开展交流或合作
欢迎[加入字节跳动 Flutter 交流群](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=b07u55bb-68f0-4a4b-871d-687637766a68)
或随时[联系开发者](mailto:sunkai.dev@bytedance.com)
## 如何提 Issue
1. 点击本仓库的 [Issue 页面](https://github.com/bytedance/flutter_ume/issues)
2. 先搜索是否有和你类似情况的 issue,若有请直接在该 issue 中反馈问题
3. 若没有类似情况 issue,点击 [New issue 按钮](https://github.com/bytedance/flutter_ume/issues/new/choose)
4. 选择一个适合你的 issue 模板
5. 在模板中填写对应信息
6. 如果有能复现问题的最简 Demo 就再好不过了
## 如何提 Pull Request
1. Fork 本仓库
2. 将你 fork 的仓库 clone 到本地
3. 切换到对应开发分支,并 checkout 出新分支
4. 在本地修改代码
5. 修改 example 工程的测试代码,并进行手工测试
6. 在 test 目录下,修改单元测试
7. 在本地提交改动并推送到你 fork 的仓库,commit message 格式请遵循本文 [Commit Message 规范](#commit-message) 部分
8. 在 GitHub 上创建 Pull Request,在模板中填写对应信息
> 目前,UME 同时支持 null-safety 版本与非 null-safety 版本。
> null-safety 版本开发分支为 `develop_nullsafety`,非 null-safety 版本开发分支为 `develop`。
> PR 需要合入对应的开发分支中。
## Commit Message 规范
1. 原则上请尽量使用英文
2. 涉及到参考资料的,请附链接
3. 格式:`[tags] description`
1. `tags` 为 PR 的类型,如 `fix` 修复错误、`feat` 新增功能、`improve` 改进代码或文档
2. `description` 为具体的改动描述
以下为标准的 Commit message 示例:
``` plaintext
[fix] README.md document syntax error
```
``` plaintext
[feat] New feature description
[https://flutter.dev/dash](https://flutter.dev/dash)
```
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 ByteDance Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+471
View File
@@ -0,0 +1,471 @@
# flutter_ume
[简体中文](./README_cn.md)
UME is an in-app debug kits platform for Flutter apps.
[![platforms](https://img.shields.io/badge/platforms-ios%20%7C%20android%20%7C%20web%20%7C%20macos%20%7C%20windows%20%7C%20linux-lightgrey)](https://pub.dev/packages/flutter_ume) [![license](https://img.shields.io/github/license/bytedance/flutter_ume.svg)](https://github.com/bytedance/flutter_ume/blob/master/LICENSE)
[![pub package](https://img.shields.io/pub/v/flutter_ume.svg)](https://pub.dev/packages/flutter_ume)
[![pub package](https://img.shields.io/pub/likes/flutter_ume.svg)](https://pub.dev/packages/flutter_ume)
[![pub package](https://img.shields.io/pub/points/flutter_ume.svg)](https://pub.dev/packages/flutter_ume)
[![pub package](https://img.shields.io/pub/popularity/flutter_ume.svg)](https://pub.dev/packages/flutter_ume)
[![pub package](https://img.shields.io/pub/publisher/flutter_ume.svg)](https://pub.dev/packages/flutter_ume)
**Since `^1.0.0`, flutter_ume starts adapting to the Flutter 3. See [Quick Start] to learn more.**
<img src="https://github.com/bytedance/flutter_ume/raw/master/apk_qrcode.png" width = "128" height = "128" alt="banner" />
Scan QR code or click link to download apk. Try it now!
https://github.com/bytedance/flutter_ume/releases/download/v0.2.1.0/app-debug.apk
There are 13 plugin kits built in the latest open source version of UME.
Developer could create custom plugin kits, and integrate them into UME.
Visit [Develop plugin kits for UME](#develop-plugin-kits-for-ume) for more details.
**Please see [Plugins from community](#plugins-from-community) to make your flutter_ume stronger.**
- [flutter_ume](#flutter_ume)
- [Quick Start](#quick-start)
- [IMPORTANT](#important)
- [Features](#features)
- [Develop plugin kits for UME](#develop-plugin-kits-for-ume)
- [Access the nested widget debug kits quickly](#access-the-nested-widget-debug-kits-quickly)
- [How to use UME in Release/Profile mode](#how-to-use-ume-in-releaseprofile-mode)
- [About version](#about-version)
- [Compatibility](#compatibility)
- [Coverage](#coverage)
- [Version upgrade rules](#version-upgrade-rules)
- [Null-safety](#null-safety)
- [Change log](#change-log)
- [Contributing](#contributing)
- [Contributors](#contributors)
- [Plugins from community](#plugins-from-community)
- [About the third-party open-source project dependencies](#about-the-third-party-open-source-project-dependencies)
- [LICENSE](#license)
- [Contact the author](#contact-the-author)
## Quick Start
**All packages whose names are prefixed with `flutter_ume_kit_` are function**
**plug-ins of UME, and users can access them according to demand**
1. Edit `pubspec.yaml`, and add dependencies.
**Compatible with Flutter 3 since version `1.0.0`.**
``` yaml
dev_dependencies:
flutter_ume: ^1.0.1
flutter_ume_kit_ui: ^1.0.0
flutter_ume_kit_device: ^1.0.0
flutter_ume_kit_perf: ^1.0.0
flutter_ume_kit_show_code: ^1.0.0
flutter_ume_kit_console: ^1.0.0
flutter_ume_kit_dio: ^1.0.0
```
**↓ Null-safety version, compatible with Flutter 2.x**
``` yaml
dev_dependencies: # Don't use UME in release mode
flutter_ume: ^0.3.0+1
flutter_ume_kit_ui: ^0.3.0+1
flutter_ume_kit_device: ^0.3.0
flutter_ume_kit_perf: ^0.3.0
flutter_ume_kit_show_code: ^0.3.0
flutter_ume_kit_console: ^0.3.0
flutter_ume_kit_dio: ^0.3.0
```
**↓ Non-null-safety version, compatible with Flutter 1.x**
``` yaml
dev_dependencies: # Don't use UME in release mode
flutter_ume: ^0.1.1
flutter_ume_kit_ui: ^0.1.1
flutter_ume_kit_device: ^0.1.1
flutter_ume_kit_perf: ^0.1.1
flutter_ume_kit_show_code: ^0.1.1
flutter_ume_kit_console: ^0.1.1
```
2. Run `flutter pub get`
3. Import packages
``` dart
import 'package:flutter_ume/flutter_ume.dart'; // UME framework
import 'package:flutter_ume_kit_ui/flutter_ume_kit_ui.dart'; // UI kits
import 'package:flutter_ume_kit_perf/flutter_ume_kit_perf.dart'; // Performance kits
import 'package:flutter_ume_kit_show_code/flutter_ume_kit_show_code.dart'; // Show Code
import 'package:flutter_ume_kit_device/flutter_ume_kit_device.dart'; // Device info
import 'package:flutter_ume_kit_console/flutter_ume_kit_console.dart'; // Show debugPrint
import 'package:flutter_ume_kit_dio/flutter_ume_kit_dio.dart'; // Dio Inspector
```
4. Edit main method of your app, register plugin kits and initial UME
``` dart
void main() {
if (kDebugMode) {
PluginManager.instance // Register plugin kits
..register(WidgetInfoInspector())
..register(WidgetDetailInspector())
..register(ColorSucker())
..register(AlignRuler())
..register(ColorPicker()) // New feature
..register(TouchIndicator()) // New feature
..register(Performance())
..register(ShowCode())
..register(MemoryInfoPage())
..register(CpuInfoPage())
..register(DeviceInfoPanel())
..register(Console())
..register(DioInspector(dio: dio)); // Pass in your Dio instance
// After flutter_ume 0.3.0
runApp(UMEWidget(child: MyApp(), enable: true));
// Before flutter_ume 0.3.0
runApp(injectUMEWidget(child: MyApp(), enable: true));
} else {
runApp(MyApp());
}
}
```
5. `flutter run` for running
or `flutter build apk --debug`、`flutter build ios --debug` for building productions.
> Some functions rely on VM Service, and additional parameters need to be added for local operation to ensure that it can connect to the VM Service.
>
> Flutter 2.0.x, 2.2.x and other versions run on real devices, `flutter run` needs to add the `--disable-dds` parameter.
> After [Pull Request #80900](https://github.com/flutter/flutter/pull/80900) merging, `--disable-dds` was renamed to `--no-dds`.
## IMPORTANT
**From `0.1.1`/`0.2.1` versionwe don't need set `useRootNavigator: false`.**
The following section only applies to versions before version `0.1.1`/`0.2.1` .
<s>
Since UME manages the routing stack at the top level, methods such as `showDialog` use `rootNavigator` to pop up by default,
therefore **must** pass in the parameter `useRootNavigator: false` in `showDialog`, `showGeneralDialog` and other 'show dialog' methods to avoid navigator errors.
``` dart
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Dialog'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'))
],
),
useRootNavigator: false); // <===== It's very IMPORTANT!
```
</s>
## Features
There are 13 plugin kits built in the current open source version of UME.
<table border="1" width="100%">
<tr>
<td width="33.33%" align="center"><p>UI kits</p></td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/widget_info.png" width="100%" alt="Widget Info" /></br>Widget Info</td>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/widget_detail.png" width="100%" alt="Widget Detail" /></br>Widget Detail</td>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/align_ruler.png" width="100%" alt="Align Ruler" /></br>Align Ruler</td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/color_picker.png" width="100%" alt="Color Picker" /></br>Color Picker</td>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/color_sucker.png" width="100%" alt="Color Sucker" /></br>Color Sucker</td>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/touch_indicator.png" width="100%" alt="Touch Indicator" /></br>Touch Indicator</td>
</tr>
<tr>
<td width="33.33%" align="center"></td>
</tr>
<tr>
<td width="33.33%" align="center"><p>Performance Kits</p></td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/memory_info.png" width="100%" alt="Memory Info" /></br>Memory Info</td>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/perf_overlay.png" width="100%" alt="Perf Overlay" /></br>Perf Overlay</td>
</tr>
<tr>
<td width="33.33%" align="center"></td>
</tr>
<tr>
<td width="33.33%" align="center"><p>Device Info Kits</p></td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/cpu_info.png" width="100%" alt="CPU Info" /></br>CPU Info</td>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/device_info.png" width="100%" alt="Device Info" /></br>Device Info</td>
</tr>
<tr>
<td width="33.33%" align="center"></td>
</tr>
<tr>
<td width="33.33%" align="center"><p>Show Code</p></td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/show_code.png" width="100%" alt="Show Code" /></br>Show Code</td>
</tr>
<tr>
<td width="33.33%" align="center"></td>
</tr>
<tr>
<td width="33.33%" align="center"><p>Console</p></td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/console.png" width="100%" alt="Console" /></br>Console</td>
</tr>
<tr>
<td width="33.33%" align="center"></td>
</tr>
<tr>
<td width="33.33%" align="center"><p>Dio Inspector</p></td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/dio_inspector.png" width="100%" alt="Dio Inspector" /></br>Dio Inspector</td>
</tr>
</table>
## Develop plugin kits for UME
> UME plugins are located in the `./kits` directory, and each one is a `package`.
> You can refer to the example in [`./custom_plugin_example`](./custom_plugin_example/) about this chapter.
1. Run `flutter create -t package custom_plugin` to create your custom plugin kit, it could be `package` or `plugin`.
2. Edit `pubspec.yaml` of the custom plugin kit to add UME framework dependency.
``` yaml
dependencies:
flutter_ume: '>=0.3.0 <0.4.0'
```
3. Create the class of the plugin kit which should implement `Pluggable`.
``` dart
import 'package:flutter_ume/flutter_ume.dart';
class CustomPlugin implements Pluggable {
CustomPlugin({Key key});
@override
Widget buildWidget(BuildContext context) => Container(
color: Colors.white
width: 100,
height: 100,
child: Center(
child: Text('Custom Plugin')
),
); // The panel of the plugin kit
@override
String get name => 'CustomPlugin'; // The name of the plugin kit
@override
String get displayName => 'CustomPlugin';
@override
void onTrigger() {} // Call when tap the icon of plugin kit
@override
ImageProvider<Object> get iconImageProvider => NetworkImage('url'); // The icon image of the plugin kit
}
```
4. Use your custom plugin kit in project
1. Edit `pubspec.yaml` of host app project to add `custom_plugin` dependency.
``` yaml
dev_dependencies:
custom_plugin:
path: path/to/custom_plugin
```
2. Run `flutter pub get`
3. Import package
``` dart
import 'package:custom_plugin/custom_plugin.dart';
```
5. Edit main method of your app, register your custom_plugin plugin kit
``` dart
if (kDebugMode) {
PluginManager.instance
..register(CustomPlugin());
runApp(
UMEWidget(
child: MyApp(),
enable: true
)
);
} else {
runApp(MyApp());
}
```
6. Run your app
### Access the nested widget debug kits quickly
We introduce the `PluggableWithNestedWidget` from `0.3.0`. It is used to insert nested Widgets in the Widget tree and quickly access embedded kits with nested widget.
For more details, see [./kits/flutter_ume_kit_ui/lib/components/color_picker/color_picker.dart](https://github.com/bytedance/flutter_ume/blob/master/kits/flutter_ume_kit_ui/lib/components/color_picker/color_picker.dart) and [./kits/flutter_ume_kit_ui/lib/components/touch_indicator/touch_indicator.dart](https://github.com/bytedance/flutter_ume/blob/master/kits/flutter_ume_kit_ui/lib/components/touch_indicator/touch_indicator.dart).
The key steps are as follows:
1. The class of your plugin should implement `PluggableWithNestedWidget`.
2. Implements `Widget buildNestedWidget(Widget child)`. Handling the nested widgets and returning the new Widget.
## How to use UME in Release/Profile mode
**Once you use flutter_ume in Release/Profile mode, you agree that you will**
**bear the relevant risks by yourself.**
**The maintainer of flutter_ume does not assume any responsibility for the accident**
**caused by this.**
**We recommend not to use it in Release/Profile mode for the following reasons:**
1. VM Service is not available in these environments, so some functions are not available
2. In this environment, developers need to isolate the app distribution channels by themselves to avoid submitting relevant debugging code to the production environment
In order to use in Release/Profile mode, the details that need to be adjusted in the normal access process:
1. In `pubspec.yaml`, `flutter_ume` and plugins should be write below `dependencies` rather than `dev_dependencies`.
2. Don't put the code which call `PluginManager.instance.register()` and `UMEWidget(child: App())` into conditionals which represent debug mode. (Such as `kDebugMode`)
3. Ensure the above details, run `flutter clean` and `flutter pub get`, then build your app.
## About version
### Compatibility
| UME version | 1.12.13 | 1.22.3 | 2.0.1 | 2.2.3 | 2.5.3 | 2.8.0 | 3.0.5 | 3.3.1
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| 0.1.x | ✅ | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | ❌ | ❌ |
| 0.2.x | ❌ | ❌ | ✅ | ✅ | ✅ | ⚠️ | ❌ | ❌ |
| 0.3.x | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| 1.0.x | ❌ | ❌ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ✅ | ✅ |
| 1.1.x | ❌ | ❌ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ✅ | ✅ |
⚠️ means the version has not been fully tested for compatibility.
### Special case
- Please use `flutter_ume_kit_ui: ^1.1.0` and above version when you are using Flutter 3.7 and above.
### Coverage
| Package | master | develop | develop_nullsafety |
| ---- | ---- | ---- | ---- |
| flutter_ume | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/coverage_badge.svg) |
| flutter_ume_kit_device | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/kits/flutter_ume_kit_device/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop/kits/flutter_ume_kit_device/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/kits/flutter_ume_kit_device/coverage_badge.svg) |
| flutter_ume_kit_perf | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/kits/flutter_ume_kit_perf/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop/kits/flutter_ume_kit_perf/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/kits/flutter_ume_kit_perf/coverage_badge.svg) |
| flutter_ume_kit_show_code | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/kits/flutter_ume_kit_show_code/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop/kits/flutter_ume_kit_show_code/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/kits/flutter_ume_kit_show_code/coverage_badge.svg) |
| flutter_ume_kit_ui | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/kits/flutter_ume_kit_ui/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop/kits/flutter_ume_kit_ui/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/kits/flutter_ume_kit_ui/coverage_badge.svg) |
| flutter_ume_kit_console | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/kits/flutter_ume_kit_console/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop/kits/flutter_ume_kit_console/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/kits/flutter_ume_kit_console/coverage_badge.svg) |
| flutter_ume_kit_dio | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/kits/flutter_ume_kit_dio/coverage_badge.svg) | N/A | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/kits/flutter_ume_kit_dio/coverage_badge.svg) |
### Version upgrade rules
Please refer to [Semantic versions](https://dart.dev/tools/pub/versioning#semantic-versions) for details.
### Change log
[Changelog](./CHANGELOG.md)
## Contributing
Contributing rules: [Contributing](./CONTRIBUTING_en.md)
### Contributors
Thanks to the following contributors (names not listed in order)
| | |
| ---- | ---- |
| ![ShirelyC](https://avatars.githubusercontent.com/u/11439167?s=64&v=4) | [ShirelyC](https://github.com/smileShirely) |
| ![lpylpyleo](https://avatars.githubusercontent.com/u/15264428?s=64&v=4) | [lpylpyleo](https://github.com/lpylpyleo) |
| ![Alex Li](https://avatars.githubusercontent.com/u/15884415?s=64&v=4) | [Alex Li](https://github.com/AlexV525) |
| ![Swain](https://avatars.githubusercontent.com/u/7621572?s=64&v=4) | [Swain](https://github.com/talisk) |
| ![mengdouer](https://avatars.githubusercontent.com/u/58758250?s=64&v=4) | [mengdouer](https://github.com/mengdouer) |
| ![LAIIIHZ](https://avatars.githubusercontent.com/u/35956195?s=64&v=4) | [LAIIIHZ](https://github.com/laiiihz) |
| ![XinLei](https://avatars.githubusercontent.com/u/23734225?s=64&v=4) | [XinLei](https://github.com/Vadaski) |
| ![suli](https://avatars.githubusercontent.com/u/5837645?s=64&v=4) | [suli](https://github.com/suli1) |
| ![wei-spring](https://avatars.githubusercontent.com/u/8155092?s=64&v=4) | [wei-spring](https://github.com/wei-spring) |
### Plugins from community
- [flutter_ume_kit_channel_monitor](https://pub.dev/packages/flutter_ume_kit_channel_monitor)
- Channel communication monitor
- Cource code: https://github.com/bytedance/flutter_ume/tree/master/kits/flutter_ume_kit_channel_monitor
- [flutter_ume_kit_slow_animation](https://pub.dev/packages/flutter_ume_kit_slow_animation)
- Animation speed control
- Cource code: https://github.com/cfug/flutter_ume_kits
- [flutter_ume_kit_shared_preferences](https://pub.dev/packages/flutter_ume_kit_shared_preferences)
- shared_preferences tool
- Cource code: https://github.com/cfug/flutter_ume_kits
- [flutter_ume_kit_designer_check](https://pub.dev/packages/)
- Comparing tool for Design UI and real UI
- Cource code: https://github.com/cfug/flutter_ume_kits
- [flutter_ume_kit_clean_local_data](https://pub.dev/packages/flutter_ume_kit_clean_local_data)
- Clean local data
- Cource code: https://github.com/cfug/flutter_ume_kits 。
- [flutter_ume_kit_database_kit](https://pub.dev/packages/flutter_ume_kit_database_kit)
- DB tool
- Cource code: https://github.com/cfug/flutter_ume_kits 。
- [ume_kit_monitor](https://pub.dev/packages/ume_kit_monitor)
- Parameters monitor tools
- Cource code: https://github.com/fastcode555/ume_kit_monitor 。
- [json2dart_viewerffi](https://pub.dev/packages/json2dart_viewerffi)
- DB tool
- Cource code: https://github.com/fastcode555/Json2Dart_Null_Safety 。
- [json2dart_viewer](https://pub.dev/packages/json2dart_viewer)
- DB tool
- Cource code: https://github.com/fastcode555/Json2Dart_Null_Safety 。
- [memory_detector_of_kit](https://github.com/bladeofgod/memory_detector_of_kit)
- Leaks tool
- [channel_observer_of_kit](https://github.com/bladeofgod/channel_observer_of_kit)
- Channel communication monitor
- [flutter-ume-kit-dio-enhance](https://github.com/linversion/flutter-ume-kit-dio-enhance)
- Plugin base on flutter_ume_kit_dio
### About the third-party open-source project dependencies
- The TouchIndicator use the pub [touch_indicator](https://pub.dev/packages/touch_indicator), the ColorPicker use the pub [cyclop](https://pub.dev/packages/cyclop).
- We [fork](https://github.com/talisk/cyclop) the package [cyclop](https://pub.dev/packages/cyclop) and modify some code meet our functional needs. We should depend cyclop by pub version after the [PR](https://github.com/rxlabz/cyclop/pull/11) being merged.
## LICENSE
This project is licensed under the MIT License - visit the [LICENSE](./LICENSE) for details.
## Contact the author
**Maybe...**
- Found a bug in the code, or an error in the documentation
- Produces an exception when you use the UME
- UME is not compatible with the new version Flutter
- Have a good idea or suggestion
You can [submit an issue](./CONTRIBUTING_en.md#how-to-raise-an-issue) in any of the above situations.
**Maybe...**
- Communicate with the author
- Communicate with more community developers
- Cooperate with UME
Welcome to [Join the ByteDance Flutter Exchange Group](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=67au2f75-3783-41b0-8868-0fc0178f1fd8).
Or contact [author](mailto:sunkai.dev@bytedance.com).
+470
View File
@@ -0,0 +1,470 @@
# flutter_ume
[English](./README.md)
Flutter 应用内调试工具平台
[![platforms](https://img.shields.io/badge/platforms-ios%20%7C%20android%20%7C%20web%20%7C%20macos%20%7C%20windows%20%7C%20linux-lightgrey)](https://pub.dev/packages/flutter_ume) [![license](https://img.shields.io/github/license/bytedance/flutter_ume.svg)](https://github.com/bytedance/flutter_ume/blob/master/LICENSE)
[![pub package](https://img.shields.io/pub/v/flutter_ume.svg)](https://pub.dev/packages/flutter_ume)
[![pub package](https://img.shields.io/pub/likes/flutter_ume.svg)](https://pub.dev/packages/flutter_ume)
[![pub package](https://img.shields.io/pub/points/flutter_ume.svg)](https://pub.dev/packages/flutter_ume)
[![pub package](https://img.shields.io/pub/popularity/flutter_ume.svg)](https://pub.dev/packages/flutter_ume)
[![pub package](https://img.shields.io/pub/publisher/flutter_ume.svg)](https://pub.dev/packages/flutter_ume)
**Since `^1.0.0`, flutter_ume starts adapting to the Flutter 3. See [Quick Start] to learn more.**
<img src="https://github.com/bytedance/flutter_ume/raw/master/apk_qrcode.png" width = "128" height = "128" alt="banner" />
扫码或点击链接下载 apk,快速体验 UME。
https://github.com/bytedance/flutter_ume/releases/download/v0.2.1.0/app-debug.apk
最新版本(1.0.1)内置 13 个插件,
开发者可以创建自己的插件,并集成进 UME 平台。
详见本文[为 UME 开发插件](#为-ume-开发插件)部分。
**更多开源社区贡献的调试插件,请见[社区插件](#社区插件)部分。**
- [flutter_ume](#flutter_ume)
- [快速接入](#快速接入)
- [特别说明](#特别说明)
- [功能介绍](#功能介绍)
- [为 UME 开发插件](#为-ume-开发插件)
- [快速集成嵌入式插件](#快速集成嵌入式插件)
- [如何在 Release/Profile mode 下使用 UME](#如何在-releaseprofile-mode-下使用-ume)
- [版本说明](#版本说明)
- [兼容性](#兼容性)
- [单测覆盖率](#单测覆盖率)
- [版本号规则](#版本号规则)
- [Null-safety 版本](#null-safety-版本)
- [更新日志](#更新日志)
- [开源贡献](#开源贡献)
- [贡献者](#贡献者)
- [社区插件](#社区插件)
- [第三方开源项目说明](#第三方开源项目说明)
- [开源协议](#开源协议)
- [联系开发者](#联系开发者)
## 快速接入
**所有名称前缀为 `flutter_ume_kit_` 的 package 都是 UME 的功能插件,**
**用户可按需接入。**
1. 修改 `pubspec.yaml`,添加依赖
**自 `1.0.0` 版本开始适配 Flutter 3。**
``` yaml
dev_dependencies:
flutter_ume: ^1.0.1
flutter_ume_kit_ui: ^1.0.0
flutter_ume_kit_device: ^1.0.0
flutter_ume_kit_perf: ^1.0.0
flutter_ume_kit_show_code: ^1.0.0
flutter_ume_kit_console: ^1.0.0
flutter_ume_kit_dio: ^1.0.0
```
**↓ Null-safety 版本,适用于 Flutter 2.x**
``` yaml
dev_dependencies:
flutter_ume: ^0.3.0+1
flutter_ume_kit_ui: ^0.3.0+1
flutter_ume_kit_device: ^0.3.0
flutter_ume_kit_perf: ^0.3.0
flutter_ume_kit_show_code: ^0.3.0
flutter_ume_kit_console: ^0.3.0
flutter_ume_kit_dio: ^0.3.0
```
**↓ 非 Null-safety 版本,适用于 Flutter 1.x**
``` yaml
dev_dependencies:
flutter_ume: ^0.1.1
flutter_ume_kit_ui: ^0.1.1
flutter_ume_kit_device: ^0.1.1
flutter_ume_kit_perf: ^0.1.1
flutter_ume_kit_show_code: ^0.1.1
flutter_ume_kit_console: ^0.1.1
```
2. 执行 `flutter pub get`
3. 引入包
``` dart
import 'package:flutter_ume/flutter_ume.dart'; // UME 框架
import 'package:flutter_ume_kit_ui/flutter_ume_kit_ui.dart'; // UI 插件包
import 'package:flutter_ume_kit_perf/flutter_ume_kit_perf.dart'; // 性能插件包
import 'package:flutter_ume_kit_show_code/flutter_ume_kit_show_code.dart'; // 代码查看插件包
import 'package:flutter_ume_kit_device/flutter_ume_kit_device.dart'; // 设备信息插件包
import 'package:flutter_ume_kit_console/flutter_ume_kit_console.dart'; // debugPrint 插件包
import 'package:flutter_ume_kit_dio/flutter_ume_kit_dio.dart'; // Dio 网络请求调试工具
```
4. 修改程序入口,增加初始化方法及注册插件代码
``` dart
void main() {
if (kDebugMode) {
PluginManager.instance // 注册插件
..register(WidgetInfoInspector())
..register(WidgetDetailInspector())
..register(ColorSucker())
..register(AlignRuler())
..register(ColorPicker()) // 新插件
..register(TouchIndicator()) // 新插件
..register(Performance())
..register(ShowCode())
..register(MemoryInfoPage())
..register(CpuInfoPage())
..register(DeviceInfoPanel())
..register(Console())
..register(DioInspector(dio: dio)); // 传入你的 Dio 实例
// flutter_ume 0.3.0 版本之后
runApp(UMEWidget(child: MyApp(), enable: true)); // 初始化
// flutter_ume 0.3.0 版本之前
runApp(injectUMEWidget(child: MyApp(), enable: true)); // 初始化
} else {
runApp(MyApp());
}
}
```
5. `flutter run` 运行代码
或 `flutter build apk --debug`、`flutter build ios --debug` 构建产物
> 部分功能依赖 VM Service,本地运行需要添加额外参数,以确保能够连接到 VM Service。
>
> Flutter 2.0.x、2.2.x 等版本在真机上运行,`flutter run` 需要添加 `--disable-dds` 参数。
> 在 [Pull Request #80900](https://github.com/flutter/flutter/pull/80900) 合入之后,`--disable-dds` 参数被更名为 `--no-dds`。
## 特别说明
**自 `0.1.1`/`0.2.1` 版本起,已经不需要设置 `useRootNavigator: false`。**
以下部分仅适用于 `0.1.1`/`0.2.1` 之前的版本。
<s>
由于 UME 在顶层管理了路由栈,`showDialog` 等方法默认使用 `rootNavigator` 弹出,
所以**必须**在 `showDialog`、`showGeneralDialog` 等弹窗方法,传入参数 `useRootNavigator: false` 避免路由栈错误。
``` dart
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Dialog'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'))
],
),
useRootNavigator: false); // <===== 非常重要
```
</s>
## 功能介绍
当前开源版 UME 内置了 13 个插件
<table border="1" width="100%">
<tr>
<td width="33.33%" align="center"><p>UI 工具包</p></td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/widget_info.png" width="100%" alt="Widget 信息" /></br>Widget 信息</td>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/widget_detail.png" width="100%" alt="Widget 详情" /></br>Widget 详情</td>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/align_ruler.png" width="100%" alt="对齐标尺" /></br>对齐标尺</td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/color_picker.png" width="100%" alt="颜色吸管(新)" /></br>颜色吸管(新)</td>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/color_sucker.png" width="100%" alt="颜色吸管" /></br>颜色吸管</td>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/touch_indicator.png" width="100%" alt="触控标记" /></br>触控标记</td>
</tr>
<tr>
<td width="33.33%" align="center"></td>
</tr>
<tr>
<td width="33.33%" align="center"><p>性能工具包</p></td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/memory_info.png" width="100%" alt="内存信息" /></br>内存信息</td>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/perf_overlay.png" width="100%" alt="性能浮层" /></br>性能浮层</td>
</tr>
<tr>
<td width="33.33%" align="center"></td>
</tr>
<tr>
<td width="33.33%" align="center"><p>设备信息工具包</p></td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/cpu_info.png" width="100%" alt="CPU 信息" /></br>CPU 信息</td>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/device_info.png" width="100%" alt="设备信息" /></br>设备信息</td>
</tr>
<tr>
<td width="33.33%" align="center"></td>
</tr>
<tr>
<td width="33.33%" align="center"><p>代码查看</p></td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/show_code.png" width="100%" alt="代码查看" /></br>代码查看</td>
</tr>
<tr>
<td width="33.33%" align="center"></td>
</tr>
<tr>
<td width="33.33%" align="center"><p>日志展示</p></td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/console.png" width="100%" alt="日志展示" /></br>日志展示</td>
</tr>
<tr>
<td width="33.33%" align="center"></td>
</tr>
<tr>
<td width="33.33%" align="center"><p>Dio 网络请求调试工具</p></td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/dio_inspector.png" width="100%" alt="Dio 网络请求调试工具" /></br>Dio 网络请求调试工具</td>
</tr>
</table>
## 为 UME 开发插件
> UME 插件位于 `./kits` 目录下,每个插件包都是一个 `package`
> 本小节示例可参考 [`./custom_plugin_example`](./custom_plugin_example/)
1. `flutter create -t package custom_plugin` 创建一个插件包,可以是 `package`,也可以是 `plugin`
2. 修改插件包的 `pubspec.yaml`,添加依赖
``` yaml
dependencies:
flutter_ume: '>=0.3.0 <0.4.0'
```
3. 创建插件配置,实现 `Pluggable` 虚类
``` dart
import 'package:flutter_ume/flutter_ume.dart';
class CustomPlugin implements Pluggable {
CustomPlugin({Key key});
@override
Widget buildWidget(BuildContext context) => Container(
color: Colors.white
width: 100,
height: 100,
child: Center(
child: Text('Custom Plugin')
),
); // 返回插件面板
@override
String get name => 'CustomPlugin'; // 插件名称
@override
String get displayName => 'CustomPlugin';
@override
void onTrigger() {} // 点击插件面板图标时调用
@override
ImageProvider<Object> get iconImageProvider => NetworkImage('url'); // 插件图标
}
```
4. 在工程中引入自定义插件
1. 修改 `pubspec.yaml`,添加依赖
``` yaml
dev_dependencies:
custom_plugin:
path: path/to/custom_plugin
```
2. 执行 `flutter pub get`
3. 引入包
``` dart
import 'package:custom_plugin/custom_plugin.dart';
```
5. 在工程中注册插件
``` dart
if (kDebugMode) {
PluginManager.instance
..register(CustomPlugin());
runApp(
UMEWidget(
child: MyApp(),
enable: true
)
);
} else {
runApp(MyApp());
}
```
6. 运行代码
### 快速集成嵌入式插件
自 `0.3.0` 版本起引入了 `PluggableWithNestedWidget`,用以实现在 Widget tree 中插入嵌套 Widget,快速接入嵌入式插件。
可参考 [./kits/flutter_ume_kit_ui/lib/components/color_picker/color_picker.dart](https://github.com/bytedance/flutter_ume/blob/master/kits/flutter_ume_kit_ui/lib/components/color_picker/color_picker.dart) 与 [./kits/flutter_ume_kit_ui/lib/components/touch_indicator/touch_indicator.dart](https://github.com/bytedance/flutter_ume/blob/master/kits/flutter_ume_kit_ui/lib/components/touch_indicator/touch_indicator.dart)。
集成重点如下:
1. 插件主体类实现 `PluggableWithNestedWidget`
2. 实现 `Widget buildNestedWidget(Widget child)`,在该方法中处理嵌套结构并返回 Widget
## 如何在 Release/Profile mode 下使用 UME
**开发者一旦在 Release/Profile mode 下使用 flutter_ume**
**即认同将自行承担相关风险,**
**对于由此引发的事故,flutter_ume 维护方不承担**
**任何责任。**
**不建议在 Release/Profile mode 下使用,原因如下:**
1. 在该环境下 VM Service 不可用,因此部分插件功能不可用
2. 在该环境下开发者需要自行隔离分发渠道,避免将相关调试代码提交到生产环境
为在 Release/Profile mode 下使用,正常接入流程中需要调整的细节:
1. `pubspec.yaml` 中,`flutter_ume` 及相关插件包需要在 `dependencies` 中引入,而不是 `dev_dependencies`
2. 调用 `PluginManager.instance.register()` 及 `UMEWidget(child: App())` 初始化方法的代码,不得由于 debug 标记剪枝(如 `kDebugMode`
3. 确保以上细节后,依次执行 `flutter clean`、`flutter pub get` 后再进行构建
## 版本说明
### 兼容性
| UME 版本 | 1.12.13 | 1.22.3 | 2.0.1 | 2.2.3 | 2.5.3 | 2.8.0 | 3.0.5 | 3.3.1
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| 0.1.x | ✅ | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | ❌ | ❌ |
| 0.2.x | ❌ | ❌ | ✅ | ✅ | ✅ | ⚠️ | ❌ | ❌ |
| 0.3.x | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| 1.0.x | ❌ | ❌ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ✅ | ✅ |
| 1.1.x | ❌ | ❌ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ✅ | ✅ |
⚠️ 意为未经过完整的兼容性测试,不建议使用。
### 特例
- Flutter 3.7 及以上版本请使用 `flutter_ume_kit_ui: ^1.1.0` 及以上版本
### 单测覆盖率
| 包 | master | develop | develop_nullsafety |
| ---- | ---- | ---- | ---- |
| flutter_ume | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/coverage_badge.svg) |
| flutter_ume_kit_device | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/kits/flutter_ume_kit_device/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop/kits/flutter_ume_kit_device/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/kits/flutter_ume_kit_device/coverage_badge.svg) |
| flutter_ume_kit_perf | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/kits/flutter_ume_kit_perf/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop/kits/flutter_ume_kit_perf/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/kits/flutter_ume_kit_perf/coverage_badge.svg) |
| flutter_ume_kit_show_code | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/kits/flutter_ume_kit_show_code/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop/kits/flutter_ume_kit_show_code/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/kits/flutter_ume_kit_show_code/coverage_badge.svg) |
| flutter_ume_kit_ui | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/kits/flutter_ume_kit_ui/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop/kits/flutter_ume_kit_ui/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/kits/flutter_ume_kit_ui/coverage_badge.svg) |
| flutter_ume_kit_console | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/kits/flutter_ume_kit_console/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop/kits/flutter_ume_kit_console/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/kits/flutter_ume_kit_console/coverage_badge.svg) |
| flutter_ume_kit_dio | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/kits/flutter_ume_kit_dio/coverage_badge.svg) | N/A | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/kits/flutter_ume_kit_dio/coverage_badge.svg) |
### 版本号规则
请参考 [Semantic versions](https://dart.dev/tools/pub/versioning#semantic-versions)
### 更新日志
[Changelog](./CHANGELOG_cn.md)
## 开源贡献
贡献文档:[Contributing](./CONTRIBUTING.md)
### 贡献者
感谢以下贡献者(排名不分先后):
| | |
| ---- | ---- |
| ![ShirelyC](https://avatars.githubusercontent.com/u/11439167?s=64&v=4) | [ShirelyC](https://github.com/smileShirely) |
| ![lpylpyleo](https://avatars.githubusercontent.com/u/15264428?s=64&v=4) | [lpylpyleo](https://github.com/lpylpyleo) |
| ![Alex Li](https://avatars.githubusercontent.com/u/15884415?s=64&v=4) | [Alex Li](https://github.com/AlexV525) |
| ![Swain](https://avatars.githubusercontent.com/u/7621572?s=64&v=4) | [Swain](https://github.com/talisk) |
| ![mengdouer](https://avatars.githubusercontent.com/u/58758250?s=64&v=4) | [mengdouer](https://github.com/mengdouer) |
| ![LAIIIHZ](https://avatars.githubusercontent.com/u/35956195?s=64&v=4) | [LAIIIHZ](https://github.com/laiiihz) |
| ![XinLei](https://avatars.githubusercontent.com/u/23734225?s=64&v=4) | [XinLei](https://github.com/Vadaski) |
| ![suli](https://avatars.githubusercontent.com/u/5837645?s=64&v=4) | [suli](https://github.com/suli1) |
| ![wei-spring](https://avatars.githubusercontent.com/u/8155092?s=64&v=4) | [wei-spring](https://github.com/wei-spring) |
### 社区插件
- [flutter_ume_kit_channel_monitor](https://pub.dev/packages/flutter_ume_kit_channel_monitor)
- channel 通信监控工具
- 源代码托管于 https://github.com/bytedance/flutter_ume/tree/master/kits/flutter_ume_kit_channel_monitor
- [flutter_ume_kit_slow_animation](https://pub.dev/packages/flutter_ume_kit_slow_animation)
- 动画速度调节插件
- 源代码托管于 https://github.com/cfug/flutter_ume_kits
- [flutter_ume_kit_shared_preferences](https://pub.dev/packages/flutter_ume_kit_shared_preferences)
- shared_preferences 调试工具
- 源代码托管于 https://github.com/cfug/flutter_ume_kits
- [flutter_ume_kit_designer_check](https://pub.dev/packages/)
- 设计稿比对工具
- 源代码托管于 https://github.com/cfug/flutter_ume_kits
- [flutter_ume_kit_clean_local_data](https://pub.dev/packages/flutter_ume_kit_clean_local_data)
- 清理本地数据插件
- 源代码托管于 https://github.com/cfug/flutter_ume_kits 。
- [flutter_ume_kit_database_kit](https://pub.dev/packages/flutter_ume_kit_database_kit)
- 数据库调试插件
- 源代码托管于 https://github.com/cfug/flutter_ume_kits 。
- [ume_kit_monitor](https://pub.dev/packages/ume_kit_monitor)
- 参数监控插件
- 源代码托管于 https://github.com/fastcode555/ume_kit_monitor 。
- [json2dart_viewerffi](https://pub.dev/packages/json2dart_viewerffi)
- 数据库调试插件
- 源代码托管于 https://github.com/fastcode555/Json2Dart_Null_Safety 。
- [json2dart_viewer](https://pub.dev/packages/json2dart_viewer)
- 数据库调试插件
- 源代码托管于 https://github.com/fastcode555/Json2Dart_Null_Safety 。
- [memory_detector_of_kit](https://github.com/bladeofgod/memory_detector_of_kit)
- 内存泄漏检测插件
- [channel_observer_of_kit](https://github.com/bladeofgod/channel_observer_of_kit)
- channel 调用记录监控插件
- [flutter-ume-kit-dio-enhance](https://github.com/linversion/flutter-ume-kit-dio-enhance)
- 基于 flutter_ume_kit_dio 扩展了一些功能的插件
### 第三方开源项目说明
- 触控标记使用了 [touch_indicator](https://pub.dev/packages/touch_indicator),颜色吸管插件使用了 [cyclop](https://pub.dev/packages/cyclop)。
- 对 [cyclop](https://pub.dev/packages/cyclop) 进行了 [fork](https://github.com/talisk/cyclop) 并修改代码以满足需要。当 [PR](https://github.com/rxlabz/cyclop/pull/11) 合入后,我们将通过 pub 的形式依赖。
## 开源协议
该项目遵循 MIT 协议,详情请见 [LICENSE](./LICENSE)。
## 联系开发者
**可能你:**
- 发现文档错误、代码有 bug
- 使用 UME 后应用运行产生异常
- 发现新版本 Flutter 无法兼容
- 有好的点子或产品建议
上述情况均可以[提一个 issue](./CONTRIBUTING.md#如何提-issue)。
**可能你:**
- 想与开发者交流
- 想与更多 Flutter 开发者交流
- 想与 UME 开展交流或合作
欢迎[加入字节跳动 Flutter 交流群](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=67au2f75-3783-41b0-8868-0fc0178f1fd8)
或随时[联系开发者](mailto:sunkai.dev@bytedance.com)
+419
View File
@@ -0,0 +1,419 @@
# flutter_ume
[简体中文](./README.md)
UME is an in-app debug kits platform for Flutter apps.
[![platforms](https://img.shields.io/badge/platforms-ios%20%7C%20android%20%7C%20web%20%7C%20macos%20%7C%20windows%20%7C%20linux-lightgrey)](https://pub.dev/packages/flutter_ume) [![license](https://img.shields.io/github/license/bytedance/flutter_ume.svg)](https://github.com/bytedance/flutter_ume/blob/master/LICENSE) [![latest](https://img.shields.io/pub/vpre/flutter_ume.svg)](https://pub.dev/packages/flutter_ume) ![![likes](https://badges.bar/flutter_ume/likes)](https://pub.dev/packages/flutter_ume/score) ![![popularity](https://badges.bar/flutter_ume/popularity)](https://pub.dev/packages/flutter_ume/score) ![![pub points](https://badges.bar/flutter_ume/pub%20points)](https://pub.dev/packages/flutter_ume/score)
<img src="https://github.com/bytedance/flutter_ume/raw/master/ume_logo_256.png" width = "128" height = "128" alt="banner" />
**UME Kits competition is in full swing!** Rich prizes are waiting for you.
See https://mp.weixin.qq.com/s/RuwiiQAdrGqI00fDhUO77g for more details.
<img src="https://github.com/bytedance/flutter_ume/raw/master/apk_qrcode.png" width = "256" height = "256" alt="banner" />
Scan QR code or click link to download apk. Try it now!
https://github.com/bytedance/flutter_ume/releases/download/v0.2.1.0/app-debug.apk
There are 13 plugin kits built in the latest open source version of UME.
Developer could create custom plugin kits, and integrate them into UME.
Visit [Develop plugin kits for UME](#develop-plugin-kits-for-ume) for more details.
- [flutter_ume](#flutter_ume)
- [Quick Start](#quick-start)
- [IMPORTANT](#important)
- [Features](#features)
- [Develop plugin kits for UME](#develop-plugin-kits-for-ume)
- [Access the nested widget debug kits quickly](#access-the-nested-widget-debug-kits-quickly)
- [How to use UME in Release/Profile mode](#how-to-use-ume-in-releaseprofile-mode)
- [About version](#about-version)
- [Compatibility](#compatibility)
- [Coverage](#coverage)
- [Version upgrade rules](#version-upgrade-rules)
- [Null-safety](#null-safety)
- [Change log](#change-log)
- [Contributing](#contributing)
- [Contributors](#contributors)
- [About the third-party open-source project dependencies](#about-the-third-party-open-source-project-dependencies)
- [LICENSE](#license)
- [Contact the author](#contact-the-author)
## Quick Start
**All packages whose names are prefixed with `flutter_ume_kit_` are function**
**plug-ins of UME, and users can access them according to demand**
1. Edit `pubspec.yaml`, and add dependencies.
**↓ Null-safety version, compatible with Flutter 2.x**
``` yaml
dev_dependencies: # Don't use UME in release mode
flutter_ume: ^0.3.0+1
flutter_ume_kit_ui: ^0.3.0+1
flutter_ume_kit_device: ^0.3.0
flutter_ume_kit_perf: ^0.3.0
flutter_ume_kit_show_code: ^0.3.0
flutter_ume_kit_console: ^0.3.0
flutter_ume_kit_dio: ^0.3.0
```
**↓ Non-null-safety version, compatible with Flutter 1.x**
``` yaml
dev_dependencies: # Don't use UME in release mode
flutter_ume: ^0.1.1
flutter_ume_kit_ui: ^0.1.1.1
flutter_ume_kit_device: ^0.1.1
flutter_ume_kit_perf: ^0.1.1
flutter_ume_kit_show_code: ^0.1.1
flutter_ume_kit_console: ^0.1.1
```
2. Run `flutter pub get`
3. Import packages
``` dart
import 'package:flutter_ume/flutter_ume.dart'; // UME framework
import 'package:flutter_ume_kit_ui/flutter_ume_kit_ui.dart'; // UI kits
import 'package:flutter_ume_kit_perf/flutter_ume_kit_perf.dart'; // Performance kits
import 'package:flutter_ume_kit_show_code/flutter_ume_kit_show_code.dart'; // Show Code
import 'package:flutter_ume_kit_device/flutter_ume_kit_device.dart'; // Device info
import 'package:flutter_ume_kit_console/flutter_ume_kit_console.dart'; // Show debugPrint
import 'package:flutter_ume_kit_dio/flutter_ume_kit_dio.dart'; // Dio Inspector
```
4. Edit main method of your app, register plugin kits and initial UME
``` dart
void main() {
if (kDebugMode) {
PluginManager.instance // Register plugin kits
..register(WidgetInfoInspector())
..register(WidgetDetailInspector())
..register(ColorSucker())
..register(AlignRuler())
..register(ColorPicker()) // New feature
..register(TouchIndicator()) // New feature
..register(Performance())
..register(ShowCode())
..register(MemoryInfoPage())
..register(CpuInfoPage())
..register(DeviceInfoPanel())
..register(Console())
..register(DioInspector(dio: dio)); // Pass in your Dio instance
// After flutter_ume 0.3.0
runApp(UMEWidget(child: MyApp(), enable: true));
// Before flutter_ume 0.3.0
runApp(injectUMEWidget(child: MyApp(), enable: true));
} else {
runApp(MyApp());
}
}
```
5. `flutter run` for running
or `flutter build apk --debug`、`flutter build ios --debug` for building productions.
> Some functions rely on VM Service, and additional parameters need to be added for local operation to ensure that it can connect to the VM Service.
>
> Flutter 2.0.x, 2.2.x and other versions run on real devices, `flutter run` needs to add the `--disable-dds` parameter.
> After [Pull Request #80900](https://github.com/flutter/flutter/pull/80900) merging, `--disable-dds` was renamed to `--no-dds`.
## IMPORTANT
**From `0.1.1`/`0.2.1` versionwe don't need set `useRootNavigator: false`.**
The following section only applies to versions before version `0.1.1`/`0.2.1` .
<s>
Since UME manages the routing stack at the top level, methods such as `showDialog` use `rootNavigator` to pop up by default,
therefore **must** pass in the parameter `useRootNavigator: false` in `showDialog`, `showGeneralDialog` and other 'show dialog' methods to avoid navigator errors.
``` dart
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Dialog'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'))
],
),
useRootNavigator: false); // <===== It's very IMPORTANT!
```
</s>
## Features
There are 13 plugin kits built in the current open source version of UME.
<table border="1" width="100%">
<tr>
<td width="33.33%" align="center"><p>UI kits</p></td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/widget_info.png" width="100%" alt="Widget Info" /></br>Widget Info</td>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/widget_detail.png" width="100%" alt="Widget Detail" /></br>Widget Detail</td>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/align_ruler.png" width="100%" alt="Align Ruler" /></br>Align Ruler</td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/color_picker.png" width="100%" alt="Color Picker" /></br>Color Picker</td>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/color_sucker.png" width="100%" alt="Color Sucker" /></br>Color Sucker</td>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/touch_indicator.png" width="100%" alt="Touch Indicator" /></br>Touch Indicator</td>
</tr>
<tr>
<td width="33.33%" align="center"></td>
</tr>
<tr>
<td width="33.33%" align="center"><p>Performance Kits</p></td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/memory_info.png" width="100%" alt="Memory Info" /></br>Memory Info</td>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/perf_overlay.png" width="100%" alt="Perf Overlay" /></br>Perf Overlay</td>
</tr>
<tr>
<td width="33.33%" align="center"></td>
</tr>
<tr>
<td width="33.33%" align="center"><p>Device Info Kits</p></td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/cpu_info.png" width="100%" alt="CPU Info" /></br>CPU Info</td>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/device_info.png" width="100%" alt="Device Info" /></br>Device Info</td>
</tr>
<tr>
<td width="33.33%" align="center"></td>
</tr>
<tr>
<td width="33.33%" align="center"><p>Show Code</p></td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/show_code.png" width="100%" alt="Show Code" /></br>Show Code</td>
</tr>
<tr>
<td width="33.33%" align="center"></td>
</tr>
<tr>
<td width="33.33%" align="center"><p>Console</p></td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/console.png" width="100%" alt="Console" /></br>Console</td>
</tr>
<tr>
<td width="33.33%" align="center"></td>
</tr>
<tr>
<td width="33.33%" align="center"><p>Dio Inspector</p></td>
</tr>
<tr>
<td width="33.33%" align="center"><img src="https://github.com/bytedance/flutter_ume/raw/master/screenshots/dio_inspector.png" width="100%" alt="Dio Inspector" /></br>Dio Inspector</td>
</tr>
</table>
## Develop plugin kits for UME
> UME plugins are located in the `./kits` directory, and each one is a `package`.
> You can refer to the example in [`./custom_plugin_example`](./custom_plugin_example/) about this chapter.
1. Run `flutter create -t package custom_plugin` to create your custom plugin kit, it could be `package` or `plugin`.
2. Edit `pubspec.yaml` of the custom plugin kit to add UME framework dependency.
``` yaml
dependencies:
flutter_ume: '>=0.3.0 <0.4.0'
```
3. Create the class of the plugin kit which should implement `Pluggable`.
``` dart
import 'package:flutter_ume/flutter_ume.dart';
class CustomPlugin implements Pluggable {
CustomPlugin({Key key});
@override
Widget buildWidget(BuildContext context) => Container(
color: Colors.white
width: 100,
height: 100,
child: Center(
child: Text('Custom Plugin')
),
); // The panel of the plugin kit
@override
String get name => 'CustomPlugin'; // The name of the plugin kit
@override
String get displayName => 'CustomPlugin';
@override
void onTrigger() {} // Call when tap the icon of plugin kit
@override
ImageProvider<Object> get iconImageProvider => NetworkImage('url'); // The icon image of the plugin kit
}
```
4. Use your custom plugin kit in project
1. Edit `pubspec.yaml` of host app project to add `custom_plugin` dependency.
``` yaml
dev_dependencies:
custom_plugin:
path: path/to/custom_plugin
```
2. Run `flutter pub get`
3. Import package
``` dart
import 'package:custom_plugin/custom_plugin.dart';
```
5. Edit main method of your app, register your custom_plugin plugin kit
``` dart
if (kDebugMode) {
PluginManager.instance
..register(CustomPlugin());
runApp(
UMEWidget(
child: MyApp(),
enable: true
)
);
} else {
runApp(MyApp());
}
```
6. Run your app
### Access the nested widget debug kits quickly
We introduce the `PluggableWithNestedWidget` from `0.3.0`. It is used to insert nested Widgets in the Widget tree and quickly access embedded kits with nested widget.
For more details, see [./kits/flutter_ume_kit_ui/lib/components/color_picker/color_picker.dart](https://github.com/bytedance/flutter_ume/blob/master/kits/flutter_ume_kit_ui/lib/components/color_picker/color_picker.dart) and [./kits/flutter_ume_kit_ui/lib/components/touch_indicator/touch_indicator.dart](https://github.com/bytedance/flutter_ume/blob/master/kits/flutter_ume_kit_ui/lib/components/touch_indicator/touch_indicator.dart).
The key steps are as follows:
1. The class of your plugin should implement `PluggableWithNestedWidget`.
2. Implements `Widget buildNestedWidget(Widget child)`. Handling the nested widgets and returning the new Widget.
## How to use UME in Release/Profile mode
**Once you use flutter_ume in Release/Profile mode, you agree that you will**
**bear the relevant risks by yourself.**
**The maintainer of flutter_ume does not assume any responsibility for the accident**
**caused by this.**
**We recommend not to use it in Release/Profile mode for the following reasons:**
1. VM Service is not available in these environments, so some functions are not available
2. In this environment, developers need to isolate the app distribution channels by themselves to avoid submitting relevant debugging code to the production environment
In order to use in Release/Profile mode, the details that need to be adjusted in the normal access process:
1. In `pubspec.yaml`, `flutter_ume` and plugins should be write below `dependencies` rather than `dev_dependencies`.
2. Don't put the code which call `PluginManager.instance.register()` and `UMEWidget(child: App())` into conditionals which represent debug mode. (Such as `kDebugMode`)
3. Ensure the above details, run `flutter clean` and `flutter pub get`, then build your app.
## About version
### Compatibility
| UME version | Flutter 1.12.13 | Flutter 1.22.3 | Flutter 2.0.1 | Flutter 2.2.3 | Flutter 2.5.3 |
| ---- | ---- | ---- | ---- | ---- | ---- |
| 0.1.x | ✅ | ✅ | ✅ | ✅ | ⚠️ |
| 0.2.x | ❌ | ❌ | ✅ | ✅ | ✅ |
| 0.3.x | ❌ | ❌ | ✅ | ✅ | ✅ |
⚠️ means the version has not been fully tested for compatibility.
⚠️ means the version has not been fully tested for compatibility.
### Coverage
| Package | master | develop | develop_nullsafety |
| ---- | ---- | ---- | ---- |
| flutter_ume | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/coverage_badge.svg) |
| flutter_ume_kit_device | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/kits/flutter_ume_kit_device/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop/kits/flutter_ume_kit_device/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/kits/flutter_ume_kit_device/coverage_badge.svg) |
| flutter_ume_kit_perf | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/kits/flutter_ume_kit_perf/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop/kits/flutter_ume_kit_perf/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/kits/flutter_ume_kit_perf/coverage_badge.svg) |
| flutter_ume_kit_show_code | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/kits/flutter_ume_kit_show_code/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop/kits/flutter_ume_kit_show_code/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/kits/flutter_ume_kit_show_code/coverage_badge.svg) |
| flutter_ume_kit_ui | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/kits/flutter_ume_kit_ui/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop/kits/flutter_ume_kit_ui/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/kits/flutter_ume_kit_ui/coverage_badge.svg) |
| flutter_ume_kit_console | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/kits/flutter_ume_kit_console/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop/kits/flutter_ume_kit_console/coverage_badge.svg) | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/kits/flutter_ume_kit_console/coverage_badge.svg) |
| flutter_ume_kit_dio | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/master/kits/flutter_ume_kit_dio/coverage_badge.svg) | N/A | ![Coverage](https://raw.githubusercontent.com/bytedance/flutter_ume/develop_nullsafety/kits/flutter_ume_kit_dio/coverage_badge.svg) |
### Version upgrade rules
Please refer to [Semantic versions](https://dart.dev/tools/pub/versioning#semantic-versions) for details.
### Null-safety
| Package | Suggest version |
| ---- | ---- |
| flutter_ume | 0.3.0+1 |
| flutter_ume_kit_ui | 0.3.0+1 |
| flutter_ume_kit_device | 0.3.0 |
| flutter_ume_kit_perf | 0.3.0 |
| flutter_ume_kit_show_code | 0.3.0 |
| flutter_ume_kit_console | 0.3.0 |
| flutter_ume_kit_dio | 0.3.0 |
### Change log
[Changelog](./CHANGELOG.md)
## Contributing
Contributing rules: [Contributing](./CONTRIBUTING_en.md)
### Contributors
Thanks to the following contributors (names not listed in order)
| | |
| ---- | ---- |
| ![ShirelyC](https://avatars.githubusercontent.com/u/11439167?s=64&v=4) | [ShirelyC](https://github.com/smileShirely) |
| ![lpylpyleo](https://avatars.githubusercontent.com/u/15264428?s=64&v=4) | [lpylpyleo](https://github.com/lpylpyleo) |
| ![Alex Li](https://avatars.githubusercontent.com/u/15884415?s=64&v=4) | [Alex Li](https://github.com/AlexV525) |
| ![Swain](https://avatars.githubusercontent.com/u/7621572?s=64&v=4) | [Swain](https://github.com/talisk) |
| ![harbor](https://avatars.githubusercontent.com/u/58758250?v=4) | [harbor](https://github.com/zzm990321) |
### About the third-party open-source project dependencies
- The TouchIndicator use the pub [touch_indicator](https://pub.dev/packages/touch_indicator), the ColorPicker use the pub [cyclop](https://pub.dev/packages/cyclop).
- We [fork](https://github.com/talisk/cyclop) the package [cyclop](https://pub.dev/packages/cyclop) and modify some code meet our functional needs. We should depend cyclop by pub version after the [PR](https://github.com/rxlabz/cyclop/pull/11) being merged.
## LICENSE
This project is licensed under the MIT License - visit the [LICENSE](./LICENSE) for details.
## Contact the author
**Maybe...**
- Found a bug in the code, or an error in the documentation
- Produces an exception when you use the UME
- UME is not compatible with the new version Flutter
- Have a good idea or suggestion
You can [submit an issue](./CONTRIBUTING_en.md#how-to-raise-an-issue) in any of the above situations.
**Maybe...**
- Communicate with the author
- Communicate with more community developers
- Cooperate with UME
Welcome to [Join the ByteDance Flutter Exchange Group](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=67au2f75-3783-41b0-8868-0fc0178f1fd8).
Or contact [author](mailto:sunkai.dev@bytedance.com).
@@ -0,0 +1 @@
include: package:lints/core.yaml
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="94" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="a">
<rect width="94" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#a)">
<path fill="#555" d="M0 0h59v20H0z"/>
<path fill="#88ca03" d="M59 0h35v20H59z"/>
<path fill="url(#b)" d="M0 0h94v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
<text x="305" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">coverage</text>
<text x="305" y="140" transform="scale(.1)" textLength="490">coverage</text>
<text x="755" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">91%</text>
<text x="755" y="140" transform="scale(.1)" textLength="250">91%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,3 @@
## 0.0.1
* First version.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 ByteDance Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,3 @@
# flutter_ume_kit_channel_monitor
Used to monitor channel transmissions in Flutter.
@@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
@@ -0,0 +1,3 @@
library flutter_ume_kit_channel_monitor;
export 'src/channel_plugin.dart';
@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'package:flutter_ume/flutter_ume.dart';
import 'package:flutter_ume_kit_channel_monitor/src/ui/channel_pages.dart';
import 'dart:convert';
import 'core/channel_binding.dart';
import 'icon.dart' as icon;
class ChannelPlugin extends Pluggable {
ChannelPlugin() {
ChannelBinding.ensureInitialized();
}
@override
Widget buildWidget(BuildContext? context) {
return const ChannelPages();
}
@override
String get displayName => 'Channel Monitor';
@override
ImageProvider<Object> get iconImageProvider =>
MemoryImage(base64Decode(icon.iconData));
@override
String get name => 'Channel Monitor';
@override
void onTrigger() {}
}
@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_ume_kit_channel_monitor/src/core/ume_binary_messenger.dart';
class ChannelBinding extends WidgetsFlutterBinding {
static WidgetsBinding? ensureInitialized() {
if (WidgetsBinding.instance == null) {
// make sure init this before WidgetsFlutterBinding ensureInitialized called
ChannelBinding();
}
return WidgetsBinding.instance;
}
@override
@protected
// 替换 BinaryMessenger
BinaryMessenger createBinaryMessenger() {
return UmeBinaryMessenger.binaryMessenger;
}
}
@@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:ui' as ui;
import 'package:flutter_ume_kit_channel_monitor/src/core/channel_info_model.dart';
import 'package:flutter_ume_kit_channel_monitor/src/core/channel_store.dart';
class _ChannelController {
final StandardMethodCodec codec = const StandardMethodCodec();
void trackChannelEvent(String channel, DateTime sendTime, bool send,
{ByteData? data,
MessageHandler? handler,
ui.PlatformMessageResponseCallback? callback}) {
MethodCall call = const MethodCall('unknown');
try {
call = codec.decodeMethodCall(data);
} catch (e) {
debugPrint('decode data failed, caused by: $e');
debugPrint('data: ${data.toString()}');
}
final ChannelInfoModel model = ChannelInfoModel(
type: ChannelType.method,
channelName: channel,
direction: send
? TransDirection.flutterToNative
: TransDirection.nativeToFlutter,
methodName: call.method,
timestamp: sendTime,
duration: DateTime.now().difference(sendTime),
sendDataSize: send ? (data?.elementSizeInBytes ?? 0) : 0,
sendData: send ? call.arguments : null,
receiveData: send ? null : call.arguments,
receiveDataSize: send ? 0 : (data?.elementSizeInBytes ?? 0),
);
channelStore.saveChannelInfo(model);
}
}
_ChannelController channelController = _ChannelController();
@@ -0,0 +1,45 @@
class ChannelInfoModel {
// channel 传输方向
final TransDirection direction;
// channel 名称
final String channelName;
// 方法名称
final String methodName;
// channel 类型(Method / Event / Basic
final ChannelType type;
// 发送时间
final DateTime timestamp;
// 发送耗时
final Duration duration;
// 输入数据大小(可能为空)
final int? sendDataSize;
// 返回数据大小(可能为空)
final int? receiveDataSize;
// 是否为系统channel
bool get isSystemChannel => methodName.contains('flutter/');
// 发送数据内容(可能为空)
final dynamic sendData;
// 接收的数据内容(可能为空)
final dynamic receiveData;
ChannelInfoModel({
required this.channelName,
required this.direction,
required this.methodName,
required this.timestamp,
required this.duration,
required this.sendDataSize,
required this.type,
this.receiveDataSize,
this.sendData,
this.receiveData,
});
@override
String toString() {
return 'ChannelInfoModel{direction: $direction, channelName: $channelName, methodName: $methodName, type: $type, timestamp: $timestamp, duration: $duration, sendDataSize: $sendDataSize, receiveDataSize: $receiveDataSize, sendData: $sendData, receiveData: $receiveData}';
}
}
enum TransDirection { flutterToNative, nativeToFlutter }
enum ChannelType { event, method, basic }
@@ -0,0 +1,39 @@
import 'dart:async';
import 'package:flutter_ume_kit_channel_monitor/src/core/channel_info_model.dart';
import 'package:rxdart/rxdart.dart';
class _ChannelStore {
final BehaviorSubject<List<String>> _orderedChannelNamePublisher =
BehaviorSubject();
final Map<String, List<ChannelInfoModel>> _orderedChannelEvents = {};
Stream<List<String>> get channelNamePublisher =>
_orderedChannelNamePublisher.stream;
void saveChannelInfo(ChannelInfoModel model) {
if (_orderedChannelEvents[model.channelName] == null) {
_orderedChannelEvents[model.channelName] = [];
}
_orderedChannelEvents[model.channelName]!.add(model);
refresh();
}
void getChannelByName(String name, Sink sink) {
if (name == '') {
return;
}
sink.add(_orderedChannelEvents[name]);
}
void clearChannelRecords() {
refresh();
}
void refresh() {
_orderedChannelNamePublisher.add(_orderedChannelEvents.keys.toList());
}
}
_ChannelStore channelStore = _ChannelStore();
@@ -0,0 +1,88 @@
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_ume_kit_channel_monitor/src/core/channel_controller.dart';
// 在 _DefaultBinaryMessenger 的基础上增加数据监控
class UmeBinaryMessenger extends BinaryMessenger {
static UmeBinaryMessenger binaryMessenger = UmeBinaryMessenger._();
UmeBinaryMessenger._();
@override
Future<void> handlePlatformMessage(String channel, ByteData? data,
ui.PlatformMessageResponseCallback? callback) async {
DateTime now = DateTime.now();
ui.channelBuffers.push(channel, data, (ByteData? data) {
if (callback != null) {
callback(data);
}
channelController.trackChannelEvent(channel, now, false,
data: data, callback: callback);
// print(
// '\n handlePlatformMessage: channel: $channel \n data:${data.toString()} \n');
});
}
@override
Future<ByteData?>? send(String channel, ByteData? message) {
DateTime now = DateTime.now();
final Completer<ByteData?> completer = Completer<ByteData?>();
// ui.PlatformDispatcher.instance is accessed directly instead of using
// ServicesBinding.instance.platformDispatcher because this method might be
// invoked before any binding is initialized. This issue was reported in
// #27541. It is not ideal to statically access
// ui.PlatformDispatcher.instance because the PlatformDispatcher may be
// dependency injected elsewhere with a different instance. However, static
// access at this location seems to be the least bad option.
// TODO(ianh): Use ServicesBinding.instance once we have better diagnostics
// on that getter.
ui.PlatformDispatcher.instance.sendPlatformMessage(channel, message,
(ByteData? reply) {
try {
completer.complete(reply);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context:
ErrorDescription('during a platform message response callback'),
));
}
});
channelController.trackChannelEvent(channel, now, true, data: message);
// print('\n send \n channel: $channel \n message: ${message.toString()} \n}');
return completer.future;
}
@override
void setMessageHandler(String channel, MessageHandler? handler) {
DateTime now = DateTime.now();
if (handler == null) {
ui.channelBuffers.clearListener(channel);
} else {
ui.channelBuffers.setListener(channel,
(ByteData? data, ui.PlatformMessageResponseCallback callback) async {
ByteData? response;
try {
response = await handler(data);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('during a platform message callback'),
));
} finally {
callback(response);
}
});
}
channelController.trackChannelEvent(channel, now, false, handler: handler);
// print(
// '\n setMessageHandler \n channel: $channel \n handler: ${handler.toString()} \n');
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,216 @@
import 'package:flutter/material.dart';
import 'package:flutter_ume_kit_channel_monitor/src/core/channel_info_model.dart';
import 'package:flutter_ume_kit_channel_monitor/src/core/channel_store.dart';
import 'package:flutter_ume_kit_channel_monitor/src/ui/template_ui.dart';
import 'package:rxdart/rxdart.dart';
class ChannelPages extends StatefulWidget {
const ChannelPages({Key? key}) : super(key: key);
@override
State<ChannelPages> createState() => _ChannelPagesState();
}
class _ChannelPagesState extends State<ChannelPages> {
int currentIndex = 0;
String currentChannel = '';
ChannelInfoModel? currentModel;
@override
Widget build(BuildContext context) {
return IndexedStack(
index: currentIndex,
children: [
buildOrderedChannels(context),
buildSingleChannelPage(),
buildChannelInfoPage(),
],
);
}
ChannelInfoPage buildChannelInfoPage() {
return ChannelInfoPage(
channelInfoModel: currentModel,
onBackPressed: () {
currentIndex = 1;
currentModel = null;
setState(() {});
},
);
}
SingleChannelPage buildSingleChannelPage() {
return SingleChannelPage(
title: currentChannel,
onBackPressed: () {
currentIndex = 0;
currentChannel = '';
setState(() {});
},
onTap: (model) {
currentIndex = 2;
currentModel = model;
setState(() {});
},
);
}
Widget buildOrderedChannels(BuildContext context) {
return TemplatePageWidget(
title: 'Ordered Channels',
body: StreamBuilder<List<String>>(
stream: channelStore.channelNamePublisher,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final List<String> channels = snapshot.data as List<String>;
return ListView.builder(
padding: EdgeInsets.zero,
itemCount: channels.length,
itemBuilder: (context, index) {
return TemplateItemWidget(
title: channels[index],
onTap: () {
currentChannel = channels[index];
currentIndex = 1;
setState(() {});
});
});
}),
);
}
}
class SingleChannelPage extends StatefulWidget {
final String title;
final VoidCallback onBackPressed;
final OnChannelModelSelected onTap;
const SingleChannelPage({
Key? key,
required this.title,
required this.onBackPressed,
required this.onTap,
}) : super(key: key);
@override
State<SingleChannelPage> createState() => _SingleChannelPageState();
}
class _SingleChannelPageState extends State<SingleChannelPage> {
final BehaviorSubject<List<ChannelInfoModel>> _publisher = BehaviorSubject();
@override
void didUpdateWidget(SingleChannelPage oldWidget) {
super.didUpdateWidget(oldWidget);
channelStore.getChannelByName(widget.title, _publisher.sink);
}
@override
Widget build(BuildContext context) {
return TemplatePageWidget(
onBackPressed: widget.onBackPressed,
title: widget.title,
body: StreamBuilder<List<ChannelInfoModel>>(
stream: _publisher,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final List<ChannelInfoModel> channels =
snapshot.data as List<ChannelInfoModel>;
return ListView.builder(
padding: EdgeInsets.zero,
itemCount: channels.length,
itemBuilder: (context, index) {
return TemplateItemWidget(
title: channels[index].methodName,
onTap: () => widget.onTap(channels[index]),
);
});
}),
);
}
}
class ChannelInfoPage extends StatelessWidget {
final ChannelInfoModel? channelInfoModel;
final VoidCallback onBackPressed;
const ChannelInfoPage(
{Key? key, this.channelInfoModel, required this.onBackPressed})
: super(key: key);
@override
Widget build(BuildContext context) {
if (channelInfoModel == null) {
return Container();
}
ChannelInfoModel model = channelInfoModel!;
bool isFlutterToNative = model.direction == TransDirection.flutterToNative;
return TemplatePageWidget(
title: 'Method : ${model.methodName}',
body: SingleChildScrollView(
child: Table(
border: TableBorder.all(color: Colors.black),
children: [
buildCell('Channel Name', model.channelName),
buildCell('Channel Type',
'${model.type.toString().substring(12)} channel'),
buildCell(
'Is System Channel', model.isSystemChannel ? 'yes' : 'no'),
buildCell(
'Trans Direction', model.direction.toString().substring(15)),
buildCell('Time Cost \n(millisecond)',
model.timestamp.millisecond.toString()),
if (isFlutterToNative)
buildCell('Send Data Size', model.sendDataSize.toString()),
if (isFlutterToNative)
...buildDataTable('Send Data', model.sendData),
if (!isFlutterToNative)
buildCell('Receive Data Size', model.receiveDataSize.toString()),
if (!isFlutterToNative)
...buildDataTable('Receive Data', model.receiveData),
],
),
),
onBackPressed: onBackPressed,
);
}
List<TableRow> buildDataTable(String title, dynamic data) {
if (data is Map) {
return (data.keys
.map((keyword) => TableRow(children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Align(
alignment: Alignment.centerLeft,
child: Text(keyword.toString())),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(data[keyword].toString()),
),
]))
.toList())
..insert(0, buildCell(title, ''));
}
return [buildCell(title, data.toString())];
}
TableRow buildCell(String type, String desc) {
return TableRow(children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Align(alignment: Alignment.centerLeft, child: Text(type)),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(desc.toString()),
),
]);
}
}
typedef OnChannelModelSelected = void Function(ChannelInfoModel model);
@@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
class TemplatePageWidget extends StatelessWidget {
final String title;
final Widget body;
final VoidCallback? onBackPressed;
const TemplatePageWidget({
Key? key,
required this.title,
required this.body,
this.onBackPressed,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: MediaQuery.of(context).padding.top),
if (onBackPressed != null)
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onBackPressed,
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 4),
child: Icon(
Icons.navigate_before_rounded,
size: 42,
),
),
),
Expanded(
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildTitle(),
const SizedBox(height: 20),
buildBody(),
],
),
))
],
),
);
}
Expanded buildBody() {
return Expanded(
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(12)),
child: body),
);
}
Text buildTitle() {
return Text(title,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold));
}
}
class TemplateItemWidget extends StatelessWidget {
final String title;
final VoidCallback onTap;
const TemplateItemWidget({Key? key, required this.title, required this.onTap})
: super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 54,
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey.shade400))),
margin: const EdgeInsets.symmetric(horizontal: 8),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => onTap(),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: Text(title, overflow: TextOverflow.clip)),
const Icon(
Icons.navigate_next_rounded,
color: Colors.grey,
),
],
),
),
);
}
}
@@ -0,0 +1,21 @@
name: flutter_ume_kit_channel_monitor
description: channel monitor kit for flutter_ume
version: 0.0.1
homepage: https://github.com/bytedance/flutter_ume
environment:
sdk: ">=2.12.0 <4.0.0"
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
flutter_ume: ^1.0.1
rxdart: ^0.27.3
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ">=1.0.0 <3.0.0"
flutter:
@@ -0,0 +1,4 @@
# melos_managed_dependency_overrides: flutter_ume
dependency_overrides:
flutter_ume:
path: ../..
Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

@@ -0,0 +1,75 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 02c026b03cd31dd3f867e5faeb7e104cce174c5f
channel: unknown
project_type: package
@@ -0,0 +1,43 @@
# Changelog
## 1.1.0
* 增加 `consolePrint` 方法,用于适配其他日志库。
* Add `consolePrint` method to adapt other log libraries.
## 1.0.0
* 正式版
* Normal version.
## 1.0.0-dev.0
* 适配 Flutter 3
* Adapt Flutter 3
## 0.3.0
* 更新版本号
* Update version
## 0.2.1
* null-safety 正式版本
* Null-Safety formal version.
## 0.2.0-dev.0
* 适配 null-safety
* Adapted Null-Safety.
## 0.1.0
* 发布开源版本。
* Release opensource version.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 ByteDance Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,57 @@
# flutter_ume_kit_console
[flutter_ume](https://pub.dev/packages/flutter_ume) 是由字节跳动 Flutter Infra 团队出品的应用内调试工具平台。
flutter_ume_kit_console 是 flutter_ume 的日志查看插件包。接入方式请见 [flutter_ume](https://pub.dev/packages/flutter_ume)。
此插件无法直接监听 `print``developer.log`,需要使用 `debugPrint` 方法打印日志,或者结合 [logging](https://pub.dev/packages/logging)、[logger](https://pub.dev/packages/logger) 等日志库使用。
如果使用其他日志库,可以调用 `consolePrint` 将日志输出到应用内控制台。
```dart
// logging
Logger.root.onRecord.listen((record) {
consolePrint(record.message);
});
```
```dart
// logger
class UmeConsoleOutput extends LogOutput {
@override
void output(OutputEvent event) {
for (var line in event.lines) {
consolePrint(line);
}
}
}
```
----
[flutter_ume](https://pub.dev/packages/flutter_ume) is an in-app debug kits platform produced for Flutter apps by ByteDance Flutter Infra team.
flutter_ume_kit_console is the Console kits package of flutter_ume. Please visit [flutter_ume](https://pub.dev/packages/flutter_ume) for details.
This plugin cannot listen to `print` or `developer.log` directly. You need to use the `debugPrint` method to print logs, or use it with another log library such as [logging](https://pub.dev/packages/logging) or [logger](https://pub.dev/packages/logger).
If you use another log library, you can call `consolePrint` to print the log to the in-app console.
```dart
// logging
Logger.root.onRecord.listen((record) {
consolePrint(record.message);
});
```
```dart
// logger
class UmeConsoleOutput extends LogOutput {
@override
void output(OutputEvent event) {
for (var line in event.lines) {
consolePrint(line);
}
}
}
```
@@ -0,0 +1,170 @@
SF:lib/console/show_date_time_style.dart
DA:1,3
DA:3,2
DA:5,2
DA:7,2
DA:9,2
DA:11,2
DA:18,2
DA:20,2
DA:22,2
DA:24,2
DA:26,2
LF:11
LH:11
end_of_record
SF:lib/console/console_manager.dart
DA:10,6
DA:14,4
DA:16,4
DA:20,2
DA:22,2
DA:24,2
DA:25,2
DA:26,2
DA:27,2
DA:28,4
DA:30,0
DA:34,8
DA:35,6
DA:36,4
DA:38,2
DA:45,2
DA:47,2
DA:48,2
DA:49,6
DA:51,2
DA:56,2
DA:57,4
DA:58,2
DA:61,2
LF:24
LH:23
end_of_record
SF:lib/console/console_panel.dart
DA:16,1
DA:17,1
DA:20,1
DA:21,1
DA:23,1
DA:26,1
DA:28,2
DA:30,1
DA:33,0
DA:36,1
DA:39,0
DA:40,0
DA:42,0
DA:43,0
DA:55,1
DA:57,2
DA:58,1
DA:59,1
DA:60,1
DA:61,1
DA:62,1
DA:65,1
DA:67,1
DA:68,1
DA:69,3
DA:70,0
DA:71,0
DA:73,1
DA:74,2
DA:75,2
DA:77,2
DA:79,2
DA:80,3
DA:81,5
DA:82,1
DA:83,1
DA:84,0
DA:85,0
DA:86,0
DA:87,0
DA:89,3
DA:92,2
DA:93,2
DA:94,4
DA:99,1
DA:100,1
DA:101,4
DA:102,4
DA:103,3
DA:104,1
DA:106,3
DA:110,1
DA:112,1
DA:113,1
DA:115,10
DA:117,1
DA:119,9
DA:120,2
DA:122,1
DA:124,9
DA:126,1
DA:135,1
DA:137,1
DA:138,1
DA:140,2
DA:141,1
DA:142,1
DA:143,2
DA:144,1
DA:145,1
DA:148,1
DA:149,2
DA:150,1
DA:151,1
DA:152,1
DA:158,1
DA:160,8
DA:161,1
DA:172,1
DA:173,1
DA:177,1
DA:178,1
DA:179,1
DA:180,1
DA:181,2
DA:183,0
DA:185,2
DA:186,1
DA:188,1
DA:191,1
DA:195,1
DA:196,1
DA:197,1
DA:200,1
DA:204,1
DA:210,1
DA:211,1
DA:213,1
DA:217,1
DA:218,1
DA:220,1
DA:224,2
DA:225,1
DA:227,1
DA:231,1
DA:232,1
DA:234,1
DA:238,1
DA:243,1
DA:244,6
DA:245,2
DA:246,2
DA:247,2
DA:250,1
DA:251,2
DA:252,2
DA:253,1
DA:254,1
DA:257,1
DA:260,1
DA:261,2
DA:264,8
DA:265,3
LF:123
LH:111
end_of_record
@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="94" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="a">
<rect width="94" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#a)">
<path fill="#555" d="M0 0h59v20H0z"/>
<path fill="#88ca03" d="M59 0h35v20H59z"/>
<path fill="url(#b)" d="M0 0h94v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
<text x="305" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">coverage</text>
<text x="305" y="140" transform="scale(.1)" textLength="490">coverage</text>
<text x="755" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">91%</text>
<text x="755" y="140" transform="scale(.1)" textLength="250">91%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,66 @@
import 'dart:async';
import 'dart:collection';
import 'package:flutter/foundation.dart';
import 'package:tuple/tuple.dart';
const int maxLine = 1000;
class ConsoleManager {
static final Queue<Tuple2<DateTime, String>> _logData = Queue();
// ignore: close_sinks
static StreamController? _logStreamController;
static Queue<Tuple2<DateTime, String>> get logData => _logData;
static StreamController? get streamController => _getLogStreamController();
static DebugPrintCallback? _originalDebugPrint;
static StreamController? _getLogStreamController() {
if (_logStreamController == null) {
_logStreamController = StreamController.broadcast();
var transformer =
StreamTransformer<dynamic, Tuple2<DateTime, String>>.fromHandlers(
handleData: (str, sink) {
final now = DateTime.now();
if (str is String) {
sink.add(Tuple2(now, str));
} else {
sink.add(Tuple2(now, str.toString()));
}
});
_logStreamController!.stream.transform(transformer).listen((value) {
if (_logData.length < maxLine) {
_logData.addFirst(value);
} else {
_logData.removeLast();
}
});
}
return _logStreamController;
}
static redirectDebugPrint() {
if (_originalDebugPrint != null) return;
_originalDebugPrint = debugPrint;
debugPrint = (String? message, {int? wrapWidth}) {
ConsoleManager.streamController!.sink.add(message);
if (_originalDebugPrint != null) {
_originalDebugPrint!(message, wrapWidth: wrapWidth);
}
};
}
static clearLog() {
logData.clear();
_logStreamController!.add('UME CONSOLE == ClearLog');
}
@visibleForTesting
static clearRedirect() {
debugPrint = _originalDebugPrint!;
_originalDebugPrint = null;
}
}
@@ -0,0 +1,264 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:share/share.dart';
import 'package:tuple/tuple.dart';
import 'package:flutter_ume/flutter_ume.dart';
import 'package:flutter_ume_kit_console/console/console_manager.dart';
import 'package:flutter_ume_kit_console/console/icon.dart' as icon;
import 'package:flutter_ume/util/floating_widget.dart';
import 'package:flutter_ume/util/store_mixin.dart';
import 'package:flutter_ume_kit_console/console/show_date_time_style.dart';
class Console extends StatefulWidget implements PluggableWithStream {
Console({Key? key}) {
ConsoleManager.redirectDebugPrint();
}
@override
ConsoleState createState() => ConsoleState();
@override
Widget buildWidget(BuildContext? context) => this;
@override
ImageProvider<Object> get iconImageProvider => MemoryImage(icon.iconBytes);
@override
String get name => 'Console';
@override
String get displayName => 'Console';
@override
void onTrigger() {}
@override
Stream get stream => ConsoleManager.streamController!.stream;
@override
StreamFilter get streamFilter => (e) => true;
}
class ConsoleState extends State<Console>
with WidgetsBindingObserver, StoreMixin {
List<Tuple2<DateTime, String>> _logList = <Tuple2<DateTime, String>>[];
StreamSubscription? _subscription;
ScrollController? _controller;
ShowDateTimeStyle? _showDateTimeStyle;
bool _showFilter = false;
RegExp? _filterExp;
@override
void dispose() {
_subscription?.cancel();
_subscription = null;
_controller = null;
_showDateTimeStyle = ShowDateTimeStyle.datetime;
_showFilter = false;
super.dispose();
}
@override
void initState() {
super.initState();
_showDateTimeStyle = ShowDateTimeStyle.none;
fetchWithKey('console_panel_datetime_style').then((value) async {
if (value != null && value is int) {
_showDateTimeStyle = styleById(value);
} else {
_showDateTimeStyle = ShowDateTimeStyle.datetime;
await storeWithKey(
'console_panel_datetime_style', idByStyle(_showDateTimeStyle!));
}
setState(() {});
});
_controller = ScrollController();
_logList = ConsoleManager.logData.toList();
_subscription = ConsoleManager.streamController!.stream.listen((onData) {
if (mounted) {
if (_filterExp != null) {
_logList = ConsoleManager.logData.where((e) {
return _filterExp!.hasMatch(e.item1.toString()) ||
_filterExp!.hasMatch(e.item2);
}).toList();
} else {
_logList = ConsoleManager.logData.toList();
}
setState(() {});
_controller!.jumpTo(
_controller!.position.maxScrollExtent + 22); // 22 is a magic number
}
});
}
void _refreshConsole() {
if (_filterExp != null) {
_logList = ConsoleManager.logData.where((e) {
return _filterExp!.hasMatch(e.item1.toString()) ||
_filterExp!.hasMatch(e.item2);
}).toList();
} else {
_logList = ConsoleManager.logData.toList();
}
}
String _dateTimeString(int logIndex) {
String result = '';
switch (_showDateTimeStyle) {
case ShowDateTimeStyle.datetime:
result =
'${_logList[_logList.length - logIndex - 1].item1.toString().padRight(26, '0')}';
break;
case ShowDateTimeStyle.time:
result =
'${_logList[_logList.length - logIndex - 1].item1.toString().padRight(26, '0')}'
.substring(11);
break;
case ShowDateTimeStyle.timestamp:
result =
'${_logList[_logList.length - logIndex - 1].item1.millisecondsSinceEpoch}';
break;
case ShowDateTimeStyle.none:
result = '';
break;
default:
break;
}
return result;
}
@override
Widget build(BuildContext context) {
return FloatingWidget(
contentWidget: Container(
color: Colors.black,
child: Stack(children: [
ListView.builder(
controller: _controller,
itemCount: _logList.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.only(
left: 8, right: 8, top: 3, bottom: 3),
child: RichText(
text: TextSpan(children: [
TextSpan(
text: _dateTimeString(index),
style: TextStyle(
color: Colors.white60,
fontFamily: 'Courier',
fontSize: 16,
fontWeight: FontWeight.w400,
)),
TextSpan(
text:
'${_logList[_logList.length - index - 1].item2}',
style: TextStyle(
color: Colors.white,
fontFamily: 'Courier',
fontSize: 16,
fontWeight: FontWeight.w400,
)),
]),
),
);
},
),
if (_showFilter)
Positioned(
left: 0,
right: 0,
top: 0,
child: Container(
child: TextField(
onChanged: (value) {
if (value.isNotEmpty) {
_filterExp = RegExp(value);
} else {
_filterExp = null;
}
setState(() {});
_refreshConsole();
},
style: TextStyle(
fontSize: 16,
),
decoration: InputDecoration(
fillColor: Colors.white,
filled: true,
hintText: 'RegExp',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(50),
),
),
contentPadding: EdgeInsets.only(
top: 0,
bottom: 0,
),
prefixIcon: Icon(Icons.search),
),
),
),
),
])),
toolbarActions: [
Tuple3(
'Style',
Icon(
Icons.access_time,
size: 20,
),
_triggerShowDate),
Tuple3(
'Clear',
Icon(
Icons.do_not_disturb,
size: 20,
),
() => ConsoleManager.clearLog()),
Tuple3(
'Filter',
Icon(
Icons.search,
size: 20,
),
_triggerFilter),
Tuple3(
'Share',
Icon(
Icons.share,
size: 20,
),
_share),
],
);
}
void _triggerShowDate() async {
_showDateTimeStyle = styleById((idByStyle(_showDateTimeStyle!) + 1) % 4);
await storeWithKey(
'console_panel_datetime_style', idByStyle(_showDateTimeStyle!));
setState(() {});
}
void _triggerFilter() {
setState(() {
_showFilter = !_showFilter;
if (!_showFilter) {
_filterExp = null;
}
});
_refreshConsole();
}
Future<void> _share() async {
if (_logList.isEmpty) {
return;
}
final l = _logList.map((e) => '${e.item1.toString()} ${e.item2}').toList();
return Share.share("${l.join('\n')}");
}
}
@@ -0,0 +1,6 @@
import 'console_manager.dart';
/// Print the message to the console.
void consolePrint(String message) {
ConsoleManager.streamController!.add(message);
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,29 @@
enum ShowDateTimeStyle { datetime, time, timestamp, none }
ShowDateTimeStyle styleById(int id) {
switch (id) {
case 0:
return ShowDateTimeStyle.datetime;
case 1:
return ShowDateTimeStyle.time;
case 2:
return ShowDateTimeStyle.timestamp;
case 3:
return ShowDateTimeStyle.none;
default:
return ShowDateTimeStyle.datetime;
}
}
int idByStyle(ShowDateTimeStyle style) {
switch (style) {
case ShowDateTimeStyle.datetime:
return 0;
case ShowDateTimeStyle.time:
return 1;
case ShowDateTimeStyle.timestamp:
return 2;
case ShowDateTimeStyle.none:
return 3;
}
}
@@ -0,0 +1,4 @@
library flutter_ume_kit_console;
export 'console/console_panel.dart';
export 'console/console_print.dart';
@@ -0,0 +1,27 @@
name: flutter_ume_kit_console
description: Show debugPrint kit for flutter_ume.
version: 1.1.0
homepage: https://github.com/bytedance/flutter_ume
environment:
sdk: ">=2.12.0 <4.0.0"
flutter: ">=2.0.0"
dependencies:
flutter:
sdk: flutter
tuple: ^2.0.0
share: ^2.0.4
shared_preferences: ^2.0.6
flutter_ume: ">=1.0.0 <2.0.0"
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^5.0.12
flutter_coverage_badge:
git:
url: https://github.com/smileShirely/flutter_coverage_badge.git
ref: 59b7580f406bb712e9d9049c8c99212946e34f65
flutter:
@@ -0,0 +1,4 @@
# melos_managed_dependency_overrides: flutter_ume
dependency_overrides:
flutter_ume:
path: ../..
@@ -0,0 +1,75 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 02c026b03cd31dd3f867e5faeb7e104cce174c5f
channel: unknown
project_type: package
@@ -0,0 +1,37 @@
# Changelog
## 1.0.0
* 正式版
* Normal version.
## 1.0.0-dev.0
* 适配 Flutter 3
* Adapt Flutter 3
## 0.3.0
* 更新版本号
* Update version
## 0.2.1
* null-safety 正式版本
* Null-Safety formal version.
## 0.2.0-dev.0
* 适配 null-safety
* Adapted Null-Safety.
## 0.1.0
* 发布开源版本。
* Release opensource version.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 ByteDance Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,11 @@
# flutter_ume_kit_device
[flutter_ume](https://pub.dev/packages/flutter_ume) 是由字节跳动 Flutter Infra 团队出品的应用内调试工具平台。
flutter_ume_kit_device 是 flutter_ume 的设备信息插件包。接入方式请见 [flutter_ume](https://pub.dev/packages/flutter_ume)。
----
[flutter_ume](https://pub.dev/packages/flutter_ume) is an in-app debug kits platform produced for Flutter apps by ByteDance Flutter Infra team.
flutter_ume_kit_device is the Device Info kits package of flutter_ume. Please visit [flutter_ume](https://pub.dev/packages/flutter_ume) for details.
@@ -0,0 +1,160 @@
SF:lib/components/device_info/device_info_panel.dart
DA:11,1
DA:13,1
DA:14,1
DA:16,1
DA:19,1
DA:21,2
DA:23,1
DA:26,0
DA:29,1
DA:36,1
DA:38,1
DA:39,1
DA:42,1
DA:43,1
DA:44,1
DA:45,3
DA:46,2
DA:47,1
DA:48,3
DA:49,2
DA:50,1
DA:52,1
DA:53,2
DA:54,2
DA:56,2
DA:57,2
DA:60,1
DA:61,1
DA:62,2
DA:63,2
DA:64,2
DA:65,2
DA:66,2
DA:67,2
DA:68,2
DA:69,1
DA:70,1
DA:71,1
DA:72,1
DA:73,1
DA:74,1
DA:75,1
DA:76,1
DA:77,1
DA:78,1
DA:79,1
DA:80,1
DA:81,1
DA:82,1
DA:83,1
DA:84,1
DA:85,1
DA:86,1
DA:87,1
DA:91,1
DA:92,1
DA:93,1
DA:94,1
DA:95,1
DA:96,1
DA:97,1
DA:98,1
DA:99,1
DA:100,2
DA:101,2
DA:102,2
DA:103,2
DA:104,2
DA:108,1
DA:110,1
DA:113,1
DA:114,1
DA:115,1
DA:117,1
DA:120,1
DA:121,1
DA:123,1
DA:131,1
DA:132,1
DA:133,4
DA:134,1
DA:135,1
DA:136,2
LF:83
LH:82
end_of_record
SF:lib/components/cpu_info/cpu_info_page.dart
DA:9,1
DA:10,1
DA:16,1
DA:17,1
DA:19,1
DA:22,1
DA:25,0
DA:28,1
DA:31,1
DA:33,2
DA:39,1
DA:41,3
DA:42,1
DA:44,1
DA:45,1
DA:48,1
DA:50,1
DA:52,1
DA:53,2
DA:54,5
DA:55,5
DA:57,2
DA:58,2
DA:62,1
DA:64,1
DA:65,4
DA:68,1
DA:70,1
DA:71,2
DA:72,3
DA:73,3
DA:74,3
DA:75,3
DA:76,3
DA:77,3
DA:78,3
DA:79,3
DA:80,3
DA:81,3
DA:82,1
DA:84,2
DA:85,1
DA:86,1
DA:88,2
DA:89,1
DA:90,1
DA:92,2
DA:93,1
DA:94,1
DA:96,2
DA:97,1
DA:98,1
DA:100,2
DA:101,1
DA:104,1
DA:105,1
DA:106,3
DA:108,2
DA:109,2
DA:110,1
DA:111,2
DA:112,1
DA:113,1
DA:114,5
DA:115,5
DA:116,5
DA:119,3
DA:120,2
DA:121,1
LF:69
LH:68
end_of_record
@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="94" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="a">
<rect width="94" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#a)">
<path fill="#555" d="M0 0h59v20H0z"/>
<path fill="#4ecb0e" d="M59 0h35v20H59z"/>
<path fill="url(#b)" d="M0 0h94v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
<text x="305" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">coverage</text>
<text x="305" y="140" transform="scale(.1)" textLength="490">coverage</text>
<text x="755" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">98%</text>
<text x="755" y="140" transform="scale(.1)" textLength="250">98%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,123 @@
import 'package:flutter/material.dart';
import 'package:system_info/system_info.dart';
import 'package:flutter_ume/flutter_ume.dart';
import 'icon.dart' as icon;
import 'package:platform/platform.dart';
class CpuInfoPage extends StatefulWidget implements Pluggable {
CpuInfoPage({Key? key, this.child, this.platform = const LocalPlatform()})
: super(key: key);
final Platform platform;
final Widget? child;
@override
_CpuInfoPageState createState() => _CpuInfoPageState();
@override
Widget buildWidget(BuildContext? context) => this;
@override
String get name => 'CPUInfo';
@override
String get displayName => 'CPUInfo';
@override
void onTrigger() {}
@override
ImageProvider<Object> get iconImageProvider => MemoryImage(icon.iconBytes);
}
class _CpuInfoPageState extends State<CpuInfoPage> {
var _deviceInfo = <Map<String, String>>[];
@override
Widget build(BuildContext context) {
if (!widget.platform.isAndroid)
return Container(
color: Colors.white,
child: Center(
child: Text('Only available on Android device'),
),
);
return Container(
color: Colors.white,
child: SafeArea(
bottom: false,
child: ListView.separated(
itemBuilder: (ctx, index) => ListTile(
title: Text(_deviceInfo[index].keys.first),
trailing: Text(_deviceInfo[index].values.first),
),
separatorBuilder: (ctx, index) => Divider(),
itemCount: _deviceInfo.length)),
);
}
@override
void initState() {
super.initState();
if (widget.platform.isAndroid) _setupData();
}
_setupData() {
const int MEGABYTE = 1024 * 1024;
final deviceInfo = <Map<String, String>>[];
deviceInfo.addAll([
{'Kernel architecture': '${SysInfo.kernelArchitecture}'},
{'Kernel bitness': '${SysInfo.kernelBitness}'},
{'Kernel name': '${SysInfo.kernelName}'},
{'Kernel version': '${SysInfo.kernelVersion}'},
{'Operating system name': '${SysInfo.operatingSystemName}'},
{'Operating system ': '${SysInfo.operatingSystemVersion}'},
{'User directory': '${SysInfo.userDirectory}'},
{'User id': '${SysInfo.userId}'},
{'User name': '${SysInfo.userName}'},
{'User space bitness': '${SysInfo.userSpaceBitness}'},
{
'Total physical memory':
'${SysInfo.getTotalPhysicalMemory() ~/ MEGABYTE} MB'
},
{
'Free physical memory':
'${SysInfo.getFreePhysicalMemory() ~/ MEGABYTE} MB'
},
{
'Total virtual memory':
'${SysInfo.getTotalVirtualMemory() ~/ MEGABYTE} MB'
},
{
'Free virtual memory':
'${SysInfo.getFreeVirtualMemory() ~/ MEGABYTE} MB'
},
{
'Virtual memory size':
'${SysInfo.getVirtualMemorySize() ~/ MEGABYTE} MB'
},
]);
final processors = SysInfo.processors;
deviceInfo.add(
{'Number of processors': '${processors.length}'},
);
for (var processor in processors) {
deviceInfo.addAll([
{
'[${processors.indexOf(processor)}] Architecture':
'${processor.architecture}'
},
{'[${processors.indexOf(processor)}] Name': '${processor.name}'},
{'[${processors.indexOf(processor)}] Socket': '${processor.socket}'},
{'[${processors.indexOf(processor)}] Vendor': '${processor.vendor}'},
]);
}
Future.delayed(Duration(seconds: 1), () {
setState(() {
_deviceInfo = deviceInfo;
});
});
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,148 @@
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:platform/platform.dart';
import 'package:flutter_ume/flutter_ume.dart';
import 'icon.dart' as icon;
class DeviceInfoPanel extends StatefulWidget implements Pluggable {
final Platform platform;
const DeviceInfoPanel({this.platform = const LocalPlatform()});
@override
_DeviceInfoPanelState createState() => _DeviceInfoPanelState();
@override
Widget buildWidget(BuildContext? context) => this;
@override
ImageProvider<Object> get iconImageProvider => MemoryImage(icon.iconBytes);
@override
String get name => 'DeviceInfo';
@override
String get displayName => 'DeviceInfo';
@override
void onTrigger() {}
}
class _DeviceInfoPanelState extends State<DeviceInfoPanel> {
String _content = '';
@override
void initState() {
super.initState();
_getDeviceInfo();
}
void _getDeviceInfo() async {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
Map dataMap = Map();
if (widget.platform.isAndroid) {
AndroidDeviceInfo androidDeviceInfo = await deviceInfo.androidInfo;
dataMap = _readAndroidBuildData(androidDeviceInfo);
} else if (widget.platform.isIOS) {
IosDeviceInfo iosDeviceInfo = await deviceInfo.iosInfo;
dataMap = _readIosDeviceInfo(iosDeviceInfo);
}
StringBuffer buffer = StringBuffer();
dataMap.forEach((k, v) {
buffer.write('$k: $v\n');
});
_content = buffer.toString();
setState(() {});
}
Map<String, dynamic> _readAndroidBuildData(AndroidDeviceInfo build) {
return <String, dynamic>{
'version.securityPatch': build.version.securityPatch,
'version.sdkInt': build.version.sdkInt,
'version.release': build.version.release,
'version.previewSdkInt': build.version.previewSdkInt,
'version.incremental': build.version.incremental,
'version.codename': build.version.codename,
'version.baseOS': build.version.baseOS,
'board': build.board,
'bootloader': build.bootloader,
'brand': build.brand,
'device': build.device,
'display': build.display,
'fingerprint': build.fingerprint,
'hardware': build.hardware,
'host': build.host,
'id': build.id,
'manufacturer': build.manufacturer,
'model': build.model,
'product': build.product,
'supported32BitAbis': build.supported32BitAbis,
'supported64BitAbis': build.supported64BitAbis,
'supportedAbis': build.supportedAbis,
'tags': build.tags,
'type': build.type,
'isPhysicalDevice': build.isPhysicalDevice,
'androidId': build.id
};
}
Map<String, dynamic> _readIosDeviceInfo(IosDeviceInfo data) {
return <String, dynamic>{
'name': data.name,
'systemName': data.systemName,
'systemVersion': data.systemVersion,
'model': data.model,
'localizedModel': data.localizedModel,
'identifierForVendor': data.identifierForVendor,
'isPhysicalDevice': data.isPhysicalDevice,
'utsname.sysname': data.utsname.sysname,
'utsname.nodename': data.utsname.nodename,
'utsname.release': data.utsname.release,
'utsname.version': data.utsname.version,
'utsname.machine': data.utsname.machine,
};
}
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(left: 12, right: 12, top: 32, bottom: 32),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.0),
color: Colors.black.withOpacity(0.85),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
'Device Info',
textScaleFactor: 1.15,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.red),
)),
Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height - 150),
child: SingleChildScrollView(
physics: BouncingScrollPhysics(),
child: Text(_content,
style: const TextStyle(
fontSize: 15,
color: Colors.white,
fontWeight: FontWeight.w500,
),
strutStyle:
const StrutStyle(forceStrutHeight: true, height: 2)),
),
),
],
),
);
}
}
@@ -0,0 +1,6 @@
import 'dart:convert';
const iconData =
r'iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAANBElEQVR4Xu2dfYxcVRnG33Nndrd8tqFfhAJqNSSasmTvne1iJLpWQ/wANRj5o4qKgKTFhChRY4zYTRtDiUBjQQxpMFL/0DQpRoISASWSWrfZe7ct0ABagy2mEaoCC5TuzsxrBpco2N2Ze84zM+fcefbfnve55/ye++vdnW5njPCLBEhgTgKGbEiABOYmQEF4d5DAPAQoCG8PEqAgvAdIwI4AnyB23DjVIwQoSI8UzWPaEaAgdtw41SMEKEiPFM1j2hGgIHbcONUjBChIjxTNY9oRoCB23DjVIwQoSI8UzWPaEaAgdtw41SMECi1IkiR91Wp1WalUOt8Yc54xpt4jvfbcMVU1UtV/lkqlx/v7+w/u2rVrCgGhsIIkSbJERH4gIh8TkYUIWMwIhsBBVf1ylmW/ExF12XUhBZmVY6+IrHCBw9mgCTTE2Coi30rT9FXbkxROkJGRkdNnZmYeMsYM20LhXGEIVFX18izL7rU9UeEESZLkehHZYguEc4Uj8Fi1Wv3ovn37/mZzskIJMjg4eEq5XL7FGHOtDQzOFJLAERH5Ypqmv7E5XaEEGR4ePqdWq91tjPmwDQzOFJbA+jRN77Q5XaEEGRoaek8URb8VkeU2MDhTWAI3pmm60eZ0hRKkUqmsUtVHRGSxDQzOFJOAqm7IsmzM5nQUxIYaZ4IiQEFm6+ITJKj7tmObpSAUpGM3W4gXoiAUJMT7tmN7piBugjxijNlijHmhVqud1LHWeCEbAhpF0WmquiPPMAVxEERVb86y7Jt5gHNt9wisXr16ca1WO5pnBxTEQRBjzNjExMSGPMC5tnsERkZGller1cavjZRa3QUFoSCt3ivBr6MgDhXavMzLJ4gD8C6MUhAH6BTEAV4goxTEoSgK4gAvkFEK4lAUBXGAF8goBXEoioI4wAtklII4FEVBHOAFMkpBHIqiIA7wAhmlIA5FURAHeIGMUhCHoiiIA7xARimIQ1EUxAFeIKMUxKEoCuIAL5BRCuJQFAVxgBfIKAVxKIqCOMALZJSCOBRFQRzgBTJKQRyKoiAO8AIZpSAORVEQB3iBjFIQh6IoiAO8QEYpiENRFMQBXiCjFMShKAriAC+QUQriUBQFcYAXyCgFcSiKgjjAC2SUgjgURUEc4AUySkEciqIgDvACGaUgDkVREAd4gYxSEIeiKIgDvEBGgxUkjuMLRWSNMaaiqmd0g3cURaeo6gUi0pfj+n9V1WdyrEcvPRhF0Z5SqXT/+Pj4s+jwouUFJ8jw8PCZqnqHql5WtDI6fJ5XRGTDypUrb9uxY0etw9cO5nJBCdL42OW+vr4DInJuMIT93+idaZqu93+b3dlhUIIkSbJNRK7qDqriXlVVP5RlWePTevn1FgLBCJIkycki0vi2gF9gAqr6yyzLPgmOLURcSIJcJCKPFoK6f4c4lKbp2/zbVvd3FIwgcRxfY4y5q/vIirmDY8eOnXbgwIGXi3k6+1PNfsLU373/AJ04jq8zxtxuf1ROzkegXq8vm5ycfJ6U3kxgaGhoaRRFRyhIj98ZFOTEN0BI32LxCdJGiSkIBWnj7RV+NAWhIOHfxW08AQUJXJAkSdaJyA/z3COvvH2VaKmcZ6QQa/teOioDz+f+NaulaZrm+jzwQsBqcohC/wzyxNh9Uj11US/0+KYzLtm1U1bsvC3XufkECfwJYvMyLwVp3REKQkFav1sCX8knCK5AfouFY+lNEgXBVUFBcCy9SaIguCooCI6lN0kUBFcFBcGx9CaJguCqoCA4lt4kURBcFRQEx9KbJAqCq4KC4Fh6k0RBcFVQEBxLb5IoCK4KCoJj6U0SBcFVQUFwLL1JoiC4KigIjqU3SRQEVwUFwbH0JomC4KqgIDiW3iRREFwVFATH0pskCoKrgoLgWHqTREFwVVAQHEtvkigIrgoKgmPpTRIFwVVBQXAsvUmiILgqKAiOpTdJFARXBQXBsfQmiYLgqqAgOJbeJFEQXBUUBMfSmyQKgquCguBYepNEQXBVUBAcS2+SKAiuCgqCY+lNEgXBVUFBcCy9SaIguCooCI6lN0kUBFcFBcGx9CaJguCqoCA4lt4kURBcFRQEx9KbJAqCq4KC4Fh6k0RBcFVQEBxLb5IoCK4KCoJj6U0SBcFVQUFwLL1JoiC4KigIjqU3SRQEVwUFwbH0JomC4KqgIDiW3iRREFwVFATH0pskCoKrgoLgWHqTREFwVVAQHEtvkigIrgoKgmPpTRIFwVVBQXAsvUmiILgqKAiOpTdJFARXBQXBsfQmiYLgqqAgOJbeJFEQXBUUBMfSmyQKgquCguBYepNEQXBVUBAcS2+SKAiuCgqCY+lNEgXBVUFBcCy9SaIguCooCI6lN0kUBFcFBcGx9CaJguCqoCA4lt4kURBcFRQEx9KbJAqCq4KC4Fh6k0RBcFVQEBxLb5IoCK4KCoJj6U0SBcFVQUFwLL1JoiC4KigIjqU3SRQEVwUFwbH0JomC4KqgIDiW3iRREFwVFATH0pskCoKrgoLgWHqTREFwVVAQHEtvkigIrgoKgmPpTRIFwVVBQXAsvUmiILgqKAiOpTdJFARXBQXBsfQmiYLgqqAgOJbeJFEQXBUUBMfSmyQKgquCguBYepNEQXBVUBAcS2+SKAiuCgqCY+lNEgXBVUFBcCy9SaIguCooCI6lN0kUBFcFBUGwPPaymOOvNU3ShWeImKjpOtcFnRQkSZIl9XrdzLdnVZ3Zu3fvC67n6sY8BXGgXt6+WUoP/VzMoadaSzFG6kMfkNqlX5La6Kdbm7FY1W5B4jheY4z5ioh8REROanGLh0Vkx8zMzOb9+/c/1+JM15dREIsKzOE/Sf/YFWIOPmYx/Z+R2iVXyswNt1vPzzfYTkGSJLlRRMYcNv5cvV5fOzk5+bBDRsdGKUhu1CoDV7/XSY43Llm9/Hqprvte7h00G2iXIEmSrBeRO5pdv4U/f1FEkjRND7awtqtLKEhO/OXtN0n57o05p+ZefnzbH0XfeT4srxHUDkFGR0cXTE1NHRGRRYjNqur2LMs+j8hqZwYFyUl34AtDYg49nXNq7uXVz35dqldvgOW1S5BKpXKFqt6D3Gh/f//Ju3fvPobMRGdRkDxEX52SBR8/M89E07X1kYtl+qZ7m67Ls6AdT5AkSb4vIjfk2UeztcaYkYmJiT3N1nXzzylIDvrmxX/IwKfOzTHRfGk9HpXpW+5vvjDHinYIEsfx1tlXrnLsZP6lqvr+LMsehQW2IYiC5IQ68ImzxUz9K+fU3Mvb8WpWOwSpVCpfVdVbYQcXkXK5fM74+PizyEx0FgXJSbRv87VSeuCnOafmXj6z8WdSu+hSWF67fgaJ4/jdxpgDqI2q6h+yLHsfKq9dORQkJ1nzl8dl4KqRnFMnXt74R8PpW38FyfrfkHY8QRr5SZLcJSLXIDasqpdlWYb94QuxsbdkUBALqKVf3yN9N6+zmPzviJ79Lpne/AvRs97hlHOi4XYJIiJRkiQPisgal00bY8YmJiawL925bGieWQpiCTba86CUf7xJoicncifULl77+j8Q6qKluWdbGWijIK9ffvYVra+JyLy/g3WCvf5ZRDalafqTVs7hwxoK4thC9FQmjW+7pIVfVpSFi6W+6kLRpSscrzr/eLsFmZVkoYh8UETOUtVmosyo6pOTk5O/b+vB2xBOQdoAtduRnRCk22fs1PUpSKdId/A6FAQHm4LgWHqTREFwVVAQHEtvkigIrgoKgmPpTRIFwVVBQXAsvUmiILgqKAiOpTdJFARXBQXBsfQmiYLgqqAgOJbeJFEQXBUUBMfSmyQKgquCguBYepNEQXBVDA4OLuvr62v8X/yW39BMVTdkWWb1zi/NfmdnzpPFcXydMSbX++Q8MXafVE+FvMcAjngHkmwEqdVq59VqtaMd2F5QlyiXy6dHUfRMnk0HI0ieQ3GtVMng/wg0nhotPznemKYgvJNIYB4CFIS3BwlQEN4DJGBHgE8QO26c6hECFKRHiuYxrQl8J03TTTbTLi/zfs4Ys93mopwhgQ4TWJem6Y9srukiyKAxZp/NRTlDAh0k8Fq9Xr/E9uMdrAWZfcuZxsc49XXwsLwUCeQiYIx5eHp6eq3thwS5CCKVSuUbqro51465mAQ6R+AlVb0yy7Kdtpd0EqRx0TiONxljvm27Ac6RQJsINN6w+btpmm51yXcWpHHxoaGhC6Io2iIioy6b4SwJOBI4LiKHVbXxs/G2LMsecMzL/U58816v8QmrIvIZY8xy1431wryqRsaYBaraeEd1yF9WvcDtRGc0xtRE5KiqPp2maYbiwFJQJJlTSAIUpJC18lAoAhQERZI5hSRAQQpZKw+FIkBBUCSZU0gCFKSQtfJQKAIUBEWSOYUkQEEKWSsPhSJAQVAkmVNIAhSkkLXyUCgCFARFkjmFJEBBClkrD4UiQEFQJJlTSAIUpJC18lAoAv8GH84Vqjytw1cAAAAASUVORK5CYII=';
final iconBytes = base64Decode(iconData);
@@ -0,0 +1,4 @@
library flutter_ume_kit_device;
export 'components/cpu_info/cpu_info_page.dart';
export 'components/device_info/device_info_panel.dart';
@@ -0,0 +1,29 @@
name: flutter_ume_kit_device
description: Device info kit for flutter_ume.
version: 1.0.0
homepage: https://github.com/bytedance/flutter_ume
publish_to: none
environment:
sdk: ">=2.12.0 <4.0.0"
flutter: ">=2.0.0"
dependencies:
flutter:
sdk: flutter
flutter_ume:
path: ../../../flutter_ume
system_info: ^1.0.1
device_info_plus: ^12.1.0
platform: ^3.0.0
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^5.0.12
flutter_coverage_badge:
git:
url: https://github.com/smileShirely/flutter_coverage_badge.git
ref: 59b7580f406bb712e9d9049c8c99212946e34f65
flutter:
@@ -0,0 +1,4 @@
# melos_managed_dependency_overrides: flutter_ume
dependency_overrides:
flutter_ume:
path: ../..
@@ -0,0 +1,75 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: d79295af24c3ed621c33713ecda14ad196fd9c31
channel: stable
project_type: package
@@ -0,0 +1,23 @@
# Changelog
## 1.0.0
* 正式版
* Normal version.
## 1.0.0-dev.0
* 适配 Flutter 3
* Adapt Flutter 3
## 0.3.0
* 更新版本号
* Update version
## 0.2.0-dev.0
* Initial release.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 ByteDance Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,3 @@
# flutter_ume_kit_dio
Dio kit for flutter_ume.
@@ -0,0 +1,229 @@
SF:lib/src/models/http_interceptor.dart
DA:10,0
DA:20,0
DA:22,0
DA:23,0
DA:26,0
DA:31,0
DA:32,0
DA:33,0
DA:36,0
DA:39,0
DA:40,0
DA:41,0
DA:42,0
LF:13
LH:0
end_of_record
SF:lib/src/pluggable.dart
DA:18,2
DA:19,4
DA:24,1
DA:25,1
DA:27,1
DA:29,2
DA:31,1
DA:34,0
DA:37,1
DA:40,1
LF:10
LH:9
end_of_record
SF:lib/src/constants/extensions.dart
DA:10,0
DA:11,0
DA:13,0
DA:14,0
DA:16,0
DA:17,0
DA:19,0
DA:20,0
LF:8
LH:0
end_of_record
SF:lib/src/containers/http_container.dart
DA:14,0
DA:18,2
DA:23,1
DA:24,7
DA:27,0
DA:29,0
DA:30,0
DA:31,0
DA:34,0
DA:35,0
DA:38,0
DA:39,0
DA:42,1
DA:43,1
DA:44,1
DA:47,0
DA:48,0
DA:49,0
DA:50,0
DA:53,0
DA:55,0
DA:56,0
LF:22
LH:6
end_of_record
SF:lib/src/instances.dart
DA:11,0
DA:13,3
LF:2
LH:1
end_of_record
SF:lib/src/widgets/pluggable_state.dart
DA:16,1
DA:20,1
DA:23,1
DA:24,1
DA:26,2
DA:33,1
DA:35,1
DA:37,3
DA:40,1
DA:42,1
DA:43,2
DA:44,1
DA:45,1
DA:50,0
DA:51,0
DA:52,0
DA:53,0
DA:54,0
DA:58,1
DA:59,1
DA:60,2
DA:61,1
DA:68,1
DA:78,1
DA:80,2
DA:81,1
DA:82,1
DA:83,0
DA:84,0
DA:85,0
DA:86,0
DA:87,0
DA:88,0
DA:89,0
DA:90,0
DA:92,0
DA:93,0
DA:99,0
DA:100,0
DA:101,0
DA:117,1
DA:119,1
DA:121,1
DA:122,3
DA:123,1
DA:125,1
DA:126,1
DA:128,4
DA:130,1
DA:134,2
DA:136,1
DA:137,1
DA:138,1
DA:140,1
DA:141,1
DA:143,1
DA:145,3
DA:147,1
DA:148,1
DA:150,1
DA:156,1
DA:157,1
DA:158,2
DA:159,1
DA:172,0
DA:175,0
DA:179,0
DA:180,0
DA:186,0
DA:188,0
DA:189,0
DA:192,0
DA:193,0
DA:196,0
DA:198,0
DA:201,0
DA:204,0
DA:207,0
DA:210,0
DA:213,0
DA:214,0
DA:217,0
DA:220,0
DA:223,0
DA:230,0
DA:233,0
DA:236,0
DA:237,0
DA:238,0
DA:240,0
DA:244,0
DA:245,0
DA:246,0
DA:248,0
DA:251,0
DA:252,0
DA:253,0
DA:254,0
DA:262,0
DA:263,0
DA:264,0
DA:265,0
DA:267,0
DA:272,0
DA:273,0
DA:274,0
DA:276,0
DA:277,0
DA:282,0
DA:283,0
DA:287,0
DA:289,0
DA:294,0
DA:295,0
DA:296,0
DA:297,0
DA:301,0
DA:303,0
DA:305,0
DA:306,0
DA:307,0
DA:308,0
DA:309,0
DA:311,0
DA:320,0
DA:322,0
DA:324,0
DA:326,0
DA:328,0
DA:330,0
DA:331,0
DA:333,0
DA:334,0
DA:343,0
DA:348,0
DA:354,0
DA:355,0
DA:356,0
DA:357,0
DA:358,0
DA:361,0
DA:366,0
DA:369,0
DA:370,0
DA:372,0
DA:374,0
DA:382,0
DA:386,0
DA:387,0
DA:388,0
LF:150
LH:46
end_of_record
@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="94" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="a">
<rect width="94" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#a)">
<path fill="#555" d="M0 0h59v20H0z"/>
<path fill="#e05d44" d="M59 0h35v20H59z"/>
<path fill="url(#b)" d="M0 0h94v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
<text x="305" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">coverage</text>
<text x="305" y="140" transform="scale(.1)" textLength="490">coverage</text>
<text x="755" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">30%</text>
<text x="755" y="140" transform="scale(.1)" textLength="250">30%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,4 @@
library flutter_ume_kit_dio;
export 'src/models/http_interceptor.dart';
export 'src/pluggable.dart';
@@ -0,0 +1,6 @@
///
/// [Author] Alex (https://github.com/AlexV525)
/// [Date] 2021/8/6 13:25
///
const String DIO_EXTRA_START_TIME = 'ume_start_time';
const String DIO_EXTRA_END_TIME = 'ume_end_time';
@@ -0,0 +1,21 @@
///
/// [Author] Alex (https://github.com/AlexV525)
/// [Date] 2021/8/6 13:58
///
import 'package:dio/dio.dart' show Response;
import 'constants.dart';
extension ResponseExtension on Response<dynamic> {
int get startTimeMilliseconds =>
requestOptions.extra[DIO_EXTRA_START_TIME] as int;
int get endTimeMilliseconds =>
requestOptions.extra[DIO_EXTRA_END_TIME] as int;
DateTime get startTime =>
DateTime.fromMillisecondsSinceEpoch(startTimeMilliseconds);
DateTime get endTime =>
DateTime.fromMillisecondsSinceEpoch(endTimeMilliseconds);
}
@@ -0,0 +1,58 @@
///
/// [Author] Alex (https://github.com/AlexV525)
/// [Date] 4/13/21 2:49 PM
///
import 'dart:math' as math;
import 'package:flutter/widgets.dart';
import 'package:dio/dio.dart' show Response;
/// Implements a [ChangeNotifier] to notify listeners when new responses
/// were recorded. Use [page] to support paging.
class HttpContainer extends ChangeNotifier {
/// Store all responses.
List<Response<dynamic>> get requests => _requests;
final List<Response<dynamic>> _requests = <Response<dynamic>>[];
/// Paging fields.
int get page => _page;
int _page = 1;
final int _perPage = 10;
/// Return requests according to the paging.
List<Response<dynamic>> get pagedRequests {
return _requests.sublist(0, math.min(page * _perPage, _requests.length));
}
bool get _hasNextPage => _page * _perPage < _requests.length;
void addRequest(Response<dynamic> response) {
_requests.insert(0, response);
notifyListeners();
}
void loadNextPage() {
if (!_hasNextPage) {
return;
}
_page++;
notifyListeners();
}
void resetPaging() {
_page = 1;
notifyListeners();
}
void clearRequests() {
_requests.clear();
_page = 1;
notifyListeners();
}
@override
void dispose() {
_requests.clear();
super.dispose();
}
}
@@ -0,0 +1,14 @@
///
/// [Author] Alex (https://github.com/AlexV525)
/// [Date] 4/13/21 2:54 PM
///
import 'containers/http_container.dart';
/// The inner singleton instance to keep containers.
///
/// Currently we only have a http container here.
class InspectorInstance {
const InspectorInstance._();
static final HttpContainer httpContainer = HttpContainer();
}
@@ -0,0 +1,44 @@
///
/// [Author] Alex (https://github.com/AlexV525)
/// [Date] 4/13/21 1:58 PM
///
import 'package:dio/dio.dart';
import '../constants/constants.dart';
import '../instances.dart';
int get _timestamp => DateTime.now().millisecondsSinceEpoch;
/// Implement a [Interceptor] to handle dio methods.
///
/// Main idea about this interceptor:
/// - Use [RequestOptions.extra] to store our timestamps.
/// - Add [DIO_EXTRA_START_TIME] when a request was requested.
/// - Add [DIO_EXTRA_END_TIME] when a response is respond or thrown an error.
/// - Deliver the [Response] to the container.
class UMEDioInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
options.extra[DIO_EXTRA_START_TIME] = _timestamp;
handler.next(options);
}
@override
void onResponse(
Response<dynamic> response,
ResponseInterceptorHandler handler,
) {
response.requestOptions.extra[DIO_EXTRA_END_TIME] = _timestamp;
InspectorInstance.httpContainer.addRequest(response);
handler.next(response);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
// Create an empty response with the [RequestOptions] for delivery.
// err.response ??= Response<dynamic>(requestOptions: err.requestOptions);
err.response!.requestOptions.extra[DIO_EXTRA_END_TIME] = _timestamp;
InspectorInstance.httpContainer.addRequest(err.response!);
handler.next(err);
}
}
@@ -0,0 +1,40 @@
///
/// [Author] Alex (https://github.com/AlexV525)
/// [Date] 2021/8/6 11:24
///
import 'package:flutter/material.dart';
import 'package:dio/dio.dart' show Dio;
import 'package:flutter_ume/core/pluggable.dart';
import 'models/http_interceptor.dart';
import 'widgets/icon.dart' as icon;
import 'widgets/pluggable_state.dart';
// TODO(Alex): Implement [PluggableStream] for dot features.
/// Implement a [Pluggable] to integrate with UME.
class DioInspector extends StatefulWidget implements Pluggable {
DioInspector({Key? key, required this.dio}) : super(key: key) {
dio.interceptors.add(UMEDioInterceptor());
}
final Dio dio;
@override
DioPluggableState createState() => DioPluggableState();
@override
ImageProvider<Object> get iconImageProvider => MemoryImage(icon.iconBytes);
@override
String get name => 'DioInspector';
@override
String get displayName => 'DioInspector';
@override
void onTrigger() {}
@override
Widget buildWidget(BuildContext? context) => this;
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,418 @@
///
/// [Author] Alex (https://github.com/AlexV525)
/// [Date] 2021/8/6 11:25
///
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import '../constants/extensions.dart';
import '../instances.dart';
import '../pluggable.dart';
const JsonEncoder _encoder = JsonEncoder.withIndent(' ');
ButtonStyle _buttonStyle(
BuildContext context, {
EdgeInsetsGeometry? padding,
}) {
return TextButton.styleFrom(
padding: padding ?? const EdgeInsets.symmetric(horizontal: 5, vertical: 3),
minimumSize: Size.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(999999),
),
backgroundColor: Theme.of(context).primaryColor,
// primary: Colors.white,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
);
}
class DioPluggableState extends State<DioInspector> {
@override
void initState() {
super.initState();
// Bind listener to refresh requests.
InspectorInstance.httpContainer.addListener(_listener);
}
@override
void dispose() {
InspectorInstance.httpContainer
..removeListener(_listener) // First, remove refresh listener.
..resetPaging(); // Then reset the paging field.
super.dispose();
}
/// Using [setState] won't cause too much performance regression,
/// since we've implemented the list with `findChildIndexCallback`.
void _listener() {
Future.microtask(() {
if (mounted &&
!context.debugDoingBuild &&
context.owner?.debugBuilding != true) {
setState(() {});
}
});
}
Widget _clearAllButton(BuildContext context) {
return TextButton(
onPressed: InspectorInstance.httpContainer.clearRequests,
style: _buttonStyle(
context,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: const <Widget>[
Text('Clear'),
Icon(Icons.cleaning_services, size: 14),
],
),
);
}
Widget _itemList(BuildContext context) {
final List<Response<dynamic>> requests =
InspectorInstance.httpContainer.pagedRequests;
final int length = requests.length;
if (length > 0) {
return CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(_, int index) {
final Response<dynamic> r = requests[index];
if (index == length - 2) {
InspectorInstance.httpContainer.loadNextPage();
}
return _ResponseCard(
key: ValueKey<int>(r.startTimeMilliseconds),
response: r,
);
},
childCount: length,
),
),
],
);
}
return const Center(
child: Text(
'Come back later...\n🧐',
style: TextStyle(fontSize: 28),
textAlign: TextAlign.center,
),
);
}
@override
Widget build(BuildContext context) {
return Material(
color: Colors.black26,
child: DefaultTextStyle.merge(
style: Theme.of(context).textTheme.bodyLarge,
child: Align(
alignment: Alignment.bottomCenter,
child: Container(
constraints: BoxConstraints.tightFor(
width: double.maxFinite,
height: MediaQuery.of(context).size.height / 1.25,
),
decoration: BoxDecoration(
borderRadius: const BorderRadius.vertical(
top: Radius.circular(20),
),
color: Theme.of(context).cardColor,
),
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
children: <Widget>[
const Spacer(),
Text(
'Dio Requests',
style: Theme.of(context).textTheme.bodySmall,
),
Expanded(
child: Align(
alignment: AlignmentDirectional.centerEnd,
child: _clearAllButton(context),
),
),
],
),
),
Expanded(
child: ColoredBox(
color: Theme.of(context).canvasColor,
child: _itemList(context),
),
),
],
),
),
),
),
);
}
}
class _ResponseCard extends StatefulWidget {
const _ResponseCard({
required Key? key,
required this.response,
}) : super(key: key);
final Response<dynamic> response;
@override
_ResponseCardState createState() => _ResponseCardState();
}
class _ResponseCardState extends State<_ResponseCard> {
final ValueNotifier<bool> _isExpanded = ValueNotifier<bool>(false);
@override
void dispose() {
_isExpanded.dispose();
super.dispose();
}
void _switchExpand() {
_isExpanded.value = !_isExpanded.value;
}
Response<dynamic> get _response => widget.response;
RequestOptions get _request => _response.requestOptions;
/// The start time for the [_request].
DateTime get _startTime => _response.startTime;
/// The end time for the [_response].
DateTime get _endTime => _response.endTime;
/// The duration between the request and the response.
Duration get _duration => _endTime.difference(_startTime);
/// Status code for the [_response].
int get _statusCode => _response.statusCode ?? 0;
/// Colors matching status.
Color get _statusColor {
if (_statusCode >= 200 && _statusCode < 300) {
return Colors.lightGreen;
}
if (_statusCode >= 300 && _statusCode < 400) {
return Colors.orangeAccent;
}
if (_statusCode >= 400 && _statusCode < 500) {
return Colors.purple;
}
if (_statusCode >= 500 && _statusCode < 600) {
return Colors.red;
}
return Colors.blueAccent;
}
/// The method that the [_request] used.
String get _method => _request.method;
/// The [Uri] that the [_request] requested.
Uri get _requestUri => _request.uri;
String? get _requestHeadersBuilder {
final Map<String, List<String>> map = _request.headers.map(
(key, value) => MapEntry(
key,
value is Iterable ? value.map((v) => v.toString()).toList() : [value],
),
);
final Headers headers = Headers.fromMap(map);
if (headers.isEmpty) {
return null;
}
return '$headers';
}
/// Data for the [_request].
String? get _requestDataBuilder {
if (_request.data is Map) {
return _encoder.convert(_request.data);
}
return _request.data?.toString();
}
/// Data for the [_response].
String? get _responseDataBuilder {
final data = _response.data;
if (data == null) {
return null;
}
if (_response.data is Map) {
return _encoder.convert(_response.data);
}
return _response.data.toString();
}
String? get _responseHeadersBuilder {
if (_response.headers.isEmpty) {
return null;
}
return '${_response.headers}';
}
Widget _detailButton(BuildContext context) {
return TextButton(
onPressed: _switchExpand,
style: _buttonStyle(context),
child: const Text(
'Detail🔍',
style: TextStyle(fontSize: 12, height: 1.2),
),
);
}
Widget _infoContent(BuildContext context) {
return Row(
children: <Widget>[
Text(_startTime.hms()),
const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 5,
vertical: 1,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3),
color: _statusColor,
),
child: Text(
_statusCode.toString(),
style: const TextStyle(color: Colors.white, fontSize: 12),
),
),
const SizedBox(width: 6),
Text(
_method,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(width: 6),
Text('${_duration.inMilliseconds}ms'),
const Spacer(),
_detailButton(context),
],
);
}
Widget _detailedContent(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: _isExpanded,
builder: (_, bool value, __) {
if (!value) {
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_TagText(
tag: 'Request headers',
content: _requestHeadersBuilder,
),
_TagText(
tag: 'Request data',
content: _requestDataBuilder,
),
_TagText(
tag: 'Response body',
content: _responseDataBuilder,
),
_TagText(
tag: 'Response headers',
content: _responseHeadersBuilder,
),
],
),
);
},
);
}
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.all(8.0),
shadowColor: Theme.of(context).canvasColor,
elevation: 5,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_infoContent(context),
const SizedBox(height: 10),
_TagText(
tag: 'Uri',
content: '$_requestUri',
shouldStartFromNewLine: false,
),
_detailedContent(context),
],
),
),
);
}
}
class _TagText extends StatelessWidget {
const _TagText({
Key? key,
required this.tag,
this.content,
this.shouldStartFromNewLine = true,
}) : super(key: key);
final String tag;
final String? content;
final bool shouldStartFromNewLine;
TextSpan get span {
return TextSpan(
children: <TextSpan>[
TextSpan(
text: '$tag: ',
style: const TextStyle(fontWeight: FontWeight.bold),
),
if (shouldStartFromNewLine) TextSpan(text: '\n'),
TextSpan(text: content!),
],
);
}
@override
Widget build(BuildContext context) {
if (content == null) {
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: SelectableText.rich(span),
);
}
}
extension _DateTimeExtension on DateTime {
String hms([String separator = ':']) => '$hour$separator'
'${'$minute'.padLeft(2, '0')}$separator'
'${'$second'.padLeft(2, '0')}';
}
@@ -0,0 +1,26 @@
name: flutter_ume_kit_dio
description: Dio kit for flutter_ume.
version: 1.0.0
homepage: https://github.com/bytedance/flutter_ume
publish_to: none
environment:
sdk: ">=2.12.0 <4.0.0"
flutter: ">=2.0.0"
dependencies:
flutter:
sdk: flutter
flutter_ume:
path: ../../../flutter_ume
dio: ^5.4.3+1
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^5.0.12
flutter_coverage_badge:
git:
url: https://github.com/smileShirely/flutter_coverage_badge.git
ref: 59b7580f406bb712e9d9049c8c99212946e34f65
@@ -0,0 +1,4 @@
# melos_managed_dependency_overrides: flutter_ume
dependency_overrides:
flutter_ume:
path: ../..
@@ -0,0 +1,75 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 02c026b03cd31dd3f867e5faeb7e104cce174c5f
channel: unknown
project_type: package
@@ -0,0 +1,37 @@
# Changelog
## 1.0.0
* 正式版
* Normal version.
## 1.0.0-dev.0
* 适配 Flutter 3
* Adapt Flutter 3
## 0.3.0
* 修复分页错误 #28
* Fix DioInspector paging issue #28
## 0.2.1
* null-safety 正式版本
* Null-Safety formal version.
## 0.2.0-dev.0
* 适配 null-safety
* Adapted Null-Safety.
## 0.1.0
* 发布开源版本。
* Release opensource version.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 ByteDance Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,11 @@
# flutter_ume_kit_perf
[flutter_ume](https://pub.dev/packages/flutter_ume) 是由字节跳动 Flutter Infra 团队出品的应用内调试工具平台。
flutter_ume_kit_perf 是 flutter_ume 的性能插件包。接入方式请见 [flutter_ume](https://pub.dev/packages/flutter_ume)。
----
[flutter_ume](https://pub.dev/packages/flutter_ume) is an in-app debug kits platform produced for Flutter apps by ByteDance Flutter Infra team.
flutter_ume_kit_perf is the Performance kits package of flutter_ume. Please visit [flutter_ume](https://pub.dev/packages/flutter_ume) for details.
@@ -0,0 +1,279 @@
SF:lib/components/memory_info/memory_service.dart
DA:11,1
DA:12,1
DA:13,1
DA:14,1
DA:15,1
DA:16,1
DA:17,1
DA:18,1
DA:20,1
DA:21,1
DA:23,4
DA:26,1
DA:33,1
DA:45,1
DA:46,3
DA:47,2
DA:48,2
DA:49,2
DA:51,0
DA:52,0
DA:53,0
DA:55,0
DA:59,0
DA:61,0
DA:63,0
DA:64,0
DA:67,0
DA:69,0
DA:71,0
DA:72,0
DA:73,0
DA:74,0
DA:75,0
DA:76,0
DA:77,0
DA:79,0
DA:81,0
DA:82,0
DA:83,0
DA:84,0
DA:87,0
DA:88,0
DA:89,0
DA:92,0
DA:94,0
DA:97,0
DA:98,0
DA:99,0
DA:100,0
DA:101,0
DA:102,0
DA:105,0
DA:106,0
DA:107,0
DA:108,0
DA:109,0
DA:110,0
DA:113,0
DA:114,0
DA:115,0
DA:116,0
DA:117,0
DA:118,0
DA:121,1
DA:123,0
DA:124,0
DA:125,0
DA:127,2
DA:131,1
DA:133,1
DA:134,1
DA:135,0
DA:136,0
DA:138,0
DA:139,0
DA:143,2
DA:144,2
DA:146,1
DA:150,0
DA:154,0
DA:155,0
DA:156,0
DA:157,0
DA:159,0
LF:84
LH:26
end_of_record
SF:lib/components/performance/performance.dart
DA:8,1
DA:10,1
DA:13,1
DA:14,1
DA:15,3
DA:18,1
DA:21,1
DA:23,2
DA:25,1
DA:28,0
DA:31,1
LF:11
LH:10
end_of_record
SF:lib/components/memory_info/memory_info_page.dart
DA:9,2
DA:11,1
DA:13,1
DA:14,1
DA:15,1
DA:19,1
DA:22,1
DA:24,2
DA:26,1
DA:29,0
DA:32,1
DA:41,0
DA:45,2
DA:47,1
DA:48,1
DA:58,1
DA:60,1
DA:61,2
DA:62,0
DA:66,1
DA:67,1
DA:68,2
DA:69,2
DA:72,0
DA:73,0
DA:74,0
DA:75,0
DA:76,0
DA:77,0
DA:78,0
DA:82,1
DA:83,2
DA:84,1
DA:87,1
DA:90,1
DA:93,3
DA:94,1
DA:97,1
DA:100,1
DA:103,3
DA:104,1
DA:106,2
DA:107,1
DA:110,1
DA:112,1
DA:113,1
DA:114,1
DA:115,1
DA:116,1
DA:119,2
DA:123,1
DA:125,1
DA:127,1
DA:129,1
DA:130,1
DA:132,1
DA:133,1
DA:134,1
DA:135,1
DA:136,3
DA:137,1
DA:140,2
DA:141,1
DA:142,1
DA:143,2
DA:144,1
DA:147,2
DA:148,1
DA:151,1
DA:152,2
DA:153,0
DA:154,1
DA:155,2
DA:156,1
DA:159,2
DA:160,1
DA:162,1
DA:166,2
DA:170,1
DA:171,1
DA:172,1
DA:173,0
DA:174,0
DA:175,0
DA:177,0
DA:178,0
DA:179,0
DA:180,0
DA:181,0
DA:182,0
DA:184,0
DA:185,0
DA:186,0
DA:187,0
DA:188,0
DA:189,0
DA:190,0
DA:191,0
DA:192,0
DA:193,0
DA:198,3
DA:210,1
DA:217,1
DA:225,1
DA:226,1
DA:232,1
DA:234,1
DA:235,3
DA:238,1
DA:240,1
DA:241,1
DA:242,2
DA:243,2
DA:244,2
DA:246,2
DA:247,6
DA:251,1
DA:252,2
DA:253,3
DA:255,2
DA:256,2
DA:257,1
DA:264,1
DA:266,1
DA:272,1
DA:274,1
DA:276,1
DA:277,0
DA:278,0
DA:279,0
DA:280,1
DA:282,1
DA:283,3
DA:284,1
DA:285,1
DA:289,1
DA:295,0
DA:296,0
DA:297,0
DA:298,0
DA:304,0
DA:305,0
DA:312,0
DA:314,0
DA:316,0
DA:317,0
DA:318,0
DA:319,0
DA:321,0
DA:322,0
DA:323,0
DA:324,0
DA:326,0
DA:327,0
DA:331,0
DA:333,0
DA:334,0
DA:335,0
DA:336,0
DA:337,0
DA:338,0
DA:339,0
DA:340,0
DA:342,0
DA:343,0
DA:346,0
DA:350,0
DA:351,0
DA:352,0
DA:355,0
DA:359,0
DA:360,0
LF:172
LH:103
end_of_record
@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="94" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="a">
<rect width="94" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#a)">
<path fill="#555" d="M0 0h59v20H0z"/>
<path fill="#df6e3a" d="M59 0h35v20H59z"/>
<path fill="url(#b)" d="M0 0h94v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
<text x="305" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">coverage</text>
<text x="305" y="140" transform="scale(.1)" textLength="490">coverage</text>
<text x="755" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">52%</text>
<text x="755" y="140" transform="scale(.1)" textLength="250">52%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

File diff suppressed because one or more lines are too long
@@ -0,0 +1,362 @@
import 'package:flutter/material.dart';
import 'package:flutter_ume/flutter_ume.dart';
import 'memory_service.dart';
import 'icon.dart' as icon;
class MemoryInfoPage extends StatelessWidget implements Pluggable {
const MemoryInfoPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primaryColor: Colors.white),
home: _MemoryWidget(),
);
}
@override
Widget buildWidget(BuildContext? context) => this;
@override
ImageProvider<Object> get iconImageProvider => MemoryImage(icon.iconBytes);
@override
String get name => 'MemoryInfo';
@override
String get displayName => 'MemoryInfo';
@override
void onTrigger() {}
}
class _DetailModel {
final int? count;
final String? classId;
final String? className;
_DetailModel(this.count, this.classId, this.className);
}
class _MemoryWidget extends StatefulWidget {
_MemoryWidget({Key? key}) : super(key: key);
@override
_MemoryWidgetState createState() => _MemoryWidgetState();
}
class _MemoryWidgetState extends State<_MemoryWidget> {
MemoryService _memoryservice = MemoryService();
int _sortColumnIndex = 0;
bool? _checked = true;
@override
void initState() {
super.initState();
_memoryservice.getInfos(() {
setState(() {});
});
}
void _hidePrivateClass(bool? check) {
_checked = check;
_memoryservice.hidePrivateClasses(check!);
setState(() {});
}
void _enterDetailPage(_DetailModel detail) {
Navigator.of(context).push(MaterialPageRoute(builder: (ctx) {
return Scaffold(
body: _MemoryDetail(detail: detail, service: _memoryservice),
appBar: PreferredSize(
child: AppBar(elevation: 0.0, title: Text(detail.className!)),
preferredSize: Size.fromHeight(44)));
}));
}
Widget _header() {
return Column(mainAxisSize: MainAxisSize.min, children: [
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: 15, top: 10, bottom: 10),
child: Text("VM Info: ",
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
textAlign: TextAlign.left)),
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: 15, right: 5),
child: Text(_memoryservice.vmInfo)),
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: 15, bottom: 10),
child: Text("Memory Info: ",
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
textAlign: TextAlign.left)),
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: 15, right: 5),
child: Text(_memoryservice.memoryUseage)),
Padding(
padding: const EdgeInsets.only(left: 12.0),
child: Row(children: [
SizedBox(
height: 24,
width: 24,
child: Checkbox(
materialTapTargetSize: MaterialTapTargetSize.padded,
value: _checked,
onChanged: _hidePrivateClass)),
Padding(
padding: EdgeInsets.only(left: 5),
child: Text("Hide private class"))
]),
),
Padding(padding: EdgeInsets.only(top: 10))
]);
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: SafeArea(
bottom: false,
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
bottom: PreferredSize(
child: _PerRow(customColor: Color(0xFFF4F4F4), widgets: [
_DropButton(
title: "Size",
index: 0,
stateChanged: (index, descending) => _memoryservice
.sort((d) => d.accumulatedSize, descending,
() {
setState(() {
_sortColumnIndex = index;
});
}),
showArrow: _sortColumnIndex == 0),
_DropButton(
title: "Count",
index: 1,
stateChanged: (index, descending) =>
_memoryservice.sort(
(d) => d.instancesAccumulated, descending,
() {
setState(() {
_sortColumnIndex = index;
});
}),
showArrow: _sortColumnIndex == 1),
_DropButton(title: "ClassName")
]),
preferredSize: Size.fromHeight(44)),
expandedHeight: 310.0,
floating: true,
pinned: true,
flexibleSpace: FlexibleSpaceBar(background: _header()),
)
];
},
body: Scrollbar(
child: ListView.builder(
physics: BouncingScrollPhysics(),
itemBuilder: (_, index) {
var stats = _memoryservice.infoList[index];
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
_DetailModel detail = _DetailModel(
stats.instancesAccumulated,
stats.classRef!.id,
stats.classRef!.name);
_enterDetailPage(detail);
},
child: _PerRow(
darkColor: index % 2 == 0,
widgets: [
Text(
"${_memoryservice.byteToString(stats.accumulatedSize!)}",
style: TextStyle(color: Colors.black87)),
Text("${stats.instancesAccumulated}",
style: TextStyle(color: Colors.black87)),
Text("${stats.classRef!.name}",
style: TextStyle(color: Colors.black87)),
],
),
);
},
itemCount: _memoryservice.infoList.length),
),
),
),
),
);
}
}
typedef _DropState = void Function(int, bool);
class _DropButton extends StatefulWidget {
_DropButton(
{Key? key,
this.showArrow = false,
this.descending = true,
required this.title,
this.index = 0,
this.stateChanged})
: super(key: key);
final bool showArrow;
final bool descending;
final int index;
final String title;
final _DropState? stateChanged;
@override
__DropButtonState createState() => __DropButtonState();
}
class __DropButtonState extends State<_DropButton> {
bool _descending = false;
@override
void initState() {
super.initState();
_descending = widget.descending;
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
if (widget.showArrow) {
_descending = !_descending;
}
if (widget.stateChanged != null) {
widget.stateChanged!(widget.index, _descending);
}
});
},
child: Container(
child: Row(children: [
Text(widget.title,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
widget.showArrow
? Icon(_descending ? Icons.arrow_drop_down : Icons.arrow_drop_up)
: Container()
], mainAxisSize: MainAxisSize.min)),
);
}
}
class _PerRow extends StatelessWidget {
const _PerRow(
{Key? key, this.widgets, this.customColor, this.darkColor = false})
: super(key: key);
final List<Widget>? widgets;
final bool darkColor;
final Color? customColor;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(left: 15, right: 15),
color: this.customColor ??
(this.darkColor
? Colors.grey.withOpacity(0.2)
: Colors.grey.withOpacity(0.03)),
child: Row(
children: this
.widgets!
.map((e) => Expanded(
child: Align(
child: Padding(
padding: const EdgeInsets.only(top: 10, bottom: 10),
child: e),
alignment: Alignment.centerLeft)))
.toList()),
);
}
}
class _MemoryDetail extends StatefulWidget {
_MemoryDetail({Key? key, required this.detail, required this.service})
: assert(service != null),
assert(detail != null),
super(key: key);
final _DetailModel detail;
final MemoryService service;
@override
__MemoryDetailState createState() => __MemoryDetailState();
}
class __MemoryDetailState extends State<_MemoryDetail> {
String _textInfoO = "";
String _textInfoT = "";
@override
void initState() {
super.initState();
widget.service.getClassDetailInfo(widget.detail.classId!, (info) {
StringBuffer buffer = StringBuffer();
info?.propeties?.forEach((element) {
buffer.writeln(element.propertyStr);
});
_textInfoO = buffer.toString();
StringBuffer bf = StringBuffer();
info?.functions?.forEach((element) {
bf.writeln(element);
});
_textInfoT = bf.toString();
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 15, left: 15, right: 15),
child: _textInfoO.isEmpty && _textInfoT.isEmpty
? Center(
child: Text('The Object is Sentinel',
style: TextStyle(fontSize: 20)))
: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(bottom: 10),
child: Text("Property: ",
style: const TextStyle(
fontSize: 20, fontWeight: FontWeight.w500),
textAlign: TextAlign.left)),
Text(_textInfoO,
textAlign: TextAlign.left, style: TextStyle(fontSize: 16)),
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(bottom: 10),
child: Text("Function: ",
style: const TextStyle(
fontSize: 20, fontWeight: FontWeight.w500),
textAlign: TextAlign.left)),
Text(_textInfoT,
textAlign: TextAlign.left, style: TextStyle(fontSize: 16)),
],
)),
);
}
}
@@ -0,0 +1,159 @@
import 'package:vm_service/vm_service.dart';
import 'package:flutter_ume/flutter_ume.dart';
class Property {
final bool? isConst;
final bool? isStatic;
final bool? isFinal;
final String? type;
final String? name;
String get propertyStr {
StringBuffer val = StringBuffer();
if (this.isStatic!) {
val.write("static");
val.write(' ');
} else if (this.isConst!) {
val.write("const");
val.write(' ');
} else {
val.write('final');
val.write(' ');
}
return "${val.toString()} ${this.type} ${this.name}";
}
Property(this.isConst, this.isStatic, this.isFinal, this.type, this.name);
}
class ClsModel {
final List<Property>? propeties;
final List<String>? functions;
ClsModel({this.propeties, this.functions});
}
class MemoryService with VMServiceWrapper {
List<ClassHeapStats> infoList = [];
List<ClassHeapStats> allClasses = [];
String vmInfo = "";
String memoryUseage = "";
void getInfos(Function completion) async {
List results = await Future.wait([
serviceWrapper.getClassHeapStats(),
serviceWrapper.getMemoryUsage(),
serviceWrapper.getVM()
]);
_heapInfoList(results[0]);
_memoryUsed(results[1]);
_vmToInfo(results[2]);
completion();
}
void getInstanceIds(
String classId, int limit, Function(List<String?>) completion) async {
InstanceSet instanceSet = await serviceWrapper.getInstances(classId, limit);
List<String?> instanceIds =
instanceSet.instances!.map((e) => e.id).toList();
completion(instanceIds);
}
void getClassDetailInfo(
String classId, Function(ClsModel?) completion) async {
Class cls = await serviceWrapper.getObject(classId) as Class;
ClsModel? _clsModel;
if (cls.fields != null && cls.fields!.isNotEmpty) {
List<Property> properties = [];
List<String> functions = [];
cls.fields?.forEach((fieldRef) {
Property _property = Property(fieldRef.isConst, fieldRef.isStatic,
fieldRef.isFinal, fieldRef.declaredType!.name, fieldRef.name);
properties.add(_property);
});
for (var fucRef in cls.functions!) {
String? code;
Obj func = await serviceWrapper.getObject(fucRef.id!);
if (func is Func) {
code = func.code!.name;
if (func.code!.name!.contains("[Stub]")) {
continue;
}
code = code!.replaceAll('[Unoptimized] ', '');
code = code.replaceAll('[Optimized] ', '');
functions.add(code);
}
}
_clsModel = ClsModel(propeties: properties, functions: functions);
}
completion(_clsModel);
}
void _heapInfoList(List<ClassHeapStats> list) {
allClasses = list;
allClasses.sort((a, b) => b.accumulatedSize!.compareTo(a.accumulatedSize!));
infoList = allClasses
.where((element) => !element.classRef!.name!.startsWith("_"))
.toList();
}
void _vmToInfo(VM vm) {
StringBuffer buffer = StringBuffer();
buffer.writeln("Pid: ${vm.pid}");
buffer.writeln("CPU: ${vm.hostCPU}");
buffer.writeln("Version: ${vm.version}");
vmInfo = buffer.toString();
}
void _memoryUsed(MemoryUsage usage) {
StringBuffer buffer = StringBuffer();
buffer.writeln("ExternalUsage: ${byteToString(usage.externalUsage!)}");
buffer.writeln("HeapCapacity: ${byteToString(usage.heapCapacity!)}");
buffer.writeln("HeapUsage: ${byteToString(usage.heapUsage!)}");
memoryUseage = buffer.toString();
}
void hidePrivateClasses(bool hide) {
if (hide) {
infoList = allClasses
.where((element) => !element.classRef!.name!.startsWith("_"))
.toList();
} else {
infoList = allClasses;
}
}
void sort<T>(Comparable<T>? Function(ClassHeapStats d) getField,
bool descending, void Function() completion) {
s(List list) {
list.sort((a, b) {
final aValue = getField(a);
final bValue = getField(b);
return descending
? Comparable.compare(bValue!, aValue!)
: Comparable.compare(aValue!, bValue!);
});
}
s(infoList);
s(allClasses);
completion();
}
String byteToString(int size) {
const int m = 1024 * 1024;
const int k = 1024;
String resultSize = "";
if (size / m >= 1) {
resultSize = "${(size / m).toStringAsFixed(1)} M";
} else if (size / k >= 1) {
resultSize = "${(size / k).toStringAsFixed(1)} K";
} else {
resultSize = "$size B";
}
return resultSize;
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'package:flutter_ume/flutter_ume.dart';
import 'icon.dart' as icon;
class Performance extends StatelessWidget implements Pluggable {
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.topCenter,
margin: const EdgeInsets.only(top: 20),
child: SizedBox(
child: PerformanceOverlay.allEnabled(),
width: MediaQuery.of(context).size.width));
}
@override
Widget buildWidget(BuildContext? context) => this;
@override
ImageProvider<Object> get iconImageProvider => MemoryImage(icon.iconBytes);
@override
String get name => 'PerfOverlay';
@override
String get displayName => 'PerfOverlay';
@override
void onTrigger() {}
}
@@ -0,0 +1,4 @@
library flutter_ume_kit_perf;
export 'components/performance/performance.dart';
export 'components/memory_info/memory_info_page.dart';
@@ -0,0 +1,25 @@
name: flutter_ume_kit_perf
description: Performance kits for flutter_ume.
version: 1.0.0
homepage: https://github.com/bytedance/flutter_ume
environment:
sdk: ">=2.12.0 <4.0.0"
flutter: ">=2.0.0"
dependencies:
flutter:
sdk: flutter
vm_service: 14.2.1
flutter_ume: ">=1.0.0 <2.0.0"
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^5.0.12
flutter_coverage_badge:
git:
url: https://github.com/smileShirely/flutter_coverage_badge.git
ref: 59b7580f406bb712e9d9049c8c99212946e34f65
flutter:
@@ -0,0 +1,4 @@
# melos_managed_dependency_overrides: flutter_ume
dependency_overrides:
flutter_ume:
path: ../..

Some files were not shown because too many files have changed in this diff Show More