Commit dc9f2239 authored by nightfallsad's avatar nightfallsad Committed by GitHub

Merge pull request #6 from alibaba/master

merge code
parents 832193c1 126c10cc
os:
- linux
sudo: false
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- libstdc++6
# - fonts-droid
before_script:
- git clone https://github.com/flutter/flutter.git -b v1.12.13-hotfixes --depth 1
- ./flutter/bin/flutter doctor
script:
- ./flutter/bin/flutter test --coverage --coverage-path=lcov.info
after_success:
- bash <(curl -s https://codecov.io/bash)
cache:
directories:
- $HOME/.pub-cache
## 0.0.1 ## 1.12.13
Supported Flutter sdk 1.12.13
* TODO: Describe initial release. ## 1.9.1+2
## 0.1.5
The main changes are as following: Rename the version number and start supporting androidx by default, Based on the flutter 1.9.1 - hotfixs。
1. The new version do the page jump (URL route) based on the inherited FlutterViewController or Activity. The jump procedure will create new instance of FlutterView, while the old version just reuse the underlying FlutterView fixed bugs
2. Avoiding keeping and reusing the FlutterView, there is no screenshot and complex attach&detach logical any more. As a result, memory is saved and black or white-screen issue occured in old version all are solved.
3. This version also solved the app life cycle observation issue, we recommend you to use ContainerLifeCycle observer to listen the app enter background or foreground notification instead of WidgetBinding. ## 0.1.66
4. We did some code refactoring, the main logic became more straightforward.
Fixed bugs
## 0.1.64
Fixed bugs
## 0.1.63
android:
Fixed bugs
iOS:
no change
## 0.1.61
android:
Fixed bugs
iOS:
no change
## 0.1.60 ## 0.1.60
...@@ -30,21 +51,19 @@ ios: ...@@ -30,21 +51,19 @@ ios:
1.based on the v1.9.1+hotfixes branch of flutter 1.based on the v1.9.1+hotfixes branch of flutter
2.bugfixed 2.bugfixed
## 0.1.61
android:
Fixed bugs
iOS: ## 0.1.5
no change The main changes are as following:
1. The new version do the page jump (URL route) based on the inherited FlutterViewController or Activity. The jump procedure will create new instance of FlutterView, while the old version just reuse the underlying FlutterView
2. Avoiding keeping and reusing the FlutterView, there is no screenshot and complex attach&detach logical any more. As a result, memory is saved and black or white-screen issue occured in old version all are solved.
3. This version also solved the app life cycle observation issue, we recommend you to use ContainerLifeCycle observer to listen the app enter background or foreground notification instead of WidgetBinding.
4. We did some code refactoring, the main logic became more straightforward.
## 0.1.63 ## 0.0.1
android: * TODO: Describe initial release.
Fixed bugs
iOS:
no change
### API changes ### API changes
From the point of API changes, we did some refactoring as following: From the point of API changes, we did some refactoring as following:
......
### 在FlutterBoost下如何管理Flutter页面的生命周期?原生的Flutter的AppLifecycleState事件会不一致,比如ViewAppear会导致app状态suspending或者paused。混合栈怎么处理? ### 1. 在FlutterBoost下如何管理Flutter页面的生命周期?原生的Flutter的AppLifecycleState事件会不一致,比如ViewAppear会导致app状态suspending或者paused。混合栈怎么处理?
回答:在混合栈下,页面事件基于以下自定义的事件: 回答:在混合栈下,页面事件基于以下自定义的事件:
```dart ```dart
enum ContainerLifeCycle { enum ContainerLifeCycle {
...@@ -12,7 +12,7 @@ enum ContainerLifeCycle { ...@@ -12,7 +12,7 @@ enum ContainerLifeCycle {
} }
``` ```
对于页面事件重复,请参考下面的FAQ。 对于页面事件重复,请参考下面的FAQ。
### 如何判断flutter的widget或者container是当前可见的? ### 2. 如何判断flutter的widget或者container是当前可见的?
回答:有个api可以判断当前页面是否可见: 回答:有个api可以判断当前页面是否可见:
```dart ```dart
bool isTopContainer = FlutterBoost.BoostContainer.of(context).onstage bool isTopContainer = FlutterBoost.BoostContainer.of(context).onstage
...@@ -20,23 +20,38 @@ bool isTopContainer = FlutterBoost.BoostContainer.of(context).onstage ...@@ -20,23 +20,38 @@ bool isTopContainer = FlutterBoost.BoostContainer.of(context).onstage
传入你widget的context,就能判断你的widget是否是可见的 传入你widget的context,就能判断你的widget是否是可见的
基于这个API,可以判断你的widget是否可见,从而避免接收一些重复的生命周期消息。参考这个issue:https://github.com/alibaba/flutter_boost/issues/498 基于这个API,可以判断你的widget是否可见,从而避免接收一些重复的生命周期消息。参考这个issue:https://github.com/alibaba/flutter_boost/issues/498
### 您好,我想请教一下flutter_boost有关的问题:ABC三个都是flutter页面,从 A页面 -> B页面 -> C页面,当打开C页面时希望自动关掉B页面,当从C页面返回时直接返回A页面,可有什么方法? ### 3. 您好,我想请教一下flutter_boost有关的问题:ABC三个都是flutter页面,从 A页面 -> B页面 -> C页面,当打开C页面时希望自动关掉B页面,当从C页面返回时直接返回A页面,可有什么方法?
回答:你只需要操作Native层的UINavigationController里的vc数组就可以了。就如同平时你操作普通的UIViewController一样。因为FlutterBoost对Native层的FlutterViewController和Dart层的flutter page的生命周期管理是一致的,当FlutterViewController被销毁,其在dart层管理的flutter page也会自动被销毁。 回答:你只需要操作Native层的UINavigationController里的vc数组就可以了。就如同平时你操作普通的UIViewController一样。因为FlutterBoost对Native层的FlutterViewController和Dart层的flutter page的生命周期管理是一致的,当FlutterViewController被销毁,其在dart层管理的flutter page也会自动被销毁。
### 在ios中voice over打开,demo在点击交互会crash ### 3.1如果我进了若干个flutter页或native页面,想实现点一个按钮直接返回到首页。如何做?
回答:同第3条,flutter页面由混合容器管理。上层实现不需要过多考虑。通过操作Native层的UINavigationController里的vc数组就可以了。清除部分或全部这个数组,就能实现你返回到什么页。底层flutter页面的资源会自动按需释放
### 4. 在ios中voice over打开,demo在点击交互会crash;
回答:无障碍模式下目前Flutter Engine有bug,已经提交issue和PR给flutter啦。请参考这个issue:https://github.com/alibaba/flutter_boost/issues/488及其分析。提交给flutter的PR见这里:https://github.com/flutter/engine/pull/14155 回答:无障碍模式下目前Flutter Engine有bug,已经提交issue和PR给flutter啦。请参考这个issue:https://github.com/alibaba/flutter_boost/issues/488及其分析。提交给flutter的PR见这里:https://github.com/flutter/engine/pull/14155
### 似乎官方已经提供了混合栈的功能,参考这里:https://flutter.dev/docs/development/add-to-app; FlutterBoost是否有存在的必要? ### 5. 在ios模拟器下运行最新的flutter boost会闪退
回答:如上面第4条所说的,最新的flutter engine在voice over下有bug,会导致crash。因为模拟器下flutter默认会将voice over模式打开,所以其实就是辅助模式,这回触发上面的bug:“在ios中voice over打开,demo在点击交互会crash”。
可参考Engine的代码注释:
```c++
#if TARGET_OS_SIMULATOR
// There doesn't appear to be any way to determine whether the accessibility
// inspector is enabled on the simulator. We conservatively always turn on the
// accessibility bridge in the simulator, but never assistive technology.
platformView->SetSemanticsEnabled(true);
platformView->SetAccessibilityFeatures(flags);
```
### 6. 似乎官方已经提供了混合栈的功能,参考这里:https://flutter.dev/docs/development/add-to-app; FlutterBoost是否有存在的必要?
回答:官方的解决方案仅仅是在native侧对FlutterViewController和Flutterengine进行解耦,如此可以一个FlutterEngine切换不同的FlutterViewController或者Activity进行渲染。但其并未解决Native和Flutter页面混合的问题,无法保证两侧的页面生命周期一致。即使是Flutter官方针对这个问题也是建议使用FlutterBoost。 回答:官方的解决方案仅仅是在native侧对FlutterViewController和Flutterengine进行解耦,如此可以一个FlutterEngine切换不同的FlutterViewController或者Activity进行渲染。但其并未解决Native和Flutter页面混合的问题,无法保证两侧的页面生命周期一致。即使是Flutter官方针对这个问题也是建议使用FlutterBoost。
其差别主要有: 其差别主要有:
|*|FlutterBoost1.5 |Flutter官方方案 |其他框架| |*|FlutterBoost2.0 |Flutter官方方案 |其他框架|
|----|----|----|----| |----|----|----|----|
|是否支持混合页面之间随意跳转 |Y |N |Y| |是否支持混合页面之间随意跳转 |Y |N |Y|
|一致的页面生命周期管理(多Flutter页面) |Y |N |?| |一致的页面生命周期管理(多Flutter页面) |Y |N |?|
|是否支持页面间数据传递(回传等) |Y |N |N| |是否支持页面间数据传递(回传等) |Y |N |N|
|是否支持测滑手势 |Y |Y |Y| |是否支持测滑手势 |Y |Y |Y|
|是否支持跨页的hero动画 |N |Y |N| |是否支持跨页的hero动画 |Y |Y |N|
|内存等资源占用是否可控 |Y |Y |Y| |内存等资源占用是否可控 |Y |Y |Y|
|是否提供一致的页面route方案 |Y |Y |N| |是否提供一致的页面route方案 |Y |Y |N|
|iOS和Android能力及接口是否一致 |Y |N |N| |iOS和Android能力及接口是否一致 |Y |N |N|
...@@ -44,3 +59,41 @@ bool isTopContainer = FlutterBoost.BoostContainer.of(context).onstage ...@@ -44,3 +59,41 @@ bool isTopContainer = FlutterBoost.BoostContainer.of(context).onstage
|是否已经支持到View级别混合 |N |N |N| |是否已经支持到View级别混合 |N |N |N|
同时FlutterBoost也提供了一次性创建混合工程的命令:flutterboot。代码参考:https://github.com/alibaba-flutter/flutter-boot 同时FlutterBoost也提供了一次性创建混合工程的命令:flutterboot。代码参考:https://github.com/alibaba-flutter/flutter-boot
### 7. 如果我需要通过FlutterViewController再弹出一个新的但frame比较小的FlutterViewController,应该怎么实现?
回答:如果不加处理会遇到window大小变化的问题,但可以解决。具体可以参考这个issue:https://github.com/alibaba/flutter_boost/issues/435
### 8. Flutter ViewController如何设置横屏
VC设置横屏依赖于NavigationController或者rootVC。可以通过一下方式来设置:
1. dart层的SystemChrome.setPreferredOrientations函数并非直接设置转向,而是设置页面优先使用的转向(preferred)
2. app的转向控制除了info.plist的设置外,主要受UIWindow.rootViewController控制。大概过程是这样的:硬件检测到转向,就会调用UIWindow的转向函数,然后调用其rootViewController的shouldAutorotate判断是否需要自动转,然后取supportedInterfaceOrientations和info.plist中设置的交集来判断可否转
3. 对于UIViewController中的转向,也只在rootviewcontroller中才有效
举例如下,实现步骤可以这样:
1. 重写NavigationController:
```objc
-(BOOL)shouldAutorotate
{
// id currentViewController = self.topViewController;
//
//
// if ([currentViewController isKindOfClass:[FlutterViewController class]])
// return [currentViewController shouldAutorotate];
return YES;
}
-(UIInterfaceOrientationMask)supportedInterfaceOrientations
{
id currentViewController = self.topViewController;
if ([currentViewController isKindOfClass:[FlutterViewController class]]){
NSLog(@"[XDEBUG]----fvc supported:%ld\n",[currentViewController supportedInterfaceOrientations]);
return [currentViewController supportedInterfaceOrientations];
}
return UIInterfaceOrientationMaskAll;
}
```
2. 改dart层:因为SystemChrome.setPreferredOrientations的设置是全局的,但混合栈是多页面,所以在main函数中设置,后面在新建一个FlutterViewController时会被冲掉。为了解决这个问题,需要在每个dart页面的build处都加上这语句来设置每个页面能支持哪些转向类型
### 9. FlutterBoost for flutter1.12出现和surface相关的crash。可以参考这个issue:https://github.com/flutter/flutter/issues/52455
可能flutter engine的bug引起
[![Build Status](https://travis-ci.com/alibaba/flutter_boost.svg?branch=master)](https://travis-ci.com/alibaba/flutter_boost) [![pub package](https://img.shields.io/pub/v/flutter_boost.svg)](https://pub.dartlang.org/packages/flutter_boost) [![codecov](https://codecov.io/gh/alibaba/flutter_boost/branch/master/graph/badge.svg)](https://codecov.io/gh/alibaba/flutter_boost)
<p align="center"> <p align="center">
<img src="flutter_boost.png"> <img src="flutter_boost.png">
<b></b><br> <b></b><br>
...@@ -7,7 +9,7 @@ ...@@ -7,7 +9,7 @@
# Release Note # Release Note
Please checkout the release note for the latest 0.1.63 to see changes [0.1.63 release note](https://github.com/alibaba/flutter_boost/releases) Please checkout the release note for the latest 0.1.64 to see changes [0.1.64 release note](https://github.com/alibaba/flutter_boost/releases)
# FlutterBoost # FlutterBoost
A next-generation Flutter-Native hybrid solution. FlutterBoost is a Flutter plugin which enables hybrid integration of Flutter for your existing native apps with minimum efforts.The philosophy of FlutterBoost is to use Flutter as easy as using a WebView. Managing Native pages and Flutter pages at the same time is non-trivial in an existing App. FlutterBoost takes care of page resolution for you. The only thing you need to care about is the name of the page(usually could be an URL).  A next-generation Flutter-Native hybrid solution. FlutterBoost is a Flutter plugin which enables hybrid integration of Flutter for your existing native apps with minimum efforts.The philosophy of FlutterBoost is to use Flutter as easy as using a WebView. Managing Native pages and Flutter pages at the same time is non-trivial in an existing App. FlutterBoost takes care of page resolution for you. The only thing you need to care about is the name of the page(usually could be an URL). 
...@@ -20,24 +22,26 @@ You need to add Flutter to your project before moving on.The version of the flut ...@@ -20,24 +22,26 @@ You need to add Flutter to your project before moving on.The version of the flut
# boost version description # boost version description
1. 0.1.50 is based on the flutter v1.5.4-hotfixes branch, android if other flutter versions or branches will compile incorrectly | Flutter Boost Version | Support Flutter SDK Version | Description | Support AndroidX? |
| --------------------- | --------------------------- | ------------------------------------------------------------ | ------------------ |
2. 0.1.51--0.1.54 is a bugfix for 0.1.50 | 0.1.50 | 1.5.4-hotfixes | android if other flutter versions or branches will compile incorrectly. | No |
| 0.1.51-0.1.59 | 1.5.4-hotfixes | bugfix for 0.1.50. | No |
| 0.1.60 | 1.9.1-hotfixes | Android does not support andriodx if other flutter branches will compile incorrectly. | No |
3. 0.1.60 is based on the flutter v1.9.1-hotfixes branch. Android does not support andriodx if other flutter branches will compile incorrectly | 0.1.61-0.1.69 | 1.9.1-hotfixes | bugfix for 0.1.60. | No |
| 0.1.63 | 1.9.1-hotfixes | If other branches will compile incorrectly. Synchronize with the 0.1.60 code, and bugfix also merge to this branch. | No |
4. 0.1.61--0.1.62 is a bugfix for 0.1.60 | 1.9.1+2 | 1.9.1-hotfixes | Rename the version number and start supporting androidx by default | Yes |
| 1.12.13 | 1.12.13-hotfixes | supporting androidx | Yes |
5. Statement of support for androidx
Current androidx branch is v0.1.61-androidx-hotfixes
Is based on flutter v1.9.1-hotfixes branch, if other branches will compile incorrectly
Synchronize with the 0.1.63 code, and bugfix also merge to this branch.
| Flutter Boost branch | Support Flutter SDK Version | Description | Support AndroidX? |
| --------------------- | --------------------------- | ------------------------------------------------------------ | ------------------ |
| v1.9.1-hotfixes | 1.9.1-hotfixes | for androidx | Yes |
| task/task_v1.9.1_support_hotfixes| 1.9.1-hotfixes | for support | NO |
| v1.12.13-hotfixes | 1.12.13-hotfixes | for androidx | Yes |
| task/task_v1.12.13_support_hotfixes| 1.12.13-hotfixes | for support | NO |
# Getting Started # Getting Started
...@@ -47,19 +51,19 @@ You need to add Flutter to your project before moving on.The version of the flut ...@@ -47,19 +51,19 @@ You need to add Flutter to your project before moving on.The version of the flut
Open you pubspec.yaml and add the following line to dependencies: Open you pubspec.yaml and add the following line to dependencies:
support branch androidx branch
```json ```json
flutter_boost: flutter_boost:
git: git:
url: 'https://github.com/alibaba/flutter_boost.git' url: 'https://github.com/alibaba/flutter_boost.git'
ref: '0.1.63' ref: ' 1.12.13'
``` ```
androidx branch support branch
```json ```json
flutter_boost: flutter_boost:
git: git:
url: 'https://github.com/alibaba/flutter_boost.git' url: 'https://github.com/alibaba/flutter_boost.git'
ref: 'v0.1.61-androidx-hotfixes' ref: 'task/task_v1.12.13_support_hotfixes'
``` ```
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
# Release Note # Release Note
请查看最新版本0.1.63的release note 确认变更,[0.1.63 release note](https://github.com/alibaba/flutter_boost/releases) 请查看最新版本0.1.64的release note 确认变更,[0.1.64 release note](https://github.com/alibaba/flutter_boost/releases)
# FlutterBoost # FlutterBoost
...@@ -20,27 +20,28 @@ ...@@ -20,27 +20,28 @@
请阅读这篇文章: 请阅读这篇文章:
<a href="Frequently Asked Question.md">FAQ</a> <a href="Frequently Asked Question.md">FAQ</a>
# boost 版本说明 # boost 版本说明
1. 0.1.50 是基于flutter v1.5.4-hotfixes 分支,android 如果其他flutter版本或者分支 会编译错误 | Flutter Boost 版本 | 支持的 Flutter SDK 版本 | Description | 是否支持 AndroidX? |
| ----------------------- | ----------------------- | ------------------------------------------------------------ | ------------------- |
2. 0.1.51--0.1.54 是对0.1.50的bugfix | 0.1.50 | 1.5.4-hotfixes | android 如果其他 flutter 版本或者分支会编译错误。 | No |
| 0.1.51-0.1.59 | 1.5.4-hotfixes | 0.1.50 的 bugfix。 | No |
| 0.1.60 | 1.9.1-hotfixes | android 如果其他 flutter 分支会编译错误。 | No |
3. 0.1.60 是基于flutter v1.9.1-hotfixes 分支,android如果其他flutter分支会编译错误,该版本不支持andriodx | 0.1.63 | 1.9.1-hotfixes | 和 0.1.60 代码同步, bugfix 也会合入该分支,如果其他分支会编译错误。 | No |
| 0.1.61-0.1.69 | 1.9.1-hotfixes | 0.1.60 的 bugfix。 | No |
4. 0.1.61-- 0.1.63 是对0.1.60 的bugfix | 1.9.1+2 | 1.9.1-hotfixes | 版本号重新命名,开始默认支持androidx | Yes |
| 1.12.13 | 1.12.13 -hotfixes | 支持androidx | Yes |
5. 关于androidx 的支持声明
目前androidx 分支为 v0.1.61-androidx-hotfixes
是基于flutter v1.9.1-hotfixes 分支,如果其他分支会编译错误
和0.1.60代码同步, bugfix 也会合入该分支。
| Flutter Boost 分支 | 支持的 Flutter SDK 版本 | Description | Support AndroidX? |
| --------------------- | --------------------------- | ------------------------------------------------------------ | ------------------ |
| v1.9.1-hotfixes | 1.9.1-hotfixes | for androidx | Yes |
| task/task_v1.9.1_support_hotfixes| 1.9.1-hotfixes | for support | NO |
| v1.12.13-hotfixes | 1.12.13-hotfixes | for androidx | Yes |
| task/task_v1.12.13_support_hotfixes| 1.12.13-hotfixes | for support | NO |
...@@ -50,22 +51,20 @@ ...@@ -50,22 +51,20 @@
打开pubspec.yaml并将以下行添加到依赖项: 打开pubspec.yaml并将以下行添加到依赖项:
support分支 androidx branch
```json ```json
flutter_boost: flutter_boost:
git: git:
url: 'https://github.com/alibaba/flutter_boost.git' url: 'https://github.com/alibaba/flutter_boost.git'
ref: '0.1.63' ref: '1.12.13'
``` ```
support branch
androidx分支
```json ```json
flutter_boost: flutter_boost:
git: git:
url: 'https://github.com/alibaba/flutter_boost.git' url: 'https://github.com/alibaba/flutter_boost.git'
ref: 'v0.1.61-androidx-hotfixes' ref: 'task/task_v1.12.13_support_hotfixes'
``` ```
......
# Specify analysis options.
#
# Until there are meta linter rules, each desired lint must be explicitly enabled.
# See: https://github.com/dart-lang/linter/issues/288
#
# For a list of lints, see: http://dart-lang.github.io/linter/lints/
# See the configuration guide for more
# https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer
#
# There are other similar analysis options files in the flutter repos,
# which should be kept in sync with this file:
#
# - analysis_options.yaml (this file)
# - packages/flutter/lib/analysis_options_user.yaml
# - https://github.com/flutter/plugins/blob/master/analysis_options.yaml
# - https://github.com/flutter/engine/blob/master/analysis_options.yaml
#
# This file contains the analysis options used by Flutter tools, such as IntelliJ,
# Android Studio, and the `flutter analyze` command.
analyzer:
strong-mode:
implicit-dynamic: false
errors:
# treat missing required parameters as a warning (not a hint)
missing_required_param: warning
# treat missing returns as a warning (not a hint)
missing_return: warning
# allow having TODOs in the code
todo: ignore
# Ignore analyzer hints for updating pubspecs when using Future or
# Stream and not importing dart:async
# Please see https://github.com/flutter/flutter/pull/24528 for details.
sdk_version_async_exported_from_core: ignore
exclude:
- "bin/cache/**"
# the following two are relative to the stocks example and the flutter package respectively
# see https://github.com/dart-lang/sdk/issues/28463
- "lib/i18n/messages_*.dart"
- "lib/src/http/**"
- "example/**"
- "example_swift/**"
- "test/**"
linter:
rules:
# these rules are documented on and in the same order as
# the Dart Lint rules page to make maintenance easier
# https://github.com/dart-lang/linter/blob/master/example/all.yaml
- always_declare_return_types
- always_put_control_body_on_new_line
# - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219
- always_require_non_null_named_parameters
- always_specify_types
- annotate_overrides
# - avoid_annotating_with_dynamic # conflicts with always_specify_types
# - avoid_as # required for implicit-casts: true
- avoid_bool_literals_in_conditional_expressions
# - avoid_catches_without_on_clauses # we do this commonly
# - avoid_catching_errors # we do this commonly
- avoid_classes_with_only_static_members
# - avoid_double_and_int_checks # only useful when targeting JS runtime
- avoid_empty_else
- avoid_field_initializers_in_const_classes
- avoid_function_literals_in_foreach_calls
# - avoid_implementing_value_types # not yet tested
- avoid_init_to_null
# - avoid_js_rounded_ints # only useful when targeting JS runtime
- avoid_null_checks_in_equality_operators
# - avoid_positional_boolean_parameters # not yet tested
# - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356)
- avoid_relative_lib_imports
- avoid_renaming_method_parameters
- avoid_return_types_on_setters
# - avoid_returning_null # there are plenty of valid reasons to return null
# - avoid_returning_null_for_future # not yet tested
- avoid_returning_null_for_void
# - avoid_returning_this # there are plenty of valid reasons to return this
# - avoid_setters_without_getters # not yet tested
# - avoid_shadowing_type_parameters # not yet tested
# - avoid_single_cascade_in_expression_statements # not yet tested
- avoid_slow_async_io
- avoid_types_as_parameter_names
# - avoid_types_on_closure_parameters # conflicts with always_specify_types
- avoid_unused_constructor_parameters
- avoid_void_async
- await_only_futures
- camel_case_types
- cancel_subscriptions
# - cascade_invocations # not yet tested
# - close_sinks # not reliable enough
# - comment_references # blocked on https://github.com/flutter/flutter/issues/20765
# - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204
- control_flow_in_finally
# - curly_braces_in_flow_control_structures # not yet tested
# - diagnostic_describe_all_properties # not yet tested
- directives_ordering
- empty_catches
- empty_constructor_bodies
- empty_statements
# - file_names # not yet tested
- flutter_style_todos
- hash_and_equals
- implementation_imports
# - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811
- iterable_contains_unrelated_type
# - join_return_with_assignment # not yet tested
- library_names
- library_prefixes
# - lines_longer_than_80_chars # not yet tested
- list_remove_unrelated_type
# - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181
- no_adjacent_strings_in_list
- no_duplicate_case_values
- non_constant_identifier_names
# - null_closures # not yet tested
# - omit_local_variable_types # opposite of always_specify_types
# - one_member_abstracts # too many false positives
# - only_throw_errors # https://github.com/flutter/flutter/issues/5792
- overridden_fields
- package_api_docs
- package_names
- package_prefixed_library_names
# - parameter_assignments # we do this commonly
- prefer_adjacent_string_concatenation
- prefer_asserts_in_initializer_lists
# - prefer_asserts_with_message # not yet tested
- prefer_collection_literals
- prefer_conditional_assignment
- prefer_const_constructors
- prefer_const_constructors_in_immutables
- prefer_const_declarations
- prefer_const_literals_to_create_immutables
# - prefer_constructors_over_static_methods # not yet tested
- prefer_contains
# - prefer_double_quotes # opposite of prefer_single_quotes
- prefer_equal_for_default_values
# - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods
- prefer_final_fields
# - prefer_final_in_for_each # not yet tested
- prefer_final_locals
# - prefer_for_elements_to_map_fromIterable # not yet tested
- prefer_foreach
# - prefer_function_declarations_over_variables # not yet tested
- prefer_generic_function_type_aliases
- prefer_if_elements_to_conditional_expressions
- prefer_if_null_operators
- prefer_initializing_formals
- prefer_inlined_adds
# - prefer_int_literals # not yet tested
# - prefer_interpolation_to_compose_strings # not yet tested
- prefer_is_empty
- prefer_is_not_empty
- prefer_iterable_whereType
# - prefer_mixin # https://github.com/dart-lang/language/issues/32
# - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932
- prefer_single_quotes
- prefer_spread_collections
- prefer_typing_uninitialized_variables
- prefer_void_to_null
# - provide_deprecation_message # not yet tested
# - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml
- recursive_getters
- slash_for_doc_comments
# - sort_child_properties_last # not yet tested
- sort_constructors_first
- sort_pub_dependencies
- sort_unnamed_constructors_first
- test_types_in_equals
- throw_in_finally
# - type_annotate_public_apis # subset of always_specify_types
- type_init_formals
# - unawaited_futures # too many false positives
# - unnecessary_await_in_return # not yet tested
- unnecessary_brace_in_string_interps
- unnecessary_const
- unnecessary_getters_setters
# - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498
- unnecessary_new
- unnecessary_null_aware_assignments
- unnecessary_null_in_if_null_operators
- unnecessary_overrides
- unnecessary_parenthesis
- unnecessary_statements
- unnecessary_this
- unrelated_type_equality_checks
# - unsafe_html # not yet tested
- use_full_hex_values_for_flutter_colors
# - use_function_type_syntax_for_parameters # not yet tested
- use_rethrow_when_possible
# - use_setters_to_change_properties # not yet tested
# - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182
# - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review
- valid_regexps
# - void_checks # not yet tested
...@@ -8,7 +8,7 @@ buildscript { ...@@ -8,7 +8,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.1.2' classpath 'com.android.tools.build:gradle:3.2.0'
} }
} }
...@@ -23,11 +23,11 @@ apply plugin: 'com.android.library' ...@@ -23,11 +23,11 @@ apply plugin: 'com.android.library'
android { android {
compileSdkVersion 28 compileSdkVersion 28
buildToolsVersion '27.0.3' buildToolsVersion '28.0.3'
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 28 targetSdkVersion 28
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
lintOptions { lintOptions {
disable 'InvalidPackage' disable 'InvalidPackage'
...@@ -35,11 +35,12 @@ android { ...@@ -35,11 +35,12 @@ android {
} }
dependencies { dependencies {
implementation 'com.android.support:appcompat-v7:28.0.0' compileOnly 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.android.support:design:28.0.0' compileOnly 'com.google.android.material:material:1.0.0'
implementation 'com.android.support:support-v4:28.0.0' compileOnly 'androidx.appcompat:appcompat:1.0.0'
implementation 'android.arch.lifecycle:common-java8:1.1.1' compileOnly 'androidx.annotation:annotation:1.0.0'
implementation 'com.alibaba:fastjson:1.2.41' implementation 'androidx.lifecycle:lifecycle-common-java8:2.1.0'
compileOnly 'com.alibaba:fastjson:1.2.41'
} }
......
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
\ No newline at end of file
...@@ -2,7 +2,7 @@ package com.idlefish.flutterboost; ...@@ -2,7 +2,7 @@ package com.idlefish.flutterboost;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import androidx.annotation.NonNull;
import io.flutter.Log; import io.flutter.Log;
import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityAware;
......
...@@ -26,15 +26,21 @@ package com.idlefish.flutterboost; ...@@ -26,15 +26,21 @@ package com.idlefish.flutterboost;
import android.util.Log; import android.util.Log;
import com.idlefish.flutterboost.log.AndroidLog;
import com.idlefish.flutterboost.log.ILog;
public class Debuger { public class Debuger {
private static final String TAG = "FlutterBoost#"; private static final String TAG = "FlutterBoost#";
private static final Debuger DEBUG = new Debuger(); private static final Debuger DEBUG = new Debuger();
private static boolean sSafeMode = false;
private static ILog sLog = new AndroidLog();
private Debuger(){ } private Debuger() {
}
private void print(String info) { private void print(String info) {
if(isDebug()) { if (isDebug()) {
Log.e(TAG, info); sLog.e(TAG, info);
} }
} }
...@@ -43,26 +49,50 @@ public class Debuger { ...@@ -43,26 +49,50 @@ public class Debuger {
} }
public static void exception(String message) { public static void exception(String message) {
if(isDebug()) { if (canThrowError()) {
throw new RuntimeException(message); throw new RuntimeException(message);
}else{ } else {
Log.e(TAG,"exception",new RuntimeException(message)); sLog.e(TAG, "exception", new RuntimeException(message));
} }
} }
public static void exception(Throwable t) { public static void exception(Throwable t) {
if(isDebug()) { if (canThrowError()) {
throw new RuntimeException(t); throw new RuntimeException(t);
}else{ } else {
Log.e(TAG,"exception",t); sLog.e(TAG, "exception", t);
} }
} }
public static boolean isDebug(){ public static boolean isDebug() {
try { try {
return FlutterBoost.instance().platform().isDebug(); return FlutterBoost.instance().platform().isDebug();
}catch (Throwable t){ } catch (Throwable t) {
return false; return false;
} }
} }
/**
* 设置boost log接收实现,默认为Anroid自带Log
*
* @param log
*/
public static void setLog(ILog log) {
if (log != null) {
sLog = log;
}
}
/**
* 设置Debugger策略倾向,如tue,则优先保证程序稳定,false,可抛Error
*
* @param safeMode
*/
public static void setSafeMode(boolean safeMode) {
sSafeMode = safeMode;
}
private static boolean canThrowError() {
return isDebug() && !sSafeMode;
}
} }
...@@ -5,13 +5,15 @@ import android.app.Activity; ...@@ -5,13 +5,15 @@ import android.app.Activity;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import com.idlefish.flutterboost.interfaces.*; import com.idlefish.flutterboost.interfaces.*;
import io.flutter.embedding.android.FlutterView; import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.plugin.common.PluginRegistry; import io.flutter.embedding.engine.loader.FlutterLoader;
import io.flutter.view.FlutterMain; import io.flutter.view.FlutterMain;
import java.lang.reflect.Method; import java.lang.reflect.Method;
...@@ -19,14 +21,14 @@ import java.util.HashMap; ...@@ -19,14 +21,14 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class FlutterBoost { public class FlutterBoost {
private Platform mPlatform; private Platform mPlatform;
private FlutterViewContainerManager mManager; private FlutterViewContainerManager mManager;
private FlutterEngine mEngine; private FlutterEngine mEngine;
private Activity mCurrentActiveActivity; private Activity mCurrentActiveActivity;
private PluginRegistry mRegistry; private boolean mEnterActivityCreate =false;
static FlutterBoost sInstance = null; static FlutterBoost sInstance = null;
private static boolean sInit;
private long FlutterPostFrameCallTime = 0; private long FlutterPostFrameCallTime = 0;
private Application.ActivityLifecycleCallbacks mActivityLifecycleCallbacks; private Application.ActivityLifecycleCallbacks mActivityLifecycleCallbacks;
...@@ -47,7 +49,10 @@ public class FlutterBoost { ...@@ -47,7 +49,10 @@ public class FlutterBoost {
} }
public void init(Platform platform) { public void init(Platform platform) {
if (sInit){
Debuger.log("FlutterBoost is alread inited. Do not init twice");
return;
}
mPlatform = platform; mPlatform = platform;
mManager = new FlutterViewContainerManager(); mManager = new FlutterViewContainerManager();
...@@ -56,24 +61,25 @@ public class FlutterBoost { ...@@ -56,24 +61,25 @@ public class FlutterBoost {
@Override @Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) { public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
//fix crash:'FlutterBoostPlugin not register yet'
//case: initFlutter after Activity.OnCreate method,and then called start/stop crash
// In SplashActivity ,showDialog(in OnCreate method) to check permission, if authorized, then init sdk and jump homePage)
mEnterActivityCreate = true;
mCurrentActiveActivity = activity; mCurrentActiveActivity = activity;
if (mPlatform.whenEngineStart() == ConfigBuilder.ANY_ACTIVITY_CREATED) { if (mPlatform.whenEngineStart() == ConfigBuilder.ANY_ACTIVITY_CREATED) {
doInitialFlutter(); doInitialFlutter();
boostPluginRegistry();
}
if (mPlatform.whenEngineStart() == ConfigBuilder.IMMEDIATELY) {
boostPluginRegistry();
} }
} }
@Override @Override
public void onActivityStarted(Activity activity) { public void onActivityStarted(Activity activity) {
if (!mEnterActivityCreate){
return;
}
if (mCurrentActiveActivity == null) { if (mCurrentActiveActivity == null) {
Debuger.log("Application entry foreground"); Debuger.log("Application entry foreground");
if (createEngine() != null) { if (mEngine != null) {
HashMap<String, String> map = new HashMap<>(); HashMap<String, String> map = new HashMap<>();
map.put("type", "foreground"); map.put("type", "foreground");
channel().sendEvent("lifecycle", map); channel().sendEvent("lifecycle", map);
...@@ -84,20 +90,28 @@ public class FlutterBoost { ...@@ -84,20 +90,28 @@ public class FlutterBoost {
@Override @Override
public void onActivityResumed(Activity activity) { public void onActivityResumed(Activity activity) {
if (!mEnterActivityCreate){
return;
}
mCurrentActiveActivity = activity; mCurrentActiveActivity = activity;
} }
@Override @Override
public void onActivityPaused(Activity activity) { public void onActivityPaused(Activity activity) {
if (!mEnterActivityCreate){
return;
}
} }
@Override @Override
public void onActivityStopped(Activity activity) { public void onActivityStopped(Activity activity) {
if (!mEnterActivityCreate){
return;
}
if (mCurrentActiveActivity == activity) { if (mCurrentActiveActivity == activity) {
Debuger.log("Application entry background"); Debuger.log("Application entry background");
if (createEngine() != null) { if (mEngine != null) {
HashMap<String, String> map = new HashMap<>(); HashMap<String, String> map = new HashMap<>();
map.put("type", "background"); map.put("type", "background");
channel().sendEvent("lifecycle", map); channel().sendEvent("lifecycle", map);
...@@ -108,15 +122,20 @@ public class FlutterBoost { ...@@ -108,15 +122,20 @@ public class FlutterBoost {
@Override @Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) { public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
if (!mEnterActivityCreate){
return;
}
} }
@Override @Override
public void onActivityDestroyed(Activity activity) { public void onActivityDestroyed(Activity activity) {
if (!mEnterActivityCreate){
return;
}
if (mCurrentActiveActivity == activity) { if (mCurrentActiveActivity == activity) {
Debuger.log("Application entry background"); Debuger.log("Application entry background");
if (createEngine() != null) { if (mEngine != null) {
HashMap<String, String> map = new HashMap<>(); HashMap<String, String> map = new HashMap<>();
map.put("type", "background"); map.put("type", "background");
channel().sendEvent("lifecycle", map); channel().sendEvent("lifecycle", map);
...@@ -132,15 +151,18 @@ public class FlutterBoost { ...@@ -132,15 +151,18 @@ public class FlutterBoost {
doInitialFlutter(); doInitialFlutter();
} }
sInit = true;
} }
public void doInitialFlutter() { public void doInitialFlutter() {
if (mEngine != null) {
return;
}
if (mPlatform.lifecycleListener != null) {
if (mEngine != null) return; mPlatform.lifecycleListener.beforeCreateEngine();
}
FlutterEngine flutterEngine = createEngine(); FlutterEngine flutterEngine = createEngine();
if (mPlatform.lifecycleListener != null) { if (mPlatform.lifecycleListener != null) {
mPlatform.lifecycleListener.onEngineCreated(); mPlatform.lifecycleListener.onEngineCreated();
...@@ -158,17 +180,8 @@ public class FlutterBoost { ...@@ -158,17 +180,8 @@ public class FlutterBoost {
); );
flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint); flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
mRegistry = new BoostPluginRegistry(createEngine());
}
public void boostPluginRegistry(){
if(mRegistry!=null&& !mRegistry.hasPlugin("boostPluginRegistry")){
mPlatform.registerPlugins(mRegistry);
mRegistry.registrarFor("boostPluginRegistry");
} }
}
public static class ConfigBuilder { public static class ConfigBuilder {
...@@ -200,7 +213,6 @@ public class FlutterBoost { ...@@ -200,7 +213,6 @@ public class FlutterBoost {
private BoostLifecycleListener lifecycleListener; private BoostLifecycleListener lifecycleListener;
private BoostPluginsRegister boostPluginsRegister;
...@@ -235,15 +247,11 @@ public class FlutterBoost { ...@@ -235,15 +247,11 @@ public class FlutterBoost {
} }
public ConfigBuilder lifecycleListener(BoostLifecycleListener lifecycleListener) { public ConfigBuilder lifecycleListener(BoostLifecycleListener lifecycleListener) {
this.lifecycleListener = lifecycleListener; this.lifecycleListener = lifecycleListener;
return this; return this;
} }
public ConfigBuilder pluginsRegister(BoostPluginsRegister boostPluginsRegister) {
this.boostPluginsRegister = boostPluginsRegister;
return this;
}
public Platform build() { public Platform build() {
Platform platform = new Platform() { Platform platform = new Platform() {
...@@ -278,7 +286,6 @@ public class FlutterBoost { ...@@ -278,7 +286,6 @@ public class FlutterBoost {
}; };
platform.lifecycleListener = this.lifecycleListener; platform.lifecycleListener = this.lifecycleListener;
platform.pluginsRegister=this.boostPluginsRegister;
return platform; return platform;
} }
...@@ -305,25 +312,33 @@ public class FlutterBoost { ...@@ -305,25 +312,33 @@ public class FlutterBoost {
return mManager.findContainerById(id); return mManager.findContainerById(id);
} }
public PluginRegistry getPluginRegistry() {
return mRegistry;
}
private FlutterEngine createEngine() { private FlutterEngine createEngine() {
if (mEngine == null) { if (mEngine == null) {
FlutterMain.startInitialization(mPlatform.getApplication()); FlutterMain.startInitialization(mPlatform.getApplication());
FlutterShellArgs flutterShellArgs = new FlutterShellArgs(new String[0]); FlutterShellArgs flutterShellArgs = new FlutterShellArgs(new String[0]);
FlutterMain.ensureInitializationComplete( FlutterMain.ensureInitializationComplete(
mPlatform.getApplication().getApplicationContext(), flutterShellArgs.toArray()); mPlatform.getApplication().getApplicationContext(), flutterShellArgs.toArray());
mEngine = new FlutterEngine(mPlatform.getApplication().getApplicationContext()); mEngine = new FlutterEngine(mPlatform.getApplication().getApplicationContext(),FlutterLoader.getInstance(),new FlutterJNI(),null,false);
registerPlugins(mEngine);
} }
return mEngine; return mEngine;
} }
private void registerPlugins(FlutterEngine engine) {
try {
Class<?> generatedPluginRegistrant = Class.forName("io.flutter.plugins.GeneratedPluginRegistrant");
Method registrationMethod = generatedPluginRegistrant.getDeclaredMethod("registerWith", FlutterEngine.class);
registrationMethod.invoke(null, engine);
} catch (Exception e) {
Debuger.exception(e);
}
}
public FlutterEngine engineProvider() { public FlutterEngine engineProvider() {
return mEngine; return mEngine;
} }
...@@ -337,12 +352,14 @@ public class FlutterBoost { ...@@ -337,12 +352,14 @@ public class FlutterBoost {
mPlatform.lifecycleListener.onEngineDestroy(); mPlatform.lifecycleListener.onEngineDestroy();
} }
mEngine = null; mEngine = null;
mRegistry = null;
mCurrentActiveActivity = null; mCurrentActiveActivity = null;
} }
public interface BoostLifecycleListener { public interface BoostLifecycleListener {
void beforeCreateEngine();
void onEngineCreated(); void onEngineCreated();
void onPluginsRegistered(); void onPluginsRegistered();
...@@ -351,9 +368,4 @@ public class FlutterBoost { ...@@ -351,9 +368,4 @@ public class FlutterBoost {
} }
public interface BoostPluginsRegister {
void registerPlugins(PluginRegistry mRegistry);
}
} }
package com.idlefish.flutterboost; package com.idlefish.flutterboost;
import android.support.annotation.Nullable; import android.os.Handler;
import android.util.Log;
import androidx.annotation.Nullable;
import com.idlefish.flutterboost.interfaces.IContainerRecord; import com.idlefish.flutterboost.interfaces.IContainerRecord;
...@@ -211,7 +214,7 @@ public class FlutterBoostPlugin { ...@@ -211,7 +214,7 @@ public class FlutterBoostPlugin {
} catch (Throwable t) { } catch (Throwable t) {
result.error("no flutter page found!", t.getMessage(), t); result.error("no flutter page found!", t.getMessage(), Log.getStackTraceString(t));
} }
} }
break; break;
...@@ -230,7 +233,7 @@ public class FlutterBoostPlugin { ...@@ -230,7 +233,7 @@ public class FlutterBoostPlugin {
} }
}); });
} catch (Throwable t) { } catch (Throwable t) {
result.error("open page error", t.getMessage(), t); result.error("open page error", t.getMessage(), Log.getStackTraceString(t));
} }
} }
break; break;
...@@ -243,7 +246,7 @@ public class FlutterBoostPlugin { ...@@ -243,7 +246,7 @@ public class FlutterBoostPlugin {
mManager.closeContainer(uniqueId, resultData, exts); mManager.closeContainer(uniqueId, resultData, exts);
result.success(true); result.success(true);
} catch (Throwable t) { } catch (Throwable t) {
result.error("close page error", t.getMessage(), t); result.error("close page error", t.getMessage(), Log.getStackTraceString(t));
} }
} }
break; break;
...@@ -255,7 +258,7 @@ public class FlutterBoostPlugin { ...@@ -255,7 +258,7 @@ public class FlutterBoostPlugin {
mManager.onShownContainerChanged(newId, oldId); mManager.onShownContainerChanged(newId, oldId);
result.success(true); result.success(true);
} catch (Throwable t) { } catch (Throwable t) {
result.error("onShownContainerChanged", t.getMessage(), t); result.error("onShownContainerChanged", t.getMessage(), Log.getStackTraceString(t));
} }
} }
break; break;
......
...@@ -133,7 +133,7 @@ public class FlutterViewContainerManager implements IContainerManager { ...@@ -133,7 +133,7 @@ public class FlutterViewContainerManager implements IContainerManager {
urlParams.put(IContainerRecord.UNIQ_KEY,uniqueId); urlParams.put(IContainerRecord.UNIQ_KEY,uniqueId);
IContainerRecord currentTopRecord = getCurrentTopRecord(); IContainerRecord currentTopRecord = getCurrentTopRecord();
if(onResult != null) { if(onResult != null&&currentTopRecord!=null) {
mOnResults.put(currentTopRecord.uniqueId(),onResult); mOnResults.put(currentTopRecord.uniqueId(),onResult);
} }
......
...@@ -11,6 +11,20 @@ import java.util.Map; ...@@ -11,6 +11,20 @@ import java.util.Map;
import io.flutter.embedding.android.FlutterView; import io.flutter.embedding.android.FlutterView;
import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry;
/**
* 插件注册方式 不在使用老的注册方式
*
* AndroidManifest.xml 中必须要添加 flutterEmbedding 版本设置
* <meta-data android:name="flutterEmbedding"
* android:value="2">
* </meta-data>
*
* GeneratedPluginRegistrant 会自动生成 新的插件方式 
*
* 插件注册方式请使用
* FlutterBoost.instance().engineProvider().getPlugins().add(new FlutterPlugin());
* GeneratedPluginRegistrant.registerWith(),是在engine 创建后马上执行,放射形式调用
*/
public abstract class Platform { public abstract class Platform {
public abstract Application getApplication(); public abstract Application getApplication();
...@@ -28,7 +42,6 @@ public abstract class Platform { ...@@ -28,7 +42,6 @@ public abstract class Platform {
public FlutterBoost.BoostLifecycleListener lifecycleListener; public FlutterBoost.BoostLifecycleListener lifecycleListener;
public FlutterBoost.BoostPluginsRegister pluginsRegister;
public void closeContainer(IContainerRecord record, Map<String, Object> result, Map<String, Object> exts) { public void closeContainer(IContainerRecord record, Map<String, Object> result, Map<String, Object> exts) {
if (record == null) return; if (record == null) return;
...@@ -37,14 +50,4 @@ public abstract class Platform { ...@@ -37,14 +50,4 @@ public abstract class Platform {
} }
public void registerPlugins(PluginRegistry mRegistry) {
if(pluginsRegister!=null){
pluginsRegister.registerPlugins(mRegistry);
}
if (lifecycleListener!= null) {
lifecycleListener.onPluginsRegistered();
}
}
} }
...@@ -185,7 +185,8 @@ public class Utils { ...@@ -185,7 +185,8 @@ public class Utils {
} }
} }
}catch (Throwable t){ }catch (Throwable t){
Debuger.exception(t); // Debuger.exception(t);
t.printStackTrace();
} }
} }
......
package com.idlefish.flutterboost; package com.idlefish.flutterboost;
import android.support.annotation.NonNull; import androidx.annotation.NonNull;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import android.view.KeyCharacterMap; import android.view.KeyCharacterMap;
import android.view.KeyEvent; import android.view.KeyEvent;
......
package com.idlefish.flutterboost;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.Surface;
import android.view.TextureView;
import io.flutter.Log;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class XFlutterTextureView extends TextureView implements FlutterRenderer.RenderSurface {
private static final String TAG = "XFlutterTextureView";
private boolean isSurfaceAvailableForRendering;
private boolean isAttachedToFlutterRenderer;
@Nullable
private FlutterRenderer flutterRenderer;
@NonNull
private Set<OnFirstFrameRenderedListener> onFirstFrameRenderedListeners;
private final SurfaceTextureListener surfaceTextureListener;
public XFlutterTextureView(@NonNull Context context) {
this(context, (AttributeSet)null);
}
public XFlutterTextureView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.isSurfaceAvailableForRendering = false;
this.isAttachedToFlutterRenderer = false;
this.onFirstFrameRenderedListeners = new HashSet();
this.surfaceTextureListener = new SurfaceTextureListener() {
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
Log.v("FlutterTextureView", "SurfaceTextureListener.onSurfaceTextureAvailable()");
XFlutterTextureView.this.isSurfaceAvailableForRendering = true;
if (XFlutterTextureView.this.isAttachedToFlutterRenderer) {
XFlutterTextureView.this.connectSurfaceToRenderer();
}
}
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
Log.v("FlutterTextureView", "SurfaceTextureListener.onSurfaceTextureSizeChanged()");
if (XFlutterTextureView.this.isAttachedToFlutterRenderer) {
XFlutterTextureView.this.changeSurfaceSize(width, height);
}
}
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
}
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
Log.v("FlutterTextureView", "SurfaceTextureListener.onSurfaceTextureDestroyed()");
XFlutterTextureView.this.isSurfaceAvailableForRendering = false;
if (XFlutterTextureView.this.isAttachedToFlutterRenderer) {
XFlutterTextureView.this.disconnectSurfaceFromRenderer();
}
return true;
}
};
this.init();
}
private void init() {
this.setSurfaceTextureListener(this.surfaceTextureListener);
}
public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
Log.v("FlutterTextureView", "Attaching to FlutterRenderer.");
if (this.flutterRenderer != null) {
Log.v("FlutterTextureView", "Already connected to a FlutterRenderer. Detaching from old one and attaching to new one.");
this.flutterRenderer.detachFromRenderSurface();
}
this.flutterRenderer = flutterRenderer;
this.isAttachedToFlutterRenderer = true;
if (this.isSurfaceAvailableForRendering) {
Log.v("FlutterTextureView", "Surface is available for rendering. Connecting FlutterRenderer to Android surface.");
this.connectSurfaceToRenderer();
}
}
public void detachFromRenderer() {
if (this.flutterRenderer != null) {
if (this.getWindowToken() != null) {
Log.v("FlutterTextureView", "Disconnecting FlutterRenderer from Android surface.");
this.disconnectSurfaceFromRenderer();
}
this.flutterRenderer = null;
this.isAttachedToFlutterRenderer = false;
} else {
Log.w("FlutterTextureView", "detachFromRenderer() invoked when no FlutterRenderer was attached.");
}
}
private void connectSurfaceToRenderer() {
if (this.flutterRenderer != null && this.getSurfaceTexture() != null) {
Surface surface= new Surface(this.getSurfaceTexture());
this.flutterRenderer.surfaceCreated(surface);
surface.release();
} else {
throw new IllegalStateException("connectSurfaceToRenderer() should only be called when flutterRenderer and getSurfaceTexture() are non-null.");
}
}
private void changeSurfaceSize(int width, int height) {
if (this.flutterRenderer == null) {
throw new IllegalStateException("changeSurfaceSize() should only be called when flutterRenderer is non-null.");
} else {
Log.v("FlutterTextureView", "Notifying FlutterRenderer that Android surface size has changed to " + width + " x " + height);
this.flutterRenderer.surfaceChanged(width, height);
}
}
private void disconnectSurfaceFromRenderer() {
if (this.flutterRenderer == null) {
throw new IllegalStateException("disconnectSurfaceFromRenderer() should only be called when flutterRenderer is non-null.");
} else {
this.flutterRenderer.surfaceDestroyed();
}
}
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
this.onFirstFrameRenderedListeners.add(listener);
}
public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
this.onFirstFrameRenderedListeners.remove(listener);
}
public void onFirstFrameRendered() {
Log.v("FlutterTextureView", "onFirstFrameRendered()");
Iterator var1 = this.onFirstFrameRenderedListeners.iterator();
while(var1.hasNext()) {
OnFirstFrameRenderedListener listener = (OnFirstFrameRenderedListener)var1.next();
listener.onFirstFrameRendered();
}
}
}
\ No newline at end of file
...@@ -8,11 +8,11 @@ import android.content.res.Configuration; ...@@ -8,11 +8,11 @@ import android.content.res.Configuration;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build; import android.os.Build;
import android.os.LocaleList; import android.os.LocaleList;
import android.support.annotation.NonNull; import androidx.annotation.NonNull;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import android.support.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import android.support.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import android.support.v4.view.ViewCompat; import androidx.core.view.ViewCompat;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.KeyEvent; import android.view.KeyEvent;
...@@ -36,7 +36,8 @@ import io.flutter.Log; ...@@ -36,7 +36,8 @@ import io.flutter.Log;
import io.flutter.embedding.android.*; import io.flutter.embedding.android.*;
import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
import io.flutter.embedding.engine.renderer.RenderSurface;
import io.flutter.embedding.engine.systemchannels.TextInputChannel; import io.flutter.embedding.engine.systemchannels.TextInputChannel;
import io.flutter.plugin.editing.TextInputPlugin; import io.flutter.plugin.editing.TextInputPlugin;
import io.flutter.plugin.platform.PlatformViewsController; import io.flutter.plugin.platform.PlatformViewsController;
...@@ -74,8 +75,10 @@ public class XFlutterView extends FrameLayout { ...@@ -74,8 +75,10 @@ public class XFlutterView extends FrameLayout {
// Internal view hierarchy references. // Internal view hierarchy references.
@Nullable @Nullable
private FlutterRenderer.RenderSurface renderSurface; private RenderSurface renderSurface;
private final Set<OnFirstFrameRenderedListener> onFirstFrameRenderedListeners = new HashSet<>();
private final Set<FlutterUiDisplayListener> flutterUiDisplayListeners = new HashSet<>();
private boolean didRenderFirstFrame; private boolean didRenderFirstFrame;
// Connections to a Flutter execution context. // Connections to a Flutter execution context.
...@@ -100,6 +103,9 @@ public class XFlutterView extends FrameLayout { ...@@ -100,6 +103,9 @@ public class XFlutterView extends FrameLayout {
private boolean hasAddFirstFrameRenderedListener=false; private boolean hasAddFirstFrameRenderedListener=false;
private boolean isFlutterUiDisplayed;
// Directly implemented View behavior that communicates with Flutter. // Directly implemented View behavior that communicates with Flutter.
private final FlutterRenderer.ViewportMetrics viewportMetrics = new FlutterRenderer.ViewportMetrics(); private final FlutterRenderer.ViewportMetrics viewportMetrics = new FlutterRenderer.ViewportMetrics();
...@@ -110,23 +116,32 @@ public class XFlutterView extends FrameLayout { ...@@ -110,23 +116,32 @@ public class XFlutterView extends FrameLayout {
} }
}; };
private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() {
private final FlutterUiDisplayListener flutterUiDisplayListener = new FlutterUiDisplayListener() {
@Override
public void onFlutterUiDisplayed() {
isFlutterUiDisplayed = true;
for (FlutterUiDisplayListener listener : flutterUiDisplayListeners) {
listener.onFlutterUiDisplayed();
}
}
@Override @Override
public void onFirstFrameRendered() { public void onFlutterUiNoLongerDisplayed() {
didRenderFirstFrame = true; isFlutterUiDisplayed = false;
for (OnFirstFrameRenderedListener listener : onFirstFrameRenderedListeners) { for (FlutterUiDisplayListener listener : flutterUiDisplayListeners) {
listener.onFirstFrameRendered(); listener.onFlutterUiNoLongerDisplayed();
} }
} }
}; };
/** /**
* Constructs a {@code FlutterView} programmatically, without any XML attributes. * Constructs a {@code FlutterView} programmatically, without any XML attributes.
* <p> * <p>
* <ul> * <ul>
* <li>{@link #renderMode} defaults to {@link RenderMode#surface}.</li>
* <li>{@link #transparencyMode} defaults to {@link TransparencyMode#opaque}.</li>
* </ul> * </ul>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context} * {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}. * to be compatible with {@link PlatformViewsController}.
...@@ -202,7 +217,7 @@ public class XFlutterView extends FrameLayout { ...@@ -202,7 +217,7 @@ public class XFlutterView extends FrameLayout {
break; break;
case texture: case texture:
Log.v(TAG, "Internally using a FlutterTextureView."); Log.v(TAG, "Internally using a FlutterTextureView.");
XFlutterTextureView flutterTextureView = new XFlutterTextureView(getContext()); FlutterTextureView flutterTextureView = new FlutterTextureView(getContext());
renderSurface = flutterTextureView; renderSurface = flutterTextureView;
addView(flutterTextureView); addView(flutterTextureView);
break; break;
...@@ -233,20 +248,20 @@ public class XFlutterView extends FrameLayout { ...@@ -233,20 +248,20 @@ public class XFlutterView extends FrameLayout {
return didRenderFirstFrame; return didRenderFirstFrame;
} }
/**
* Adds the given {@code listener} to this {@code FlutterView}, to be notified upon Flutter's
* first rendered frame.
*/
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { public void addOnFirstFrameRenderedListener(@NonNull FlutterUiDisplayListener listener) {
onFirstFrameRenderedListeners.add(listener); flutterUiDisplayListeners.add(listener);
} }
/** /**
* Removes the given {@code listener}, which was previously added with * Removes the given {@code listener}, which was previously added with
* {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}. * {@link #addOnFirstFrameRenderedListener(FlutterUiDisplayListener)}.
*/ */
public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { public void removeOnFirstFrameRenderedListener(@NonNull FlutterUiDisplayListener listener) {
onFirstFrameRenderedListeners.remove(listener); flutterUiDisplayListeners.remove(listener);
} }
//------- Start: Process View configuration that Flutter cares about. ------ //------- Start: Process View configuration that Flutter cares about. ------
...@@ -587,17 +602,12 @@ public class XFlutterView extends FrameLayout { ...@@ -587,17 +602,12 @@ public class XFlutterView extends FrameLayout {
} }
this.flutterEngine = flutterEngine; this.flutterEngine = flutterEngine;
// Instruct our FlutterRenderer that we are now its designated RenderSurface.
FlutterRenderer flutterRenderer = this.flutterEngine.getRenderer(); FlutterRenderer flutterRenderer = this.flutterEngine.getRenderer();
didRenderFirstFrame = flutterRenderer.hasRenderedFirstFrame(); isFlutterUiDisplayed = flutterRenderer.isDisplayingFlutterUi();
if(!hasAddFirstFrameRenderedListener){ renderSurface.attachToRenderer(flutterRenderer);
flutterRenderer.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener); flutterRenderer.addIsDisplayingFlutterUiListener(flutterUiDisplayListener);
hasAddFirstFrameRenderedListener=true; this.flutterEngine.getPlatformViewsController().attachToView(this);
}
flutterRenderer.attachToRenderSurface(renderSurface);
// Initialize various components that know how to process Android View I/O
// in a way that Flutter understands.
if(textInputPlugin==null){ if(textInputPlugin==null){
...@@ -682,7 +692,7 @@ public class XFlutterView extends FrameLayout { ...@@ -682,7 +692,7 @@ public class XFlutterView extends FrameLayout {
// Disconnect the FlutterEngine's PlatformViewsController from the AccessibilityBridge. // Disconnect the FlutterEngine's PlatformViewsController from the AccessibilityBridge.
flutterEngine.getPlatformViewsController().detachAccessibiltyBridge(); flutterEngine.getPlatformViewsController().detachAccessibiltyBridge();
flutterEngine.getPlatformViewsController().detachFromView();
// Disconnect and clean up the AccessibilityBridge. // Disconnect and clean up the AccessibilityBridge.
accessibilityBridge.release(); accessibilityBridge.release();
accessibilityBridge = null; accessibilityBridge = null;
...@@ -693,11 +703,17 @@ public class XFlutterView extends FrameLayout { ...@@ -693,11 +703,17 @@ public class XFlutterView extends FrameLayout {
// TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin // TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin
// Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface. // Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface.
FlutterRenderer flutterRenderer = flutterEngine.getRenderer(); FlutterRenderer flutterRenderer = flutterEngine.getRenderer();
// didRenderFirstFrame = false; isFlutterUiDisplayed = false;
flutterRenderer.removeOnFirstFrameRenderedListener(onFirstFrameRenderedListener); flutterRenderer.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener);
flutterRenderer.detachFromRenderSurface(); flutterRenderer.stopRenderingToSurface();
flutterRenderer.setSemanticsEnabled(false);
renderSurface.detachFromRenderer();
flutterEngine = null; flutterEngine = null;
} }
public void release(){ public void release(){
if(textInputPlugin!=null){ if(textInputPlugin!=null){
...@@ -786,6 +802,7 @@ public class XFlutterView extends FrameLayout { ...@@ -786,6 +802,7 @@ public class XFlutterView extends FrameLayout {
+ "FlutterView was not attached to a FlutterEngine."); + "FlutterView was not attached to a FlutterEngine.");
return; return;
} }
if(viewportMetrics.width==0&&viewportMetrics.height==0)return;
viewportMetrics.devicePixelRatio = getResources().getDisplayMetrics().density; viewportMetrics.devicePixelRatio = getResources().getDisplayMetrics().density;
flutterEngine.getRenderer().setViewportMetrics(viewportMetrics); flutterEngine.getRenderer().setViewportMetrics(viewportMetrics);
......
package com.idlefish.flutterboost; package com.idlefish.flutterboost;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import androidx.annotation.NonNull;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import android.text.Editable; import android.text.Editable;
import android.text.InputType; import android.text.InputType;
import android.text.Selection; import android.text.Selection;
...@@ -191,8 +191,13 @@ public class XTextInputPlugin { ...@@ -191,8 +191,13 @@ public class XTextInputPlugin {
if (isInputConnectionLocked) { if (isInputConnectionLocked) {
return lastInputConnection; return lastInputConnection;
} }
lastInputConnection = platformViewsController.getPlatformViewById(inputTarget.id).onCreateInputConnection(outAttrs); View platformView = platformViewsController.getPlatformViewById(inputTarget.id);
if (platformView != null) {
lastInputConnection = platformView.onCreateInputConnection(outAttrs);
return lastInputConnection; return lastInputConnection;
} else {
return null;
}
} }
outAttrs.inputType = inputTypeFromTextInputType( outAttrs.inputType = inputTypeFromTextInputType(
......
...@@ -2,9 +2,9 @@ package com.idlefish.flutterboost.containers; ...@@ -2,9 +2,9 @@ package com.idlefish.flutterboost.containers;
import android.app.Activity; import android.app.Activity;
import android.arch.lifecycle.Lifecycle; import androidx.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
import android.arch.lifecycle.LifecycleRegistry; import androidx.lifecycle.LifecycleRegistry;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
...@@ -15,8 +15,8 @@ import android.graphics.drawable.ColorDrawable; ...@@ -15,8 +15,8 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import androidx.annotation.NonNull;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import android.view.*; import android.view.*;
import android.widget.*; import android.widget.*;
import com.idlefish.flutterboost.FlutterBoost; import com.idlefish.flutterboost.FlutterBoost;
...@@ -73,7 +73,7 @@ public class BoostFlutterActivity extends Activity ...@@ -73,7 +73,7 @@ public class BoostFlutterActivity extends Activity
private Map params = new HashMap(); private Map params = new HashMap();
protected NewEngineIntentBuilder(@NonNull Class<? extends BoostFlutterActivity> activityClass) { public NewEngineIntentBuilder(@NonNull Class<? extends BoostFlutterActivity> activityClass) {
this.activityClass = activityClass; this.activityClass = activityClass;
} }
...@@ -458,6 +458,11 @@ public class BoostFlutterActivity extends Activity ...@@ -458,6 +458,11 @@ public class BoostFlutterActivity extends Activity
// No-op. Hook for subclasses. // No-op. Hook for subclasses.
} }
@Override
public void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine) {
}
@Override @Override
public boolean shouldAttachEngineToActivity() { public boolean shouldAttachEngineToActivity() {
......
...@@ -2,14 +2,14 @@ package com.idlefish.flutterboost.containers; ...@@ -2,14 +2,14 @@ package com.idlefish.flutterboost.containers;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.arch.lifecycle.Lifecycle; import androidx.lifecycle.Lifecycle;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import androidx.annotation.NonNull;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
...@@ -20,7 +20,6 @@ import java.util.HashMap; ...@@ -20,7 +20,6 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.idlefish.flutterboost.BoostPluginRegistry;
import com.idlefish.flutterboost.FlutterBoost; import com.idlefish.flutterboost.FlutterBoost;
import com.idlefish.flutterboost.Utils; import com.idlefish.flutterboost.Utils;
import com.idlefish.flutterboost.XFlutterView; import com.idlefish.flutterboost.XFlutterView;
...@@ -58,11 +57,11 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer ...@@ -58,11 +57,11 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
protected IOperateSyncer mSyncer; protected IOperateSyncer mSyncer;
FlutterActivityAndFragmentDelegate(@NonNull Host host) { public FlutterActivityAndFragmentDelegate(@NonNull Host host) {
this.host = host; this.host = host;
} }
void release() { public void release() {
this.host = null; this.host = null;
this.flutterEngine = null; this.flutterEngine = null;
this.flutterView = null; this.flutterView = null;
...@@ -71,19 +70,18 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer ...@@ -71,19 +70,18 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
@Nullable @Nullable
FlutterEngine getFlutterEngine() { public FlutterEngine getFlutterEngine() {
return flutterEngine; return flutterEngine;
} }
XFlutterView getFlutterView() { public XFlutterView getFlutterView() {
return flutterView; return flutterView;
} }
void onAttach(@NonNull Context context) { public void onAttach(@NonNull Context context) {
ensureAlive(); ensureAlive();
if (FlutterBoost.instance().platform().whenEngineStart() == FlutterBoost.ConfigBuilder.FLUTTER_ACTIVITY_CREATED) { if (FlutterBoost.instance().platform().whenEngineStart() == FlutterBoost.ConfigBuilder.FLUTTER_ACTIVITY_CREATED) {
FlutterBoost.instance().doInitialFlutter(); FlutterBoost.instance().doInitialFlutter();
FlutterBoost.instance().boostPluginRegistry();
} }
// When "retain instance" is true, the FlutterEngine will survive configuration // When "retain instance" is true, the FlutterEngine will survive configuration
// changes. Therefore, we create a new one only if one does not already exist. // changes. Therefore, we create a new one only if one does not already exist.
...@@ -127,14 +125,17 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer ...@@ -127,14 +125,17 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
@SuppressLint("ResourceType") @SuppressLint("ResourceType")
@NonNull @NonNull
View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.v(TAG, "Creating FlutterView."); Log.v(TAG, "Creating FlutterView.");
flutterEngine.getActivityControlSurface().attachToActivity( flutterEngine.getActivityControlSurface().attachToActivity(
host.getActivity(), host.getActivity(),
host.getLifecycle() host.getLifecycle()
); );
mSyncer = FlutterBoost.instance().containerManager().generateSyncer(this); mSyncer = FlutterBoost.instance().containerManager().generateSyncer(this);
ensureAlive(); ensureAlive();
...@@ -156,7 +157,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer ...@@ -156,7 +157,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
} }
void onStart() { public void onStart() {
Log.v(TAG, "onStart()"); Log.v(TAG, "onStart()");
ensureAlive(); ensureAlive();
...@@ -169,34 +170,33 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer ...@@ -169,34 +170,33 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
} }
void onResume() { public void onResume() {
mSyncer.onAppear(); mSyncer.onAppear();
Log.v(TAG, "onResume()"); Log.v(TAG, "onResume()");
ensureAlive(); ensureAlive();
flutterEngine.getLifecycleChannel().appIsResumed(); flutterEngine.getLifecycleChannel().appIsResumed();
BoostPluginRegistry registry = (BoostPluginRegistry) FlutterBoost.instance().getPluginRegistry();
ActivityPluginBinding binding = registry.getRegistrarAggregate().getActivityPluginBinding();
if (binding != null && (binding.getActivity() != this.host.getActivity())) {
flutterEngine.getActivityControlSurface().attachToActivity( flutterEngine.getActivityControlSurface().attachToActivity(
host.getActivity(), host.getActivity(),
host.getLifecycle() host.getLifecycle()
); );
}
} }
void onPostResume() { public void onPostResume() {
Log.v(TAG, "onPostResume()"); Log.v(TAG, "onPostResume()");
ensureAlive(); ensureAlive();
Utils.setStatusBarLightMode(host.getActivity(), true); // Utils.setStatusBarLightMode(host.getActivity(), true);
} }
void onPause() { public void onPause() {
Log.v(TAG, "onPause()"); Log.v(TAG, "onPause()");
ensureAlive(); ensureAlive();
...@@ -205,34 +205,24 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer ...@@ -205,34 +205,24 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
} }
void onStop() { public void onStop() {
Log.v(TAG, "onStop()"); Log.v(TAG, "onStop()");
ensureAlive(); ensureAlive();
} }
void onDestroyView() { public void onDestroyView() {
Log.v(TAG, "onDestroyView()"); Log.v(TAG, "onDestroyView()");
mSyncer.onDestroy(); mSyncer.onDestroy();
ensureAlive(); ensureAlive();
BoostPluginRegistry registry = (BoostPluginRegistry) FlutterBoost.instance().getPluginRegistry();
if (registry != null) {
ActivityPluginBinding binding = registry.getRegistrarAggregate().getActivityPluginBinding();
if (binding != null && (binding.getActivity() == this.host.getActivity())) {
registry.getRegistrarAggregate().onDetachedFromActivityForConfigChanges();
flutterEngine.getActivityControlSurface().detachFromActivityForConfigChanges();
}
}
flutterView.release(); flutterView.release();
} }
void onDetach() { public void onDetach() {
Log.v(TAG, "onDetach()"); Log.v(TAG, "onDetach()");
ensureAlive(); ensureAlive();
...@@ -240,7 +230,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer ...@@ -240,7 +230,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
// Null out the platformPlugin to avoid a possible retain cycle between the plugin, this Fragment, // Null out the platformPlugin to avoid a possible retain cycle between the plugin, this Fragment,
// and this Fragment's Activity. // and this Fragment's Activity.
if (platformPlugin != null) { if (platformPlugin != null) {
platformPlugin.destroy(); // platformPlugin.destroy();
platformPlugin = null; platformPlugin = null;
} }
...@@ -249,14 +239,14 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer ...@@ -249,14 +239,14 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
} }
void onBackPressed() { public void onBackPressed() {
mSyncer.onBackPressed(); mSyncer.onBackPressed();
ensureAlive(); ensureAlive();
} }
void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
mSyncer.onRequestPermissionsResult(requestCode, permissions, grantResults); mSyncer.onRequestPermissionsResult(requestCode, permissions, grantResults);
ensureAlive(); ensureAlive();
...@@ -272,7 +262,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer ...@@ -272,7 +262,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
} }
void onNewIntent(@NonNull Intent intent) { public void onNewIntent(@NonNull Intent intent) {
mSyncer.onNewIntent(intent); mSyncer.onNewIntent(intent);
ensureAlive(); ensureAlive();
...@@ -285,7 +275,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer ...@@ -285,7 +275,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
} }
void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
mSyncer.onActivityResult(requestCode, resultCode, data); mSyncer.onActivityResult(requestCode, resultCode, data);
Map<String, Object> result = null; Map<String, Object> result = null;
if (data != null) { if (data != null) {
...@@ -311,7 +301,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer ...@@ -311,7 +301,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
} }
void onUserLeaveHint() { public void onUserLeaveHint() {
ensureAlive(); ensureAlive();
if (flutterEngine != null) { if (flutterEngine != null) {
Log.v(TAG, "Forwarding onUserLeaveHint() to FlutterEngine."); Log.v(TAG, "Forwarding onUserLeaveHint() to FlutterEngine.");
...@@ -322,7 +312,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer ...@@ -322,7 +312,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
} }
void onTrimMemory(int level) { public void onTrimMemory(int level) {
mSyncer.onTrimMemory(level); mSyncer.onTrimMemory(level);
ensureAlive(); ensureAlive();
...@@ -338,7 +328,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer ...@@ -338,7 +328,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
} }
} }
void onLowMemory() { public void onLowMemory() {
Log.v(TAG, "Forwarding onLowMemory() to FlutterEngine."); Log.v(TAG, "Forwarding onLowMemory() to FlutterEngine.");
mSyncer.onLowMemory(); mSyncer.onLowMemory();
...@@ -414,7 +404,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer ...@@ -414,7 +404,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
* The {@link FlutterActivity} or {@link FlutterFragment} that owns this * The {@link FlutterActivity} or {@link FlutterFragment} that owns this
* {@code FlutterActivityAndFragmentDelegate}. * {@code FlutterActivityAndFragmentDelegate}.
*/ */
/* package */ interface Host extends SplashScreenProvider, FlutterEngineProvider, FlutterEngineConfigurator { public interface Host extends SplashScreenProvider, FlutterEngineProvider, FlutterEngineConfigurator {
/** /**
* Returns the {@link Context} that backs the host {@link Activity} or {@code Fragment}. * Returns the {@link Context} that backs the host {@link Activity} or {@code Fragment}.
*/ */
......
package com.idlefish.flutterboost.containers; package com.idlefish.flutterboost.containers;
import android.app.Activity; import android.app.Activity;
import android.arch.lifecycle.Lifecycle; import android.graphics.Color;
import android.view.*;
import androidx.lifecycle.Lifecycle;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import androidx.annotation.NonNull;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import android.support.v4.app.Fragment; import androidx.fragment.app.Fragment;
import android.support.v4.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.idlefish.flutterboost.FlutterBoost; import com.idlefish.flutterboost.FlutterBoost;
import com.idlefish.flutterboost.Utils;
import com.idlefish.flutterboost.XFlutterView; import com.idlefish.flutterboost.XFlutterView;
import io.flutter.embedding.android.*; import io.flutter.embedding.android.*;
import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngine;
...@@ -256,18 +256,24 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm ...@@ -256,18 +256,24 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
return delegate.onCreateView(inflater, container, savedInstanceState); return delegate.onCreateView(inflater, container, savedInstanceState);
} }
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
if (!isHidden()) {
delegate.onStart(); delegate.onStart();
} }
}
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
if (!isHidden()) {
delegate.onResume(); delegate.onResume();
} }
}
// TODO(mattcarroll): determine why this can't be in onResume(). Comment reason, or move if possible. // TODO(mattcarroll): determine why this can't be in onResume(). Comment reason, or move if possible.
@ActivityCallThrough @ActivityCallThrough
public void onPostResume() { public void onPostResume() {
...@@ -277,14 +283,18 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm ...@@ -277,14 +283,18 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
if (!isHidden()) {
delegate.onPause(); delegate.onPause();
} }
}
@Override @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
if (!isHidden()) {
delegate.onStop(); delegate.onStop();
} }
}
@Override @Override
public void onDestroyView() { public void onDestroyView() {
...@@ -300,6 +310,15 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm ...@@ -300,6 +310,15 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
delegate = null; delegate = null;
} }
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden) {
delegate.onPause();
} else {
delegate.onResume();
}
}
@ActivityCallThrough @ActivityCallThrough
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
...@@ -482,6 +501,11 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm ...@@ -482,6 +501,11 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
} }
} }
@Override
public void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine) {
}
/** /**
* See {@link NewEngineFragmentBuilder#shouldAttachEngineToActivity()} and * See {@link NewEngineFragmentBuilder#shouldAttachEngineToActivity()} and
* <p> * <p>
......
...@@ -6,8 +6,8 @@ import android.os.Bundle; ...@@ -6,8 +6,8 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.support.annotation.NonNull; import androidx.annotation.NonNull;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.widget.FrameLayout; import android.widget.FrameLayout;
...@@ -17,9 +17,8 @@ import io.flutter.Log; ...@@ -17,9 +17,8 @@ import io.flutter.Log;
import io.flutter.embedding.android.FlutterView; import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.android.SplashScreen; import io.flutter.embedding.android.SplashScreen;
import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
import java.util.Date;
/** /**
* {@code View} that displays a {@link SplashScreen} until a given {@link FlutterView} * {@code View} that displays a {@link SplashScreen} until a given {@link FlutterView}
...@@ -57,39 +56,20 @@ public class FlutterSplashView extends FrameLayout { ...@@ -57,39 +56,20 @@ public class FlutterSplashView extends FrameLayout {
}; };
@NonNull @NonNull
private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() { private final FlutterUiDisplayListener onFirstFrameRenderedListener = new FlutterUiDisplayListener() {
int i = 0;
@Override @Override
public void onFirstFrameRendered() { public void onFlutterUiDisplayed() {
if (FlutterBoost.instance().platform().whenEngineStart() == FlutterBoost.ConfigBuilder.FLUTTER_ACTIVITY_CREATED) {
long now = new Date().getTime();
long flutterPostFrameCallTime = FlutterBoost.instance().getFlutterPostFrameCallTime();
if (flutterPostFrameCallTime != 0 && (now - flutterPostFrameCallTime) > 800) {
if (splashScreen != null) { if (splashScreen != null) {
transitionToFlutter(); transitionToFlutter();
} }
return;
} }
handler.postDelayed(new Runnable() {
@Override @Override
public void run() { public void onFlutterUiNoLongerDisplayed() {
onFirstFrameRenderedListener.onFirstFrameRendered();
}
}, 200);
} else {
if (splashScreen != null) {
transitionToFlutter();
}
} }
}
}; };
@NonNull @NonNull
......
package com.idlefish.flutterboost.log;
import android.util.Log;
public class AndroidLog implements ILog {
@Override
public void d(String tag, String msg) {
Log.d(tag, msg);
}
@Override
public void d(String tag, String msg, Throwable throwable) {
Log.d(tag, msg, throwable);
}
@Override
public void e(String tag, String msg) {
Log.e(tag, msg);
}
@Override
public void e(String tag, String msg, Throwable throwable) {
Log.e(tag, msg, throwable);
}
@Override
public void i(String tag, String msg) {
Log.i(tag, msg);
}
@Override
public void i(String tag, String msg, Throwable throwable) {
Log.i(tag, msg, throwable);
}
@Override
public void v(String tag, String msg) {
Log.v(tag, msg);
}
@Override
public void v(String tag, String msg, Throwable throwable) {
Log.v(tag, msg, throwable);
}
@Override
public void w(String tag, String msg) {
Log.w(tag, msg);
}
@Override
public void w(String tag, String msg, Throwable throwable) {
Log.w(tag, msg, throwable);
}
@Override
public boolean isLogLevelEnabled(int level) {
return true;
}
}
package com.idlefish.flutterboost.log;
public interface ILog {
public enum LogLevelEnum {
VERBOSE(0, "V"), DEBUG(1, "D"), INFO(2, "I"), WARNING(3, "W"), ERROR(4, "E");
private String logLevelName;
private int loglevel;
LogLevelEnum(int loglevel, String name) {
this.loglevel = loglevel;
this.logLevelName = name;
}
public String getLogLevelName() {
return logLevelName;
}
public int getLoglevel() {
return loglevel;
}
}
void d(String tag, String msg);
void d(String tag, String msg, Throwable throwable);
void e(String tag, String msg);
void e(String tag, String msg, Throwable throwable);
void i(String tag, String msg);
void i(String tag, String msg, Throwable throwable);
void v(String tag, String msg);
void v(String tag, String msg, Throwable throwable);
void w(String tag, String msg);
void w(String tag, String msg, Throwable throwable);
boolean isLogLevelEnabled(int level);
}
...@@ -38,7 +38,7 @@ android { ...@@ -38,7 +38,7 @@ android {
targetSdkVersion 28 targetSdkVersion 28
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
...@@ -56,8 +56,8 @@ flutter { ...@@ -56,8 +56,8 @@ flutter {
dependencies { dependencies {
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'androidx.appcompat:appcompat:1.0.0'
} }
...@@ -60,6 +60,8 @@ ...@@ -60,6 +60,8 @@
android:theme="@style/Theme.AppCompat" android:theme="@style/Theme.AppCompat"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:windowSoftInputMode="adjustResize"/> android:windowSoftInputMode="adjustResize"/>
<meta-data android:name="flutterEmbedding"
android:value="2">
</meta-data>
</application> </application>
</manifest> </manifest>
package com.taobao.idlefish.flutterboostexample;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import androidx.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.view.ViewCompat;
public class FitSystemWindowFrameLayout extends FrameLayout {
public FitSystemWindowFrameLayout( @NonNull Context context) {
super(context);
}
public FitSystemWindowFrameLayout( @NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public FitSystemWindowFrameLayout( @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public FitSystemWindowFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
WindowInsets result = super.dispatchApplyWindowInsets(insets);
if (!insets.isConsumed()) {
final int count = getChildCount();
for (int i = 0; i < count; i++)
result = getChildAt(i).dispatchApplyWindowInsets(insets);
}
return result;
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
super.addView(child, index, params);
ViewCompat.requestApplyInsets(child);
}
}
...@@ -6,9 +6,9 @@ import android.graphics.Color; ...@@ -6,9 +6,9 @@ import android.graphics.Color;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import android.support.v7.app.ActionBar; import androidx.appcompat.app.ActionBar;
import android.support.v7.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import android.view.View; import android.view.View;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
......
package com.taobao.idlefish.flutterboostexample; package com.taobao.idlefish.flutterboostexample;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import android.support.v7.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
......
...@@ -10,6 +10,7 @@ import java.util.Map; ...@@ -10,6 +10,7 @@ import java.util.Map;
import com.idlefish.flutterboost.interfaces.INativeRouter; import com.idlefish.flutterboost.interfaces.INativeRouter;
import io.flutter.embedding.android.FlutterView; import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugins.GeneratedPluginRegistrant; import io.flutter.plugins.GeneratedPluginRegistrant;
...@@ -30,23 +31,50 @@ public class MyApplication extends Application { ...@@ -30,23 +31,50 @@ public class MyApplication extends Application {
}; };
FlutterBoost.BoostPluginsRegister pluginsRegister= new FlutterBoost.BoostPluginsRegister(){ FlutterBoost.BoostLifecycleListener boostLifecycleListener= new FlutterBoost.BoostLifecycleListener(){
@Override @Override
public void registerPlugins(PluginRegistry mRegistry) { public void beforeCreateEngine() {
GeneratedPluginRegistrant.registerWith(mRegistry);
TextPlatformViewPlugin.register(mRegistry.registrarFor("TextPlatformViewPlugin")); }
@Override
public void onEngineCreated() {
}
@Override
public void onPluginsRegistered() {
}
@Override
public void onEngineDestroy() {
} }
}; };
//
// AndroidManifest.xml 中必须要添加 flutterEmbedding 版本设置
//
// <meta-data android:name="flutterEmbedding"
// android:value="2">
// </meta-data>
// GeneratedPluginRegistrant 会自动生成 新的插件方式 
//
// 插件注册方式请使用
// FlutterBoost.instance().engineProvider().getPlugins().add(new FlutterPlugin());
// GeneratedPluginRegistrant.registerWith(),是在engine 创建后马上执行,放射形式调用
//
Platform platform= new FlutterBoost Platform platform= new FlutterBoost
.ConfigBuilder(this,router) .ConfigBuilder(this,router)
.isDebug(true) .isDebug(true)
.whenEngineStart(FlutterBoost.ConfigBuilder.ANY_ACTIVITY_CREATED) .whenEngineStart(FlutterBoost.ConfigBuilder.ANY_ACTIVITY_CREATED)
.renderMode(FlutterView.RenderMode.texture) .renderMode(FlutterView.RenderMode.texture)
.pluginsRegister(pluginsRegister) .lifecycleListener(boostLifecycleListener)
.build(); .build();
FlutterBoost.instance().init(platform); FlutterBoost.instance().init(platform);
......
package com.taobao.idlefish.flutterboostexample; package com.taobao.idlefish.flutterboostexample;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import android.support.v7.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
android:orientation="vertical" android:orientation="vertical"
android:background="@android:color/white"> android:background="@android:color/white">
<FrameLayout <com.taobao.idlefish.flutterboostexample.FitSystemWindowFrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
......
...@@ -5,7 +5,7 @@ buildscript { ...@@ -5,7 +5,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.1.2' classpath 'com.android.tools.build:gradle:3.2.0'
} }
} }
......
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
\ No newline at end of file
...@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME ...@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
...@@ -24,8 +24,26 @@ ...@@ -24,8 +24,26 @@
DF544AA72177253600931378 /* UIViewControllerDemo.xib in Resources */ = {isa = PBXBuildFile; fileRef = DF544AA52177253600931378 /* UIViewControllerDemo.xib */; }; DF544AA72177253600931378 /* UIViewControllerDemo.xib in Resources */ = {isa = PBXBuildFile; fileRef = DF544AA52177253600931378 /* UIViewControllerDemo.xib */; };
DF544AD4217838EF00931378 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = DF544AD3217838EF00931378 /* libc++.tbd */; }; DF544AD4217838EF00931378 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = DF544AD3217838EF00931378 /* libc++.tbd */; };
DFD80BFA217DF95400E3F036 /* PlatformRouterImp.m in Sources */ = {isa = PBXBuildFile; fileRef = DFD80BF9217DF95400E3F036 /* PlatformRouterImp.m */; }; DFD80BFA217DF95400E3F036 /* PlatformRouterImp.m in Sources */ = {isa = PBXBuildFile; fileRef = DFD80BF9217DF95400E3F036 /* PlatformRouterImp.m */; };
FA32AEF623B4A56E00449D68 /* NativeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FA32AEF523B4A56D00449D68 /* NativeViewController.m */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
FA32AEEF23B4685B00449D68 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = FA32AEEA23B4685A00449D68 /* products.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = EDB8DD4E9A4BE1510409988F;
remoteInfo = sources;
};
FA32AEF123B4686E00449D68 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = FA32AEEA23B4685A00449D68 /* products.xcodeproj */;
proxyType = 1;
remoteGlobalIDString = 7EE23C83374079D8D3916ACE;
remoteInfo = All;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = { 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase; isa = PBXCopyFilesBuildPhase;
...@@ -68,6 +86,9 @@ ...@@ -68,6 +86,9 @@
DF544AD3217838EF00931378 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; DF544AD3217838EF00931378 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
DFD80BF8217DF95400E3F036 /* PlatformRouterImp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlatformRouterImp.h; sourceTree = "<group>"; }; DFD80BF8217DF95400E3F036 /* PlatformRouterImp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlatformRouterImp.h; sourceTree = "<group>"; };
DFD80BF9217DF95400E3F036 /* PlatformRouterImp.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PlatformRouterImp.m; sourceTree = "<group>"; }; DFD80BF9217DF95400E3F036 /* PlatformRouterImp.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PlatformRouterImp.m; sourceTree = "<group>"; };
FA32AEEA23B4685A00449D68 /* products.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = products.xcodeproj; path = ../../../engine_github/src/out/ios_debug_unopt/products.xcodeproj; sourceTree = "<group>"; };
FA32AEF423B4A56D00449D68 /* NativeViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NativeViewController.h; sourceTree = "<group>"; };
FA32AEF523B4A56D00449D68 /* NativeViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NativeViewController.m; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
...@@ -120,6 +141,7 @@ ...@@ -120,6 +141,7 @@
97C146E51CF9000F007C117D = { 97C146E51CF9000F007C117D = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FA32AEEA23B4685A00449D68 /* products.xcodeproj */,
9740EEB11CF90186004384FC /* Flutter */, 9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */, 97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */, 97C146EF1CF9000F007C117D /* Products */,
...@@ -153,6 +175,8 @@ ...@@ -153,6 +175,8 @@
97C146F11CF9000F007C117D /* Supporting Files */, 97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
FA32AEF423B4A56D00449D68 /* NativeViewController.h */,
FA32AEF523B4A56D00449D68 /* NativeViewController.m */,
); );
path = Runner; path = Runner;
sourceTree = "<group>"; sourceTree = "<group>";
...@@ -165,6 +189,14 @@ ...@@ -165,6 +189,14 @@
name = "Supporting Files"; name = "Supporting Files";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
FA32AEEB23B4685A00449D68 /* Products */ = {
isa = PBXGroup;
children = (
FA32AEF023B4685B00449D68 /* sources */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
...@@ -180,11 +212,11 @@ ...@@ -180,11 +212,11 @@
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
3FC74F80EA9D039C70D1F681 /* [CP] Embed Pods Frameworks */, 3FC74F80EA9D039C70D1F681 /* [CP] Embed Pods Frameworks */,
D49B04E67D494964A3A713AC /* [CP] Copy Pods Resources */,
); );
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
FA32AEF223B4686E00449D68 /* PBXTargetDependency */,
); );
name = Runner; name = Runner;
productName = Runner; productName = Runner;
...@@ -218,6 +250,12 @@ ...@@ -218,6 +250,12 @@
mainGroup = 97C146E51CF9000F007C117D; mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */; productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = ""; projectDirPath = "";
projectReferences = (
{
ProductGroup = FA32AEEB23B4685A00449D68 /* Products */;
ProjectRef = FA32AEEA23B4685A00449D68 /* products.xcodeproj */;
},
);
projectRoot = ""; projectRoot = "";
targets = ( targets = (
97C146ED1CF9000F007C117D /* Runner */, 97C146ED1CF9000F007C117D /* Runner */,
...@@ -225,6 +263,16 @@ ...@@ -225,6 +263,16 @@
}; };
/* End PBXProject section */ /* End PBXProject section */
/* Begin PBXReferenceProxy section */
FA32AEF023B4685B00449D68 /* sources */ = {
isa = PBXReferenceProxy;
fileType = "compiled.mach-o.executable";
path = sources;
remoteRef = FA32AEEF23B4685B00449D68 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */ /* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = { 97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
...@@ -262,8 +310,8 @@ ...@@ -262,8 +310,8 @@
files = ( files = (
); );
inputPaths = ( inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", "${PODS_ROOT}/../.symlinks/flutter/ios_debug_sim_unopt/Flutter.framework",
); );
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputPaths = ( outputPaths = (
...@@ -271,7 +319,7 @@ ...@@ -271,7 +319,7 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
9740EEB61CF901F6004384FC /* Run Script */ = { 9740EEB61CF901F6004384FC /* Run Script */ = {
...@@ -303,22 +351,7 @@ ...@@ -303,22 +351,7 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
D49B04E67D494964A3A713AC /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
...@@ -333,11 +366,20 @@ ...@@ -333,11 +366,20 @@
97C146F31CF9000F007C117D /* main.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
DF544AA62177253600931378 /* UIViewControllerDemo.m in Sources */, DF544AA62177253600931378 /* UIViewControllerDemo.m in Sources */,
FA32AEF623B4A56E00449D68 /* NativeViewController.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
FA32AEF223B4686E00449D68 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = All;
targetProxy = FA32AEF123B4686E00449D68 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */ /* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = { 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup; isa = PBXVariantGroup;
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#import "AppDelegate.h" #import "AppDelegate.h"
#import "UIViewControllerDemo.h" #import "UIViewControllerDemo.h"
#import "PlatformRouterImp.h" #import "PlatformRouterImp.h"
#import "NativeViewController.h"
#import <flutter_boost/FlutterBoost.h> #import <flutter_boost/FlutterBoost.h>
@interface AppDelegate () @interface AppDelegate ()
...@@ -50,19 +51,19 @@ ...@@ -50,19 +51,19 @@
self.window.rootViewController = rvc; self.window.rootViewController = rvc;
UIButton *nativeButton = [UIButton buttonWithType:UIButtonTypeCustom]; UIButton *nativeButton = [UIButton buttonWithType:UIButtonTypeCustom];
nativeButton.frame = CGRectMake(self.window.frame.size.width * 0.5 - 50, 200, 100, 45); nativeButton.frame = CGRectMake(self.window.frame.size.width * 0.5 - 50, 200, 100, 40);
nativeButton.backgroundColor = [UIColor redColor]; nativeButton.backgroundColor = [UIColor redColor];
[nativeButton setTitle:@"push native" forState:UIControlStateNormal]; [nativeButton setTitle:@"push native" forState:UIControlStateNormal];
[nativeButton addTarget:self action:@selector(pushNative) forControlEvents:UIControlEventTouchUpInside]; [nativeButton addTarget:self action:@selector(pushNative) forControlEvents:UIControlEventTouchUpInside];
[self.window addSubview:nativeButton]; [self.window addSubview:nativeButton];
UIButton *pushEmbeded = [UIButton buttonWithType:UIButtonTypeCustom];
pushEmbeded.frame = CGRectMake(self.window.frame.size.width * 0.5 - 70, 150, 140, 40);
pushEmbeded.backgroundColor = [UIColor redColor];
[pushEmbeded setTitle:@"push embeded" forState:UIControlStateNormal];
[pushEmbeded addTarget:self action:@selector(pushEmbeded) forControlEvents:UIControlEventTouchUpInside];
[self.window addSubview:pushEmbeded];
return YES; return YES;
} }
...@@ -74,6 +75,12 @@ ...@@ -74,6 +75,12 @@
[nvc pushViewController:vc animated:YES]; [nvc pushViewController:vc animated:YES];
} }
- (void)pushEmbeded
{
UINavigationController *nvc = (id)self.window.rootViewController;
UIViewController *vc = [[NativeViewController alloc] init];
[nvc pushViewController:vc animated:YES];
}
- (void)applicationWillResignActive:(UIApplication *)application { - (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
......
...@@ -7,8 +7,11 @@ ...@@ -7,8 +7,11 @@
#import <Flutter/Flutter.h> #import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN
@interface GeneratedPluginRegistrant : NSObject @interface GeneratedPluginRegistrant : NSObject
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry; + (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry;
@end @end
NS_ASSUME_NONNULL_END
#endif /* GeneratedPluginRegistrant_h */ #endif /* GeneratedPluginRegistrant_h */
...@@ -3,7 +3,12 @@ ...@@ -3,7 +3,12 @@
// //
#import "GeneratedPluginRegistrant.h" #import "GeneratedPluginRegistrant.h"
#if __has_include(<flutter_boost/FlutterBoostPlugin.h>)
#import <flutter_boost/FlutterBoostPlugin.h> #import <flutter_boost/FlutterBoostPlugin.h>
#else
@import flutter_boost;
#endif
@implementation GeneratedPluginRegistrant @implementation GeneratedPluginRegistrant
......
//
// NativeViewController.h
// Runner
//
// Created by yujie on 2019/12/26.
// Copyright © 2019 The Chromium Authors. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface NativeViewController : UIViewController
@end
NS_ASSUME_NONNULL_END
//
// NativeViewController.m
// Runner
//
// Created by yujie on 2019/12/26.
// Copyright © 2019 The Chromium Authors. All rights reserved.
//
#import "NativeViewController.h"
#import <Flutter/Flutter.h>
#import <flutter_boost/FlutterBoost.h>
@interface NativeViewController ()
@property(nonatomic, strong)FLBFlutterViewContainer *flutterContainer;
@end
@implementation NativeViewController
- (instancetype)init{
if (self = [super init]) {
_flutterContainer = [[FLBFlutterViewContainer alloc]init];
[_flutterContainer setName:@"embeded" params:@{}];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor grayColor];
self.flutterContainer.view.frame = CGRectInset(self.view.bounds, 30, 100);
[self.view addSubview:self.flutterContainer.view];
[self addChildViewController:self.flutterContainer];
UIButton *nativeButton = [UIButton buttonWithType:UIButtonTypeCustom];
nativeButton.frame = CGRectMake(50,self.view.bounds.size.height-50,200,40);
nativeButton.backgroundColor = [UIColor blueColor];
[nativeButton setTitle:@"Button in Native" forState:UIControlStateNormal];
[nativeButton addTarget:self action:@selector(pushMe) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:nativeButton];
}
- (void)pushMe
{
UIViewController *vc = [[UIViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
//注意这行代码不可缺少
// [self.flutterContainer.view setNeedsLayout];
// [self.flutterContainer.view layoutIfNeeded];
}
//NOTES: embed情景下必须实现!!!
- (void)didMoveToParentViewController:(UIViewController *)parent {
[self.flutterContainer didMoveToParentViewController:parent];
[super didMoveToParentViewController:parent];
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
- (void)dealloc{
NSLog(@"dealloc native controller%p", self.flutterContainer);
}
@end
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
// //
#import "PlatformRouterImp.h" #import "PlatformRouterImp.h"
#import "UIViewControllerDemo.h"
#import <flutter_boost/FlutterBoost.h> #import <flutter_boost/FlutterBoost.h>
@interface PlatformRouterImp() @interface PlatformRouterImp()
...@@ -14,12 +15,30 @@ ...@@ -14,12 +15,30 @@
@implementation PlatformRouterImp @implementation PlatformRouterImp
- (void)openNativeVC:(NSString *)name
urlParams:(NSDictionary *)params
exts:(NSDictionary *)exts{
UIViewController *vc = UIViewControllerDemo.new;
BOOL animated = [exts[@"animated"] boolValue];
if([params[@"present"] boolValue]){
[self.navigationController presentViewController:vc animated:animated completion:^{
}];
}else{
[self.navigationController pushViewController:vc animated:animated];
}
}
#pragma mark - Boost 1.5 #pragma mark - Boost 1.5
- (void)open:(NSString *)name - (void)open:(NSString *)name
urlParams:(NSDictionary *)params urlParams:(NSDictionary *)params
exts:(NSDictionary *)exts exts:(NSDictionary *)exts
completion:(void (^)(BOOL))completion completion:(void (^)(BOOL))completion
{ {
if ([name isEqualToString:@"native"]) {//模拟打开native页面
[self openNativeVC:name urlParams:params exts:exts];
return;
}
BOOL animated = [exts[@"animated"] boolValue]; BOOL animated = [exts[@"animated"] boolValue];
FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new; FLBFlutterViewContainer *vc = FLBFlutterViewContainer.new;
[vc setName:name params:params]; [vc setName:name params:params];
......
Future<Map<String,dynamic>> open(String url,{Map<String,dynamic> urlParams,Map<String,dynamic> exts}){
}
void close(String id,{Map<String,dynamic> result,Map<String,dynamic> exts}){
}
\ No newline at end of file
...@@ -17,8 +17,11 @@ class _MyAppState extends State<MyApp> { ...@@ -17,8 +17,11 @@ class _MyAppState extends State<MyApp> {
super.initState(); super.initState();
FlutterBoost.singleton.registerPageBuilders({ FlutterBoost.singleton.registerPageBuilders({
'embeded': (pageName, params, _)=>EmbededFirstRouteWidget(),
'first': (pageName, params, _) => FirstRouteWidget(), 'first': (pageName, params, _) => FirstRouteWidget(),
'firstFirst': (pageName, params, _) => FirstFirstRouteWidget(),
'second': (pageName, params, _) => SecondRouteWidget(), 'second': (pageName, params, _) => SecondRouteWidget(),
'secondStateful': (pageName, params, _) => SecondStatefulRouteWidget(),
'tab': (pageName, params, _) => TabRouteWidget(), 'tab': (pageName, params, _) => TabRouteWidget(),
'platformView': (pageName, params, _) => PlatformRouteWidget(), 'platformView': (pageName, params, _) => PlatformRouteWidget(),
'flutterFragment': (pageName, params, _) => FragmentRouteWidget(params), 'flutterFragment': (pageName, params, _) => FragmentRouteWidget(params),
...@@ -29,6 +32,7 @@ class _MyAppState extends State<MyApp> { ...@@ -29,6 +32,7 @@ class _MyAppState extends State<MyApp> {
return FlutterRouteWidget(params:params); return FlutterRouteWidget(params:params);
}, },
}); });
FlutterBoost.singleton.addBoostNavigatorObserver(TestBoostNavigatorObserver());
} }
@override @override
...@@ -36,10 +40,31 @@ class _MyAppState extends State<MyApp> { ...@@ -36,10 +40,31 @@ class _MyAppState extends State<MyApp> {
return MaterialApp( return MaterialApp(
title: 'Flutter Boost example', title: 'Flutter Boost example',
builder: FlutterBoost.init(postPush: _onRoutePushed), builder: FlutterBoost.init(postPush: _onRoutePushed),
home: Container()); home: Container(
color:Colors.white
));
} }
void _onRoutePushed( void _onRoutePushed(
String pageName, String uniqueId, Map params, Route route, Future _) { String pageName, String uniqueId, Map params, Route route, Future _) {
} }
} }
class TestBoostNavigatorObserver extends NavigatorObserver{
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
print("flutterboost#didPush");
}
void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
print("flutterboost#didPop");
}
void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) {
print("flutterboost#didRemove");
}
void didReplace({Route<dynamic> newRoute, Route<dynamic> oldRoute}) {
print("flutterboost#didReplace");
}
}
...@@ -3,7 +3,154 @@ import 'package:flutter/material.dart'; ...@@ -3,7 +3,154 @@ import 'package:flutter/material.dart';
import 'package:flutter_boost/flutter_boost.dart'; import 'package:flutter_boost/flutter_boost.dart';
import 'package:flutter_boost_example/platform_view.dart'; import 'package:flutter_boost_example/platform_view.dart';
class FirstRouteWidget extends StatelessWidget { class FirstRouteWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new _FirstRouteWidgetState();
}
}
class _FirstRouteWidgetState extends State<FirstRouteWidget>{
_FirstRouteWidgetState();
@override
void initState() {
print('initState');
super.initState();
}
@override
void didChangeDependencies() {
print('didChangeDependencies');
super.didChangeDependencies();
}
@override
void didUpdateWidget(FirstRouteWidget oldWidget) {
print('didUpdateWidget');
super.didUpdateWidget(oldWidget);
}
@override
void deactivate() {
print('deactivate');
super.deactivate();
}
@override
void dispose() {
print('[XDEBUG] - FirstRouteWidget is disposing~');
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: Center(
child:
Column(
mainAxisAlignment: MainAxisAlignment.center,
children:
<Widget>[
RaisedButton(
child: Text('Open native page'),
onPressed: () {
print("open natve page!");
FlutterBoost.singleton.open("native").then((Map value) {
print(
"call me when page is finished. did recieve native route result $value");
});
},
),
RaisedButton(
child: Text('Open FF route'),
onPressed: () {
print("open FF page!");
FlutterBoost.singleton.open("firstFirst").then((Map value) {
print(
"call me when page is finished. did recieve FF route result $value");
});
},
),
RaisedButton(
child: Text('Open second route1'),
onPressed: () {
print("open second page!");
FlutterBoost.singleton.open("second").then((Map value) {
print(
"call me when page is finished. did recieve second route result $value");
});
},
),
RaisedButton(
child: Text('Present second stateful route'),
onPressed: () {
print("Present second stateful page!");
FlutterBoost.singleton.open("secondStateful",urlParams:<dynamic,dynamic>{"present":true}).then((Map value) {
print(
"call me when page is finished. did recieve second stateful route result $value");
});
},
),
RaisedButton(
child: Text('Present second route'),
onPressed: () {
print("Present second page!");
FlutterBoost.singleton.open("second",urlParams:<dynamic,dynamic>{"present":true}).then((Map value) {
print(
"call me when page is finished. did recieve second route result $value");
});
},
),
],
),
),
);
}
}
class FirstFirstRouteWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new _FirstFirstRouteWidgetState();
}
}
class _FirstFirstRouteWidgetState extends State<FirstFirstRouteWidget>{
_FirstFirstRouteWidgetState();
@override
void initState() {
print('initState');
super.initState();
}
@override
void didChangeDependencies() {
print('didChangeDependencies');
super.didChangeDependencies();
}
@override
void didUpdateWidget(FirstFirstRouteWidget oldWidget) {
print('didUpdateWidget');
super.didUpdateWidget(oldWidget);
}
@override
void deactivate() {
print('deactivate');
super.deactivate();
}
@override
void dispose() {
print('[XDEBUG] - FirstFirstRouteWidget is disposing~');
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
...@@ -12,7 +159,39 @@ class FirstRouteWidget extends StatelessWidget { ...@@ -12,7 +159,39 @@ class FirstRouteWidget extends StatelessWidget {
), ),
body: Center( body: Center(
child: RaisedButton( child: RaisedButton(
child: Text('Open second route'), child: Text('Open first route'),
onPressed: () {
print("open first page again!");
FlutterBoost.singleton.open("first").then((Map value){
print("did recieve first route result");
print("did recieve first route result $value");
});
},
),
),
);
}
}
class EmbededFirstRouteWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _EmbededFirstRouteWidgetState();
}
}
class _EmbededFirstRouteWidgetState extends State<EmbededFirstRouteWidget> {
@override
Widget build(BuildContext context) {
print('_EmbededFirstRouteWidgetState build called!');
return Scaffold(
body: Center(
child: RaisedButton(
child: Text('Open second route2'),
onPressed: () { onPressed: () {
print("open second page!"); print("open second page!");
FlutterBoost.singleton.open("second").then((Map value) { FlutterBoost.singleton.open("second").then((Map value) {
...@@ -24,6 +203,48 @@ class FirstRouteWidget extends StatelessWidget { ...@@ -24,6 +203,48 @@ class FirstRouteWidget extends StatelessWidget {
), ),
); );
} }
@override
void dispose() {
print('[XDEBUG]:_EmbededFirstRouteWidgetState disposing~');
super.dispose();
}
}
class SecondStatefulRouteWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _SecondStatefulRouteWidgetState();
}
}
class _SecondStatefulRouteWidgetState extends State<SecondStatefulRouteWidget>{
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("SecondStateful Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
// Navigate back to first route when tapped.
BoostContainerSettings settings =
BoostContainer.of(context).settings;
FlutterBoost.singleton.close(settings.uniqueId,
result: <dynamic,dynamic>{"result": "data from second"});
},
child: Text('Go back with result!'),
),
),
);
}
@override
void dispose() {
print('[XDEBUG]:SecondStatefulRouteWidget disposing~');
super.dispose();
}
} }
class SecondRouteWidget extends StatelessWidget { class SecondRouteWidget extends StatelessWidget {
...@@ -41,7 +262,7 @@ class SecondRouteWidget extends StatelessWidget { ...@@ -41,7 +262,7 @@ class SecondRouteWidget extends StatelessWidget {
BoostContainerSettings settings = BoostContainerSettings settings =
BoostContainer.of(context).settings; BoostContainer.of(context).settings;
FlutterBoost.singleton.close(settings.uniqueId, FlutterBoost.singleton.close(settings.uniqueId,
result: {"result": "data from second"}); result: <dynamic,dynamic>{"result": "data from second"});
}, },
child: Text('Go back with result!'), child: Text('Go back with result!'),
), ),
...@@ -62,7 +283,7 @@ class TabRouteWidget extends StatelessWidget { ...@@ -62,7 +283,7 @@ class TabRouteWidget extends StatelessWidget {
onPressed: () { onPressed: () {
FlutterBoost.singleton.open("second"); FlutterBoost.singleton.open("second");
}, },
child: Text('Open second route'), child: Text('Open second route3'),
), ),
), ),
); );
...@@ -157,7 +378,7 @@ class _FlutterRouteWidgetState extends State<FlutterRouteWidget> { ...@@ -157,7 +378,7 @@ class _FlutterRouteWidgetState extends State<FlutterRouteWidget> {
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。 ///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb ///例如:sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton onTap: () => FlutterBoost.singleton
.open("sample://nativePage", urlParams: { .open("sample://nativePage", urlParams: <dynamic,dynamic>{
"query": {"aaa": "bbb"} "query": {"aaa": "bbb"}
}), }),
), ),
...@@ -174,7 +395,7 @@ class _FlutterRouteWidgetState extends State<FlutterRouteWidget> { ...@@ -174,7 +395,7 @@ class _FlutterRouteWidgetState extends State<FlutterRouteWidget> {
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。 ///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb ///例如:sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton onTap: () => FlutterBoost.singleton
.open("first", urlParams: { .open("first", urlParams: <dynamic,dynamic>{
"query": {"aaa": "bbb"} "query": {"aaa": "bbb"}
}), }),
), ),
...@@ -191,7 +412,7 @@ class _FlutterRouteWidgetState extends State<FlutterRouteWidget> { ...@@ -191,7 +412,7 @@ class _FlutterRouteWidgetState extends State<FlutterRouteWidget> {
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。 ///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb ///例如:sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton onTap: () => FlutterBoost.singleton
.open("second", urlParams: { .open("second", urlParams:<dynamic,dynamic> {
"query": {"aaa": "bbb"} "query": {"aaa": "bbb"}
}), }),
), ),
...@@ -208,7 +429,7 @@ class _FlutterRouteWidgetState extends State<FlutterRouteWidget> { ...@@ -208,7 +429,7 @@ class _FlutterRouteWidgetState extends State<FlutterRouteWidget> {
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。 ///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb ///例如:sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton onTap: () => FlutterBoost.singleton
.open("tab", urlParams: { .open("tab", urlParams:<dynamic,dynamic> {
"query": {"aaa": "bbb"} "query": {"aaa": "bbb"}
}), }),
), ),
...@@ -225,7 +446,7 @@ class _FlutterRouteWidgetState extends State<FlutterRouteWidget> { ...@@ -225,7 +446,7 @@ class _FlutterRouteWidgetState extends State<FlutterRouteWidget> {
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。 ///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb ///例如:sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton onTap: () => FlutterBoost.singleton
.open("sample://flutterPage", urlParams: { .open("sample://flutterPage", urlParams:<String,dynamic> {
"query": {"aaa": "bbb"} "query": {"aaa": "bbb"}
}), }),
), ),
...@@ -239,8 +460,8 @@ class _FlutterRouteWidgetState extends State<FlutterRouteWidget> { ...@@ -239,8 +460,8 @@ class _FlutterRouteWidgetState extends State<FlutterRouteWidget> {
style: TextStyle(fontSize: 22.0, color: Colors.black), style: TextStyle(fontSize: 22.0, color: Colors.black),
)), )),
onTap: () { onTap: () {
Navigator.push(context, Navigator.push<dynamic>(context,
MaterialPageRoute(builder: (_) => PushWidget())); MaterialPageRoute<dynamic>(builder: (_) => PushWidget()));
}, },
), ),
...@@ -254,8 +475,8 @@ class _FlutterRouteWidgetState extends State<FlutterRouteWidget> { ...@@ -254,8 +475,8 @@ class _FlutterRouteWidgetState extends State<FlutterRouteWidget> {
style: TextStyle(fontSize: 22.0, color: Colors.black), style: TextStyle(fontSize: 22.0, color: Colors.black),
)), )),
onTap: () { onTap: () {
Navigator.push(context, Navigator.push<dynamic>(context,
MaterialPageRoute(builder: (_) => PlatformRouteWidget())); MaterialPageRoute<dynamic>(builder: (_) => PlatformRouteWidget()));
}, },
), ),
InkWell( InkWell(
...@@ -383,6 +604,7 @@ class _PushWidgetState extends State<PushWidget> { ...@@ -383,6 +604,7 @@ class _PushWidgetState extends State<PushWidget> {
@override @override
void dispose() { void dispose() {
// TODO: implement dispose // TODO: implement dispose
print('[XDEBUG] - PushWidget is disposing~');
super.dispose(); super.dispose();
_backPressedListenerUnsub?.call(); _backPressedListenerUnsub?.call();
} }
......
...@@ -134,7 +134,7 @@ class _TestTextFieldState extends State<TestTextField> { ...@@ -134,7 +134,7 @@ class _TestTextFieldState extends State<TestTextField> {
if (_node.hasFocus) { if (_node.hasFocus) {
print('showBottomSheet'); print('showBottomSheet');
_controller = Scaffold.of(context) _controller = Scaffold.of(context)
.showBottomSheet((BuildContext ctx) => Container( .showBottomSheet<dynamic>((BuildContext ctx) => Container(
width: double.infinity, width: double.infinity,
height: 36.0, height: 36.0,
color: Colors.deepPurple, color: Colors.deepPurple,
......
Future<Map<String,dynamic>> open(String url,{Map<String,dynamic> urlParams,Map<String,dynamic> exts}){
}
void close(String id,{Map<String,dynamic> result,Map<String,dynamic> exts}){
}
\ No newline at end of file
...@@ -38,7 +38,7 @@ class SecondRouteWidget extends StatelessWidget { ...@@ -38,7 +38,7 @@ class SecondRouteWidget extends StatelessWidget {
BoostContainerSettings settings = BoostContainerSettings settings =
BoostContainer.of(context).settings; BoostContainer.of(context).settings;
FlutterBoost.singleton.close(settings.uniqueId, FlutterBoost.singleton.close(settings.uniqueId,
result: {"result": "data from second"}); result: <dynamic,dynamic>{"result": "data from second"});
}, },
child: Text('Go back with result!'), child: Text('Go back with result!'),
), ),
...@@ -102,7 +102,7 @@ class FlutterRouteWidget extends StatelessWidget { ...@@ -102,7 +102,7 @@ class FlutterRouteWidget extends StatelessWidget {
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。 ///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb ///例如:sample://nativePage?aaa=bbb
onTap: () => onTap: () =>
FlutterBoost.singleton.open("sample://nativePage", urlParams: { FlutterBoost.singleton.open("sample://nativePage", urlParams: <dynamic,dynamic>{
"query": {"aaa": "bbb"} "query": {"aaa": "bbb"}
}), }),
), ),
...@@ -119,7 +119,7 @@ class FlutterRouteWidget extends StatelessWidget { ...@@ -119,7 +119,7 @@ class FlutterRouteWidget extends StatelessWidget {
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。 ///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb ///例如:sample://nativePage?aaa=bbb
onTap: () => onTap: () =>
FlutterBoost.singleton.open("sample://flutterPage", urlParams: { FlutterBoost.singleton.open("sample://flutterPage", urlParams: <dynamic,dynamic>{
"query": {"aaa": "bbb"} "query": {"aaa": "bbb"}
}), }),
), ),
...@@ -133,8 +133,8 @@ class FlutterRouteWidget extends StatelessWidget { ...@@ -133,8 +133,8 @@ class FlutterRouteWidget extends StatelessWidget {
style: TextStyle(fontSize: 22.0, color: Colors.black), style: TextStyle(fontSize: 22.0, color: Colors.black),
)), )),
onTap: () { onTap: () {
Navigator.push( Navigator.push<dynamic>(
context, MaterialPageRoute(builder: (_) => PushWidget())); context, MaterialPageRoute<dynamic>(builder: (_) => PushWidget()));
}, },
), ),
InkWell( InkWell(
......
...@@ -134,7 +134,7 @@ class _TestTextFieldState extends State<TestTextField> { ...@@ -134,7 +134,7 @@ class _TestTextFieldState extends State<TestTextField> {
if (_node.hasFocus) { if (_node.hasFocus) {
print('showBottomSheet'); print('showBottomSheet');
_controller = Scaffold.of(context) _controller = Scaffold.of(context)
.showBottomSheet((BuildContext ctx) => Container( .showBottomSheet<dynamic>((BuildContext ctx) => Container(
width: double.infinity, width: double.infinity,
height: 36.0, height: 36.0,
color: Colors.deepPurple, color: Colors.deepPurple,
......
name: flutter_boost_example name: example_swift
description: Demonstrates how to use the flutter_boost plugin. description: Demonstrates how to use the flutter_boost plugin.
# The following defines the version and build number for your application. # The following defines the version and build number for your application.
......
...@@ -36,6 +36,7 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -36,6 +36,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)startFlutterWithPlatform:(id<FLBPlatform>)platform - (void)startFlutterWithPlatform:(id<FLBPlatform>)platform
withEngine:(FlutterEngine* _Nullable)engine withEngine:(FlutterEngine* _Nullable)engine
withPluginRegisterred:(BOOL)registerPlugin
onStart:(void (^)(FlutterEngine *engine))callback; onStart:(void (^)(FlutterEngine *engine))callback;
- (FlutterViewController *)flutterViewController; - (FlutterViewController *)flutterViewController;
...@@ -45,6 +46,7 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -45,6 +46,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)addUniqueViewController:(id<FLBFlutterContainer>)vc; - (void)addUniqueViewController:(id<FLBFlutterContainer>)vc;
- (void)removeViewController:(id<FLBFlutterContainer>)vc; - (void)removeViewController:(id<FLBFlutterContainer>)vc;
- (BOOL)isTop:(NSString *)pageId; - (BOOL)isTop:(NSString *)pageId;
- (NSInteger)pageCount;
#pragma mark - App Control #pragma mark - App Control
- (void)pause; - (void)pause;
...@@ -64,6 +66,7 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -64,6 +66,7 @@ NS_ASSUME_NONNULL_BEGIN
onPageFinished:(void (^)(NSDictionary *))resultCallback onPageFinished:(void (^)(NSDictionary *))resultCallback
completion:(void (^)(BOOL))completion; completion:(void (^)(BOOL))completion;
- (void)attachToPreviousContainer;
- (void)didInitPageContainer:(NSString *)url - (void)didInitPageContainer:(NSString *)url
params:(NSDictionary *)urlParams params:(NSDictionary *)urlParams
...@@ -73,6 +76,9 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -73,6 +76,9 @@ NS_ASSUME_NONNULL_BEGIN
params:(NSDictionary *)params params:(NSDictionary *)params
uniqueId:(NSString *)uniqueId; uniqueId:(NSString *)uniqueId;
- (void)onShownContainerChanged:(NSString *)uniqueId
params:(NSDictionary *)params;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END
......
...@@ -32,6 +32,7 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -32,6 +32,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)addUnique:(id<FLBFlutterContainer>)vc; - (void)addUnique:(id<FLBFlutterContainer>)vc;
- (void)remove:(id<FLBFlutterContainer>)vc; - (void)remove:(id<FLBFlutterContainer>)vc;
- (BOOL)contains:(id<FLBFlutterContainer>)vc; - (BOOL)contains:(id<FLBFlutterContainer>)vc;
- (NSInteger)pageCount;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END
...@@ -79,6 +79,10 @@ ...@@ -79,6 +79,10 @@
return _idStk.lastObject; return _idStk.lastObject;
} }
- (NSInteger)pageCount{
return _idStk.count;
}
#if DEBUG #if DEBUG
- (void)dump:(NSString*)flag{ - (void)dump:(NSString*)flag{
NSMutableString *log = [[NSMutableString alloc]initWithFormat:@"[DEBUG]--%@--PageStack uid/name", flag]; NSMutableString *log = [[NSMutableString alloc]initWithFormat:@"[DEBUG]--%@--PageStack uid/name", flag];
......
...@@ -31,7 +31,7 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -31,7 +31,7 @@ NS_ASSUME_NONNULL_BEGIN
@protocol FLBFlutterProvider <NSObject> @protocol FLBFlutterProvider <NSObject>
@required @required
- (FlutterEngine *)engine; - (FlutterEngine *)engine;
- (void)atacheToViewController:(FlutterViewController *)vc; - (BOOL)atacheToViewController:(FlutterViewController *)vc;
- (void)detach; - (void)detach;
- (void)prepareEngineIfNeeded; - (void)prepareEngineIfNeeded;
- (void)pause; - (void)pause;
......
...@@ -32,7 +32,13 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -32,7 +32,13 @@ NS_ASSUME_NONNULL_BEGIN
+ (instancetype)sharedInstance; + (instancetype)sharedInstance;
/** /**
* 初始化FlutterBoost混合栈环境。应在程序使用混合栈之前调用。如在AppDelegate中 * 获取当前管理的页面栈中页面的个数
*
*/
+ (NSInteger)pageCount;
/**
* 初始化FlutterBoost混合栈环境。应在程序使用混合栈之前调用。如在AppDelegate中。本函数默认需要flutter boost来注册所有插件。
* *
* @param platform 平台层实现FLBPlatform的对象 * @param platform 平台层实现FLBPlatform的对象
* @param callback 启动之后回调 * @param callback 启动之后回调
...@@ -40,7 +46,7 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -40,7 +46,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)startFlutterWithPlatform:(id<FLBPlatform>)platform - (void)startFlutterWithPlatform:(id<FLBPlatform>)platform
onStart:(void (^)(FlutterEngine *engine))callback; onStart:(void (^)(FlutterEngine *engine))callback;
/** /**
* 初始化FlutterBoost混合栈环境。应在程序使用混合栈之前调用。如在AppDelegate中 * 初始化FlutterBoost混合栈环境。应在程序使用混合栈之前调用。如在AppDelegate中。本函数默认需要flutter boost来注册所有插件。
* *
* @param platform 平台层实现FLBPlatform的对象 * @param platform 平台层实现FLBPlatform的对象
* @param engine 外部实例化engine后传入 * @param engine 外部实例化engine后传入
...@@ -50,6 +56,17 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -50,6 +56,17 @@ NS_ASSUME_NONNULL_BEGIN
engine:(FlutterEngine* _Nullable)engine engine:(FlutterEngine* _Nullable)engine
onStart:(void (^)(FlutterEngine *engine))callback; onStart:(void (^)(FlutterEngine *engine))callback;
/**
* 初始化FlutterBoost混合栈环境。应在程序使用混合栈之前调用。如在AppDelegate中。本函数可以控制是否需要flutter boost来注册所有插件
*
* @param platform 平台层实现FLBPlatform的对象
* @param engine 外部实例化engine后传入
* @param callback 启动之后回调
*/
- (void)startFlutterWithPlatform:(id<FLBPlatform>)platform
engine:(FlutterEngine* _Nullable)engine
pluginRegisterred:(BOOL)registerPlugin
onStart:(void (^)(FlutterEngine *engine))callback;
#pragma mark - Some properties. #pragma mark - Some properties.
- (BOOL)isRunning; - (BOOL)isRunning;
...@@ -119,5 +136,12 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -119,5 +136,12 @@ NS_ASSUME_NONNULL_BEGIN
exts:(NSDictionary *)exts exts:(NSDictionary *)exts
onPageFinished:(void (^)(NSDictionary *))resultCallback onPageFinished:(void (^)(NSDictionary *))resultCallback
completion:(void (^)(BOOL))completion; completion:(void (^)(BOOL))completion;
//切记:在destroyPluginContext前务必将所有FlutterViewController及其子类的实例销毁。在这里是FLBFlutterViewContainer。否则会异常;以下是全部步骤
//1. 首先通过为所有FlutterPlugin的methodChannel属性设为nil来解除其与FlutterEngine的间接强引用
//2. 销毁所有的FlutterViewController实例(或保证所有FlutterVC已经退出),来解除其与FlutterEngine的强引用,在每个VC卸载的时候FlutterEngine会调用destroyContext
//3. 调用FlutterBoostPlugin.destroyPluginContext函数来解除与其内部context的强引用。内部持有的FlutterEngine也会被卸载(非外部传入的情形)
//4. 如果是外部传入的FlutterEngine,需要外部自己释放
- (void)destroyPluginContext;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END
...@@ -67,8 +67,15 @@ ...@@ -67,8 +67,15 @@
result(@(r)); result(@(r));
}]; }];
}else if([@"onShownContainerChanged" isEqualToString:call.method]){ }else if([@"onShownContainerChanged" isEqualToString:call.method]){
NSString *newName = call.arguments[@"newName"]; NSDictionary *args = [FLBCollectionHelper deepCopyNSDictionary:call.arguments
filter:^bool(id _Nonnull value) {
return ![value isKindOfClass:NSNull.class];
}];
NSString *newName = args[@"newName"];
NSString *uid = args[@"uniqueId"];
if(newName){ if(newName){
[[FlutterBoostPlugin sharedInstance].application onShownContainerChanged:uid params:args];
[NSNotificationCenter.defaultCenter postNotificationName:@"flutter_boost_container_showed" [NSNotificationCenter.defaultCenter postNotificationName:@"flutter_boost_container_showed"
object:newName]; object:newName];
} }
...@@ -111,24 +118,43 @@ ...@@ -111,24 +118,43 @@
return _instance; return _instance;
} }
+ (NSInteger)pageCount{
id<FLBFlutterApplicationInterface> app = [[FlutterBoostPlugin sharedInstance] application];
return [app pageCount];
}
- (void)startFlutterWithPlatform:(id<FLBPlatform>)platform - (void)startFlutterWithPlatform:(id<FLBPlatform>)platform
onStart:(void (^)(FlutterEngine *engine))callback; onStart:(void (^)(FlutterEngine *engine))callback;
{ {
[self startFlutterWithPlatform:platform engine:nil onStart:callback]; [self startFlutterWithPlatform:platform
engine:nil
pluginRegisterred:YES
onStart:callback];
} }
- (void)startFlutterWithPlatform:(id<FLBPlatform>)platform - (void)startFlutterWithPlatform:(id<FLBPlatform>)platform
engine:(FlutterEngine* _Nullable)engine engine:(FlutterEngine* _Nullable)engine
onStart:(void (^)(FlutterEngine *engine))callback; onStart:(void (^)(FlutterEngine *engine))callback;
{ {
[self startFlutterWithPlatform:platform
engine:engine
pluginRegisterred:YES
onStart:callback];
}
- (void)startFlutterWithPlatform:(id<FLBPlatform>)platform
engine:(FlutterEngine *)engine
pluginRegisterred:(BOOL)registerPlugin
onStart:(void (^)(FlutterEngine * _Nonnull))callback{
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
__weak __typeof__(self) weakSelf = self; __weak __typeof__(self) weakSelf = self;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
__strong __typeof__(weakSelf) self = weakSelf; __strong __typeof__(weakSelf) self = weakSelf;
self.factory = FLBFactory.new; FLBFactory *factory = FLBFactory.new;
self.application = [self->_factory createApplication:platform]; self.application = [factory createApplication:platform];
[self.application startFlutterWithPlatform:platform [self.application startFlutterWithPlatform:platform
withEngine:engine withEngine:engine
withPluginRegisterred:registerPlugin
onStart:callback]; onStart:callback];
}); });
} }
...@@ -177,4 +203,9 @@ ...@@ -177,4 +203,9 @@
id<FLBFlutterApplicationInterface> app = [[FlutterBoostPlugin sharedInstance] application]; id<FLBFlutterApplicationInterface> app = [[FlutterBoostPlugin sharedInstance] application];
[app close:uniqueId result:resultData exts:exts completion:completion]; [app close:uniqueId result:resultData exts:exts completion:completion];
} }
- (void)destroyPluginContext{
self.methodChannel = nil;
self.application = nil;
}
@end @end
...@@ -28,7 +28,6 @@ ...@@ -28,7 +28,6 @@
#import "FlutterBoostPlugin.h" #import "FlutterBoostPlugin.h"
@interface FlutterBoostPlugin() @interface FlutterBoostPlugin()
@property (nonatomic,strong) id<FLBFlutterApplicationInterface> application; @property (nonatomic,strong) id<FLBFlutterApplicationInterface> application;
@property (nonatomic,strong) id<FLBAbstractFactory> factory;
@property (nonatomic,strong) FlutterMethodChannel *methodChannel; @property (nonatomic,strong) FlutterMethodChannel *methodChannel;
@property (nonatomic,copy) NSString *fPageId; @property (nonatomic,copy) NSString *fPageId;
@property (nonatomic,copy) NSString *fPagename; @property (nonatomic,copy) NSString *fPagename;
......
...@@ -26,10 +26,12 @@ ...@@ -26,10 +26,12 @@
#import "FlutterBoost.h" #import "FlutterBoost.h"
#import "FLBFlutterContainerManager.h" #import "FLBFlutterContainerManager.h"
#import "FLBFlutterEngine.h" #import "FLBFlutterEngine.h"
#import "FLBFlutterViewContainer.h"
@interface FLBFlutterApplication() @interface FLBFlutterApplication()
@property (nonatomic,strong) FLBFlutterContainerManager *manager; @property (nonatomic,strong) FLBFlutterContainerManager *manager;
@property (nonatomic,strong) id<FLBFlutterProvider> viewProvider; @property (nonatomic,strong) id<FLBFlutterProvider> viewProvider;
@property (nonatomic, weak, readonly)FlutterViewController * previousViewController;
@property (nonatomic,assign) BOOL isRunning; @property (nonatomic,assign) BOOL isRunning;
@property (nonatomic,strong) NSMutableDictionary *pageResultCallbacks; @property (nonatomic,strong) NSMutableDictionary *pageResultCallbacks;
@property (nonatomic,strong) NSMutableDictionary *callbackCache; @property (nonatomic,strong) NSMutableDictionary *callbackCache;
...@@ -51,6 +53,7 @@ ...@@ -51,6 +53,7 @@
- (void)startFlutterWithPlatform:(id<FLBPlatform>)platform - (void)startFlutterWithPlatform:(id<FLBPlatform>)platform
withEngine:(FlutterEngine* _Nullable)engine withEngine:(FlutterEngine* _Nullable)engine
withPluginRegisterred:(BOOL)registerPlugin
onStart:(void (^)(FlutterEngine *engine))callback onStart:(void (^)(FlutterEngine *engine))callback
{ {
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
...@@ -58,6 +61,16 @@ ...@@ -58,6 +61,16 @@
self.platform = platform; self.platform = platform;
self.viewProvider = [[FLBFlutterEngine alloc] initWithPlatform:platform engine:engine]; self.viewProvider = [[FLBFlutterEngine alloc] initWithPlatform:platform engine:engine];
self.isRunning = YES; self.isRunning = YES;
if(registerPlugin){
Class clazz = NSClassFromString(@"GeneratedPluginRegistrant");
FlutterEngine *myengine = [self.viewProvider engine];
if (clazz && myengine) {
if ([clazz respondsToSelector:NSSelectorFromString(@"registerWithRegistry:")]) {
[clazz performSelector:NSSelectorFromString(@"registerWithRegistry:")
withObject:myengine];
}
}
}
if(callback) callback(self.viewProvider.engine); if(callback) callback(self.viewProvider.engine);
}); });
} }
...@@ -116,6 +129,9 @@ ...@@ -116,6 +129,9 @@
return [_manager remove:vc]; return [_manager remove:vc];
} }
- (NSInteger)pageCount{
return [_manager pageCount];
}
- (BOOL)isTop:(NSString *)pageId - (BOOL)isTop:(NSString *)pageId
{ {
...@@ -142,6 +158,13 @@ ...@@ -142,6 +158,13 @@
return self.flutterProvider.engine.viewController; return self.flutterProvider.engine.viewController;
} }
- (void)attachToPreviousContainer{
if([self.viewProvider atacheToViewController:self.previousViewController]){
[self.previousViewController.view setNeedsLayout];
[(FLBFlutterViewContainer*)self.previousViewController surfaceUpdated:YES];
}
}
- (void)close:(NSString *)uniqueId - (void)close:(NSString *)uniqueId
result:(NSDictionary *)resultData result:(NSDictionary *)resultData
exts:(NSDictionary *)exts exts:(NSDictionary *)exts
...@@ -175,7 +198,7 @@ ...@@ -175,7 +198,7 @@
[newParams setObject:cid?cid:@"__default#0__" forKey:kPageCallBackId]; [newParams setObject:cid?cid:@"__default#0__" forKey:kPageCallBackId];
urlParams = newParams; urlParams = newParams;
} }
_previousViewController = [self flutterViewController];
_callbackCache[cid] = resultCallback; _callbackCache[cid] = resultCallback;
if([urlParams[@"present"]respondsToSelector:@selector(boolValue)] && [urlParams[@"present"] boolValue] && [self.platform respondsToSelector:@selector(present:urlParams:exts:completion:)]){ if([urlParams[@"present"]respondsToSelector:@selector(boolValue)] && [urlParams[@"present"] boolValue] && [self.platform respondsToSelector:@selector(present:urlParams:exts:completion:)]){
[self.platform present:url [self.platform present:url
...@@ -212,4 +235,13 @@ ...@@ -212,4 +235,13 @@
} }
} }
- (void)onShownContainerChanged:(NSString *)uniqueId
params:(NSDictionary *)params{
NSString *oldName = params[@"oldName"];
NSString *newName = params[@"newName"];
if (oldName!=nil && [newName isEqualToString:@"default"]) {
[self.flutterProvider detach];
}
}
@end @end
...@@ -53,18 +53,10 @@ ...@@ -53,18 +53,10 @@
}else{ }else{
[_engine runWithEntrypoint:nil]; [_engine runWithEntrypoint:nil];
} }
_dummy = [[FLBFlutterViewContainer alloc] initWithEngine:_engine // _dummy = [[FLBFlutterViewContainer alloc] initWithEngine:_engine
nibName:nil // nibName:nil
bundle:nil]; // bundle:nil];
_dummy.name = kIgnoreMessageWithName; // _dummy.name = kIgnoreMessageWithName;
Class clazz = NSClassFromString(@"GeneratedPluginRegistrant");
if (clazz) {
if ([clazz respondsToSelector:NSSelectorFromString(@"registerWithRegistry:")]) {
[clazz performSelector:NSSelectorFromString(@"registerWithRegistry:")
withObject:_engine];
}
}
} }
return self; return self;
...@@ -105,32 +97,32 @@ ...@@ -105,32 +97,32 @@
arguments:@{@"type":@"foreground"}]; arguments:@{@"type":@"foreground"}];
} }
- (FlutterEngine *)engine - (BOOL)atacheToViewController:(FlutterViewController *)vc
{
return _engine;
}
- (void)atacheToViewController:(FlutterViewController *)vc
{ {
if(_engine.viewController != vc){ if(_engine.viewController != vc){
[(FLBFlutterViewContainer *)_engine.viewController surfaceUpdated:NO];
_engine.viewController = vc; _engine.viewController = vc;
return YES;
} }
return NO;
} }
- (void)detach - (void)detach
{ {
if(_engine.viewController != _dummy){ if(_engine.viewController != _dummy){
[(FLBFlutterViewContainer *)_engine.viewController surfaceUpdated:NO];
_engine.viewController = _dummy; _engine.viewController = _dummy;
} }
} }
- (void)prepareEngineIfNeeded - (void)prepareEngineIfNeeded
{ {
[(FLBFlutterViewContainer *)_engine.viewController surfaceUpdated:NO]; // [(FLBFlutterViewContainer *)_engine.viewController surfaceUpdated:NO];
NSLog(@"[XDEBUG]---surface changed--reset-"); // NSLog(@"[XDEBUG]---surface changed--reset-");
// [self detach]; // [self detach];
} }
- (void)dealloc{
[self.engine setViewController:nil];
}
@end @end
...@@ -31,6 +31,5 @@ NS_ASSUME_NONNULL_BEGIN ...@@ -31,6 +31,5 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic,copy,readwrite) NSString *name; @property (nonatomic,copy,readwrite) NSString *name;
- (instancetype)init NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_DESIGNATED_INITIALIZER;
- (void)surfaceUpdated:(BOOL)appeared; - (void)surfaceUpdated:(BOOL)appeared;
- (void)setEnableForRunnersBatch:(BOOL)enable;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END
...@@ -34,6 +34,36 @@ ...@@ -34,6 +34,36 @@
#define FLUTTER_VIEW FLUTTER_APP.flutterViewController.view #define FLUTTER_VIEW FLUTTER_APP.flutterViewController.view
#define FLUTTER_VC FLUTTER_APP.flutterViewController #define FLUTTER_VC FLUTTER_APP.flutterViewController
@interface FlutterViewController (bridgeToviewDidDisappear)
- (void)flushOngoingTouches;
- (void)bridge_viewDidDisappear:(BOOL)animated;
- (void)bridge_viewWillAppear:(BOOL)animated;
@end
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wincomplete-implementation"
@implementation FlutterViewController (bridgeToviewDidDisappear)
- (void)bridge_viewDidDisappear:(BOOL)animated{
// TRACE_EVENT0("flutter", "viewDidDisappear");
[self flushOngoingTouches];
[super viewDidDisappear:animated];
}
- (void)bridge_viewWillAppear:(BOOL)animated{
// TRACE_EVENT0("flutter", "viewWillAppear");
// if (_engineNeedsLaunch) {
// [_engine.get() launchEngine:nil libraryURI:nil];
// [_engine.get() setViewController:self];
// _engineNeedsLaunch = NO;
// }
[FLUTTER_APP inactive];
[super viewWillAppear:animated];
}
@end
#pragma pop
@interface FLBFlutterViewContainer () @interface FLBFlutterViewContainer ()
@property (nonatomic,strong,readwrite) NSDictionary *params; @property (nonatomic,strong,readwrite) NSDictionary *params;
@property (nonatomic,assign) long long identifier; @property (nonatomic,assign) long long identifier;
...@@ -51,11 +81,21 @@ ...@@ -51,11 +81,21 @@
if(self = [super initWithEngine:FLUTTER_APP.flutterProvider.engine if(self = [super initWithEngine:FLUTTER_APP.flutterProvider.engine
nibName:_flbNibName nibName:_flbNibName
bundle:_flbNibBundle]){ bundle:_flbNibBundle]){
//NOTES:在present页面时,默认是全屏,如此可以触发底层VC的页面事件。否则不会触发而导致异常
self.modalPresentationStyle = UIModalPresentationFullScreen;
[self _setup]; [self _setup];
} }
return self; return self;
} }
- (instancetype)initWithProject:(FlutterDartProject*)projectOrNil
nibName:(NSString*)nibNameOrNil
bundle:(NSBundle*)nibBundleOrNil {
NSAssert(NO, @"unsupported init method!");
return nil;
}
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-designated-initializers" #pragma clang diagnostic ignored "-Wobjc-designated-initializers"
- (instancetype)initWithCoder:(NSCoder *)aDecoder{ - (instancetype)initWithCoder:(NSCoder *)aDecoder{
...@@ -78,10 +118,6 @@ ...@@ -78,10 +118,6 @@
if(!_name && name){ if(!_name && name){
_name = name; _name = name;
_params = params; _params = params;
[BoostMessageChannel didInitPageContainer:^(NSNumber *r) {}
pageName:name
params:params
uniqueId:[self uniqueIDString]];
} }
} }
...@@ -120,9 +156,38 @@ static NSUInteger kInstanceCounter = 0; ...@@ -120,9 +156,38 @@ static NSUInteger kInstanceCounter = 0;
[self.class instanceCounterIncrease]; [self.class instanceCounterIncrease];
} }
- (void)willMoveToParentViewController:(UIViewController *)parent {
if (parent && _name) {
//当VC将要被移动到Parent中的时候,才出发flutter层面的page init
[BoostMessageChannel didInitPageContainer:^(NSNumber *r) {}
pageName:_name
params:_params
uniqueId:[self uniqueIDString]];
}
[super willMoveToParentViewController:parent];
}
- (void)didMoveToParentViewController:(UIViewController *)parent {
if (!parent) {
//当VC被移出parent时,就通知flutter层销毁page
[self notifyWillDealloc];
}
[super didMoveToParentViewController:parent];
}
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {
[super dismissViewControllerAnimated:flag completion:^(){
if (completion) {
completion();
}
//当VC被dismiss时,就通知flutter层销毁page
[self notifyWillDealloc];
}];
}
- (void)dealloc - (void)dealloc
{ {
[self notifyWillDealloc];
[NSNotificationCenter.defaultCenter removeObserver:self]; [NSNotificationCenter.defaultCenter removeObserver:self];
} }
...@@ -158,11 +223,6 @@ static NSUInteger kInstanceCounter = 0; ...@@ -158,11 +223,6 @@ static NSUInteger kInstanceCounter = 0;
[FLUTTER_APP.flutterProvider detach]; [FLUTTER_APP.flutterProvider detach];
} }
- (void)setEnableForRunnersBatch:(BOOL)enable{
//dummy function
NSLog(@"[DEBUG]- I did nothing, I am innocent");
}
#pragma mark - Life circle methods #pragma mark - Life circle methods
- (void)viewDidLayoutSubviews - (void)viewDidLayoutSubviews
...@@ -176,25 +236,21 @@ static NSUInteger kInstanceCounter = 0; ...@@ -176,25 +236,21 @@ static NSUInteger kInstanceCounter = 0;
//For new page we should attach flutter view in view will appear //For new page we should attach flutter view in view will appear
//for better performance. //for better performance.
[self attatchFlutterEngine];
[BoostMessageChannel willShowPageContainer:^(NSNumber *result) {} [BoostMessageChannel willShowPageContainer:^(NSNumber *result) {}
pageName:_name pageName:_name
params:_params params:_params
uniqueId:self.uniqueIDString]; uniqueId:self.uniqueIDString];
//Save some first time page info. //Save some first time page info.
if(![FlutterBoostPlugin sharedInstance].fPagename){
[FlutterBoostPlugin sharedInstance].fPagename = _name; [FlutterBoostPlugin sharedInstance].fPagename = _name;
[FlutterBoostPlugin sharedInstance].fPageId = self.uniqueIDString; [FlutterBoostPlugin sharedInstance].fPageId = self.uniqueIDString;
[FlutterBoostPlugin sharedInstance].fParams = _params; [FlutterBoostPlugin sharedInstance].fParams = _params;
}
[super viewWillAppear:animated];
//instead of calling [super viewWillAppear:animated];, call super's super
// struct objc_super target = { [super bridge_viewWillAppear:animated];
// .super_class = class_getSuperclass([FlutterViewController class]), [self.view setNeedsLayout];//TODO:通过param来设定
// .receiver = self,
// };
// NSMethodSignature * (*callSuper)(struct objc_super *, SEL, BOOL animated) = (__typeof__(callSuper))objc_msgSendSuper;
// callSuper(&target, @selector(viewWillAppear:), animated);
} }
- (void)viewDidAppear:(BOOL)animated - (void)viewDidAppear:(BOOL)animated
...@@ -208,8 +264,7 @@ static NSUInteger kInstanceCounter = 0; ...@@ -208,8 +264,7 @@ static NSUInteger kInstanceCounter = 0;
pageName:_name pageName:_name
params:_params params:_params
uniqueId:self.uniqueIDString]; uniqueId:self.uniqueIDString];
//NOTES:务必在show之后再update,否则有闪烁; 或导致侧滑返回时上一个页面会和top页面内容一样
//NOTES:务必在show之后再update,否则有闪烁
[self surfaceUpdated:YES]; [self surfaceUpdated:YES];
[super viewDidAppear:animated]; [super viewDidAppear:animated];
...@@ -232,14 +287,7 @@ static NSUInteger kInstanceCounter = 0; ...@@ -232,14 +287,7 @@ static NSUInteger kInstanceCounter = 0;
pageName:_name pageName:_name
params:_params params:_params
uniqueId:self.uniqueIDString]; uniqueId:self.uniqueIDString];
[super viewDidDisappear:animated]; [super bridge_viewDidDisappear:animated];
// instead of calling [super viewDidDisappear:animated];, call super's super
// struct objc_super target = {
// .super_class = class_getSuperclass([FlutterViewController class]),
// .receiver = self,
// };
// NSMethodSignature * (*callSuper)(struct objc_super *, SEL, BOOL animated) = (__typeof__(callSuper))objc_msgSendSuper;
// callSuper(&target, @selector(viewDidDisappear:), animated);
} }
- (void)installSplashScreenViewIfNecessary { - (void)installSplashScreenViewIfNecessary {
......
...@@ -36,7 +36,7 @@ class BoostChannel { ...@@ -36,7 +36,7 @@ class BoostChannel {
final Set<MethodHandler> _methodHandlers = Set(); final Set<MethodHandler> _methodHandlers = Set();
BoostChannel() { BoostChannel() {
_methodChannel.setMethodCallHandler((MethodCall call){ _methodChannel.setMethodCallHandler((MethodCall call) {
if (call.method == "__event__") { if (call.method == "__event__") {
String name = call.arguments["name"]; String name = call.arguments["name"];
Map arg = call.arguments["arguments"]; Map arg = call.arguments["arguments"];
...@@ -46,13 +46,13 @@ class BoostChannel { ...@@ -46,13 +46,13 @@ class BoostChannel {
l(name, arg); l(name, arg);
} }
} }
}else{ } else {
for(MethodHandler handler in _methodHandlers) { for (MethodHandler handler in _methodHandlers) {
handler(call); handler(call);
} }
} }
return Future.value(); return Future<dynamic>.value();
}); });
} }
...@@ -62,31 +62,33 @@ class BoostChannel { ...@@ -62,31 +62,33 @@ class BoostChannel {
} }
if (arguments == null) { if (arguments == null) {
arguments = Map(); arguments = Map<dynamic, dynamic>();
} }
Map msg = Map(); Map msg = Map<dynamic, dynamic>();
msg["name"] = name; msg["name"] = name;
msg["arguments"] = arguments; msg["arguments"] = arguments;
_methodChannel.invokeMethod("__event__", msg); _methodChannel.invokeMethod<dynamic>("__event__", msg);
} }
Future<T> invokeMethod<T>(String method, [ dynamic arguments ]) async { Future<T> invokeMethod<T>(String method, [dynamic arguments]) async {
assert(method != "__event__"); assert(method != "__event__");
return _methodChannel.invokeMethod<T>(method,arguments); return _methodChannel.invokeMethod<T>(method, arguments);
} }
Future<List<T>> invokeListMethod<T>(String method, [ dynamic arguments ]) async { Future<List<T>> invokeListMethod<T>(String method,
[dynamic arguments]) async {
assert(method != "__event__"); assert(method != "__event__");
return _methodChannel.invokeListMethod<T>(method,arguments); return _methodChannel.invokeListMethod<T>(method, arguments);
} }
Future<Map<K, V>> invokeMapMethod<K, V>(String method, [ dynamic arguments ]) async { Future<Map<K, V>> invokeMapMethod<K, V>(String method,
[dynamic arguments]) async {
assert(method != "__event__"); assert(method != "__event__");
return _methodChannel.invokeMapMethod<K, V>(method,arguments); return _methodChannel.invokeMapMethod<K, V>(method, arguments);
} }
VoidCallback addEventListener(String name, EventListener listener) { VoidCallback addEventListener(String name, EventListener listener) {
...@@ -110,7 +112,7 @@ class BoostChannel { ...@@ -110,7 +112,7 @@ class BoostChannel {
_methodHandlers.add(handler); _methodHandlers.add(handler);
return (){ return () {
_methodHandlers.remove(handler); _methodHandlers.remove(handler);
}; };
} }
......
...@@ -87,7 +87,8 @@ class BoostContainer extends Navigator { ...@@ -87,7 +87,8 @@ class BoostContainer extends Navigator {
} }
}, },
observers: <NavigatorObserver>[ observers: <NavigatorObserver>[
ContainerNavigatorObserver.bindContainerManager() ContainerNavigatorObserver.bindContainerManager(),
HeroController(),
], ],
onUnknownRoute: navigator.onUnknownRoute); onUnknownRoute: navigator.onUnknownRoute);
...@@ -99,13 +100,13 @@ class BoostContainer extends Navigator { ...@@ -99,13 +100,13 @@ class BoostContainer extends Navigator {
static BoostContainerState tryOf(BuildContext context) { static BoostContainerState tryOf(BuildContext context) {
final BoostContainerState container = final BoostContainerState container =
context.ancestorStateOfType(const TypeMatcher<BoostContainerState>()); context.findAncestorStateOfType<BoostContainerState>();
return container; return container;
} }
static BoostContainerState of(BuildContext context) { static BoostContainerState of(BuildContext context) {
final BoostContainerState container = final BoostContainerState container =
context.ancestorStateOfType(const TypeMatcher<BoostContainerState>()); context.findAncestorStateOfType<BoostContainerState>();
assert(container != null, 'not in flutter boost'); assert(container != null, 'not in flutter boost');
return container; return container;
} }
...@@ -155,14 +156,10 @@ class BoostContainerState extends NavigatorState { ...@@ -155,14 +156,10 @@ class BoostContainerState extends NavigatorState {
@override @override
void didUpdateWidget(Navigator oldWidget) { void didUpdateWidget(Navigator oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
findContainerNavigatorObserver(oldWidget)?.removeBoostNavigatorObserver(
FlutterBoost.containerManager.navigatorObserver);
} }
@override @override
void dispose() { void dispose() {
findContainerNavigatorObserver(widget)?.removeBoostNavigatorObserver(
FlutterBoost.containerManager.navigatorObserver);
routerHistory.clear(); routerHistory.clear();
super.dispose(); super.dispose();
} }
...@@ -192,6 +189,7 @@ class BoostContainerState extends NavigatorState { ...@@ -192,6 +189,7 @@ class BoostContainerState extends NavigatorState {
break; break;
} }
} }
return false;
} }
@override @override
...@@ -201,7 +199,7 @@ class BoostContainerState extends NavigatorState { ...@@ -201,7 +199,7 @@ class BoostContainerState extends NavigatorState {
} }
if (canPop()) { if (canPop()) {
return super.pop(result); super.pop<T>(result);
} else { } else {
if (T is Map<String, dynamic>) { if (T is Map<String, dynamic>) {
FlutterBoost.singleton FlutterBoost.singleton
...@@ -210,7 +208,7 @@ class BoostContainerState extends NavigatorState { ...@@ -210,7 +208,7 @@ class BoostContainerState extends NavigatorState {
FlutterBoost.singleton.close(uniqueId); FlutterBoost.singleton.close(uniqueId);
} }
} }
return false; return true;
} }
@override @override
...@@ -261,63 +259,48 @@ class ContainerElement extends StatefulElement { ...@@ -261,63 +259,48 @@ class ContainerElement extends StatefulElement {
} }
class ContainerNavigatorObserver extends NavigatorObserver { class ContainerNavigatorObserver extends NavigatorObserver {
BoostNavigatorObserver observer; static final Set<NavigatorObserver> boostObservers = Set<NavigatorObserver>();
final Set<BoostNavigatorObserver> _boostObservers =
Set<BoostNavigatorObserver>();
ContainerNavigatorObserver(); ContainerNavigatorObserver();
factory ContainerNavigatorObserver.bindContainerManager() => factory ContainerNavigatorObserver.bindContainerManager() =>
ContainerNavigatorObserver() ContainerNavigatorObserver();
..addBoostNavigatorObserver(
FlutterBoost.containerManager.navigatorObserver);
VoidCallback addBoostNavigatorObserver(BoostNavigatorObserver observer) { VoidCallback addBoostNavigatorObserver(NavigatorObserver observer) {
_boostObservers.add(observer); boostObservers.add(observer);
return () => _boostObservers.remove(observer); return () => boostObservers.remove(observer);
} }
void removeBoostNavigatorObserver(BoostNavigatorObserver observer) { void removeBoostNavigatorObserver(NavigatorObserver observer) {
_boostObservers.remove(observer); boostObservers.remove(observer);
} }
@override @override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) { void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
for (BoostNavigatorObserver observer in _boostObservers) { for (NavigatorObserver observer in boostObservers) {
observer.didPush(route, previousRoute); observer.didPush(route, previousRoute);
} }
} }
@override @override
void didPop(Route<dynamic> route, Route<dynamic> previousRoute) { void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
for (BoostNavigatorObserver observer in _boostObservers) { for (NavigatorObserver observer in boostObservers) {
observer.didPop(route, previousRoute); observer.didPop(route, previousRoute);
} }
} }
@override @override
void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) { void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) {
for (BoostNavigatorObserver observer in _boostObservers) { for (NavigatorObserver observer in boostObservers) {
observer.didRemove(route, previousRoute); observer.didRemove(route, previousRoute);
} }
} }
@override @override
void didReplace({Route<dynamic> newRoute, Route<dynamic> oldRoute}) { void didReplace({Route<dynamic> newRoute, Route<dynamic> oldRoute}) {
for (BoostNavigatorObserver observer in _boostObservers) { for (NavigatorObserver observer in boostObservers) {
observer.didReplace(newRoute: newRoute, oldRoute: oldRoute); observer.didReplace(newRoute: newRoute, oldRoute: oldRoute);
} }
} }
} }
class BoostNavigatorObserver {
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {}
void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {}
void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) {}
void didReplace({Route<dynamic> newRoute, Route<dynamic> oldRoute}) {}
}
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
...@@ -36,23 +37,14 @@ class BoostPageRoute<T> extends MaterialPageRoute<T> { ...@@ -36,23 +37,14 @@ class BoostPageRoute<T> extends MaterialPageRoute<T> {
final Set<VoidCallback> backPressedListeners = Set<VoidCallback>(); final Set<VoidCallback> backPressedListeners = Set<VoidCallback>();
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return super.buildTransitions(context, animation, secondaryAnimation, child);
}
BoostPageRoute( BoostPageRoute(
{Key stubKey, {this.pageName,
this.pageName,
this.params, this.params,
this.uniqueId, this.uniqueId,
this.animated, this.animated,
this.builder, this.builder,
this.settings}) this.settings})
: super( : super(builder: builder, settings: settings);
builder: (BuildContext context) => Stub(stubKey, builder(context)),
settings: settings);
static BoostPageRoute<T> of<T>(BuildContext context) { static BoostPageRoute<T> of<T>(BuildContext context) {
final Route<T> route = ModalRoute.of(context); final Route<T> route = ModalRoute.of(context);
...@@ -72,18 +64,3 @@ class BoostPageRoute<T> extends MaterialPageRoute<T> { ...@@ -72,18 +64,3 @@ class BoostPageRoute<T> extends MaterialPageRoute<T> {
} }
} }
} }
@immutable
class Stub extends StatefulWidget {
final Widget child;
const Stub(Key key, this.child) : super(key: key);
@override
_StubState createState() => _StubState();
}
class _StubState extends State<Stub> {
@override
Widget build(BuildContext context) => widget.child;
}
...@@ -102,7 +102,7 @@ class ContainerCoordinator { ...@@ -102,7 +102,7 @@ class ContainerCoordinator {
Map map = event; Map map = event;
final String type = map['type']; final String type = map['type'];
Logger.log("onEvent $type"); Logger.log('onEvent $type');
switch (type) { switch (type) {
//Handler back key pressed event. //Handler back key pressed event.
...@@ -242,7 +242,7 @@ class ContainerCoordinator { ...@@ -242,7 +242,7 @@ class ContainerCoordinator {
ContainerLifeCycle.Appear); ContainerLifeCycle.Appear);
Logger.log( Logger.log(
'native containner did show,\nmanager dump:\n${FlutterBoost.containerManager?.dump()}'); 'native containner did show-$name,\nmanager dump:\n${FlutterBoost.containerManager?.dump()}');
return true; return true;
} }
...@@ -266,18 +266,16 @@ class ContainerCoordinator { ...@@ -266,18 +266,16 @@ class ContainerCoordinator {
} }
bool _nativeContainerWillDealloc(String name, Map params, String pageId) { bool _nativeContainerWillDealloc(String name, Map params, String pageId) {
try{ try {
performContainerLifeCycle(_createContainerSettings(name, params, pageId), performContainerLifeCycle(_createContainerSettings(name, params, pageId),
ContainerLifeCycle.Destroy); ContainerLifeCycle.Destroy);
} catch (e){ } catch (e) {
Logger.log( Logger.log('nativeContainerWillDealloc error: $e');
'nativeContainerWillDealloc error: ${e}' );
} }
FlutterBoost.containerManager?.remove(pageId); FlutterBoost.containerManager?.remove(pageId);
Logger.log( Logger.log(
'native containner dealloc, \n manager dump:\n${FlutterBoost.containerManager?.dump()}'); 'native containner dealloc for $name, \n manager dump:\n${FlutterBoost.containerManager?.dump()}');
return true; return true;
} }
......
...@@ -47,13 +47,13 @@ class BoostContainerManager extends StatefulWidget { ...@@ -47,13 +47,13 @@ class BoostContainerManager extends StatefulWidget {
static ContainerManagerState tryOf(BuildContext context) { static ContainerManagerState tryOf(BuildContext context) {
final ContainerManagerState manager = final ContainerManagerState manager =
context.ancestorStateOfType(const TypeMatcher<ContainerManagerState>()); context.findAncestorStateOfType<ContainerManagerState>();
return manager; return manager;
} }
static ContainerManagerState of(BuildContext context) { static ContainerManagerState of(BuildContext context) {
final ContainerManagerState manager = final ContainerManagerState manager =
context.ancestorStateOfType(const TypeMatcher<ContainerManagerState>()); context.findAncestorStateOfType<ContainerManagerState>();
assert(manager != null, 'not in flutter boost'); assert(manager != null, 'not in flutter boost');
return manager; return manager;
} }
...@@ -62,8 +62,6 @@ class BoostContainerManager extends StatefulWidget { ...@@ -62,8 +62,6 @@ class BoostContainerManager extends StatefulWidget {
class ContainerManagerState extends State<BoostContainerManager> { class ContainerManagerState extends State<BoostContainerManager> {
final GlobalKey<OverlayState> _overlayKey = GlobalKey<OverlayState>(); final GlobalKey<OverlayState> _overlayKey = GlobalKey<OverlayState>();
final List<BoostContainer> _offstage = <BoostContainer>[]; final List<BoostContainer> _offstage = <BoostContainer>[];
final ManagerNavigatorObserver _navigatorObserver =
ManagerNavigatorObserver();
List<_ContainerOverlayEntry> _leastEntries; List<_ContainerOverlayEntry> _leastEntries;
...@@ -78,11 +76,11 @@ class ContainerManagerState extends State<BoostContainerManager> { ...@@ -78,11 +76,11 @@ class ContainerManagerState extends State<BoostContainerManager> {
bool get foreground => _foreground; bool get foreground => _foreground;
ManagerNavigatorObserver get navigatorObserver => _navigatorObserver;
//Number of containers. //Number of containers.
int get containerCounts => _offstage.length; int get containerCounts => _offstage.length;
List<BoostContainer> get offstage => _offstage;
//Setting for current visible container. //Setting for current visible container.
BoostContainerSettings get onstageSettings => _onstage.settings; BoostContainerSettings get onstageSettings => _onstage.settings;
...@@ -139,7 +137,8 @@ class ContainerManagerState extends State<BoostContainerManager> { ...@@ -139,7 +137,8 @@ class ContainerManagerState extends State<BoostContainerManager> {
properties['newName'] = now; properties['newName'] = now;
properties['oldName'] = old; properties['oldName'] = old;
FlutterBoost.singleton.channel.invokeMethod('onShownContainerChanged',properties); FlutterBoost.singleton.channel
.invokeMethod<dynamic>('onShownContainerChanged', properties);
} }
void _refreshOverlayEntries() { void _refreshOverlayEntries() {
...@@ -184,9 +183,11 @@ class ContainerManagerState extends State<BoostContainerManager> { ...@@ -184,9 +183,11 @@ class ContainerManagerState extends State<BoostContainerManager> {
if (SchedulerBinding.instance.schedulerPhase == if (SchedulerBinding.instance.schedulerPhase ==
SchedulerPhase.persistentCallbacks) { SchedulerPhase.persistentCallbacks) {
SchedulerBinding.instance.addPostFrameCallback((Duration duration) { SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
Logger.log('_refreshOverlayEntries in addPostFrameCallback');
_refreshOverlayEntries(); _refreshOverlayEntries();
}); });
} else { } else {
Logger.log('_refreshOverlayEntries in setState');
_refreshOverlayEntries(); _refreshOverlayEntries();
} }
...@@ -346,52 +347,3 @@ class _ContainerOverlayEntry extends OverlayEntry { ...@@ -346,52 +347,3 @@ class _ContainerOverlayEntry extends OverlayEntry {
super.remove(); super.remove();
} }
} }
class ManagerNavigatorObserver extends BoostNavigatorObserver {
BoostNavigatorObserver observer;
final Set<BoostNavigatorObserver> _boostObservers =
Set<BoostNavigatorObserver>();
VoidCallback addBoostNavigatorObserver(BoostNavigatorObserver observer) {
_boostObservers.add(observer);
return () => _boostObservers.remove(observer);
}
void removeBoostNavigatorObserver(BoostNavigatorObserver observer) {
_boostObservers.remove(observer);
}
@override
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
Logger.log('ManagerNavigatorObserver didPush');
for (BoostNavigatorObserver observer in _boostObservers) {
observer.didPush(route, previousRoute);
}
}
@override
void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
Logger.log('ManagerNavigatorObserver didPop');
for (BoostNavigatorObserver observer in _boostObservers) {
observer.didPop(route, previousRoute);
}
}
@override
void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) {
Logger.log('ManagerNavigatorObserver didRemove');
for (BoostNavigatorObserver observer in _boostObservers) {
observer.didRemove(route, previousRoute);
}
}
@override
void didReplace({Route<dynamic> newRoute, Route<dynamic> oldRoute}) {
Logger.log('ManagerNavigatorObserver didReplace');
for (BoostNavigatorObserver observer in _boostObservers) {
observer.didReplace(newRoute: newRoute, oldRoute: oldRoute);
}
}
}
...@@ -44,7 +44,6 @@ typedef void PostPushRoute( ...@@ -44,7 +44,6 @@ typedef void PostPushRoute(
String url, String uniqueId, Map params, Route route, Future result); String url, String uniqueId, Map params, Route route, Future result);
class FlutterBoost { class FlutterBoost {
static final FlutterBoost _instance = FlutterBoost(); static final FlutterBoost _instance = FlutterBoost();
final GlobalKey<ContainerManagerState> containerManagerKey = final GlobalKey<ContainerManagerState> containerManagerKey =
GlobalKey<ContainerManagerState>(); GlobalKey<ContainerManagerState>();
...@@ -56,23 +55,14 @@ class FlutterBoost { ...@@ -56,23 +55,14 @@ class FlutterBoost {
static ContainerManagerState get containerManager => static ContainerManagerState get containerManager =>
_instance.containerManagerKey.currentState; _instance.containerManagerKey.currentState;
static TransitionBuilder init( static void onPageStart() {
{TransitionBuilder builder, WidgetsBinding.instance.addPostFrameCallback((_) {
PrePushRoute prePush, singleton.channel.invokeMethod<Map>('pageOnStart').then((Map pageInfo) {
PostPushRoute postPush}) {
if(Platform.isAndroid){
WidgetsBinding.instance.addPostFrameCallback((_){
singleton.channel.invokeMethod<Map>('pageOnStart').then((Map pageInfo){
if (pageInfo == null || pageInfo.isEmpty) return; if (pageInfo == null || pageInfo.isEmpty) return;
if (pageInfo.containsKey("name") && if (pageInfo.containsKey("name") &&
pageInfo.containsKey("params") && pageInfo.containsKey("params") &&
pageInfo.containsKey("uniqueId")) { pageInfo.containsKey("uniqueId")) {
ContainerCoordinator.singleton.nativeContainerDidShow( ContainerCoordinator.singleton.nativeContainerDidShow(
pageInfo["name"], pageInfo["params"], pageInfo["uniqueId"]); pageInfo["name"], pageInfo["params"], pageInfo["uniqueId"]);
} }
...@@ -80,6 +70,20 @@ class FlutterBoost { ...@@ -80,6 +70,20 @@ class FlutterBoost {
}); });
} }
static TransitionBuilder init(
{TransitionBuilder builder,
PrePushRoute prePush,
PostPushRoute postPush}) {
if (Platform.isAndroid) {
onPageStart();
} else if (Platform.isIOS) {
assert(() {
() async {
onPageStart();
}();
return true;
}());
}
return (BuildContext context, Widget child) { return (BuildContext context, Widget child) {
assert(child is Navigator, 'child must be Navigator, what is wrong?'); assert(child is Navigator, 'child must be Navigator, what is wrong?');
...@@ -102,7 +106,7 @@ class FlutterBoost { ...@@ -102,7 +106,7 @@ class FlutterBoost {
BoostChannel get channel => _boostChannel; BoostChannel get channel => _boostChannel;
FlutterBoost(){ FlutterBoost() {
ContainerCoordinator(_boostChannel); ContainerCoordinator(_boostChannel);
} }
...@@ -116,30 +120,29 @@ class FlutterBoost { ...@@ -116,30 +120,29 @@ class FlutterBoost {
ContainerCoordinator.singleton.registerPageBuilders(builders); ContainerCoordinator.singleton.registerPageBuilders(builders);
} }
Future<Map<dynamic,dynamic>> open(String url,{Map<dynamic,dynamic> urlParams,Map<dynamic,dynamic> exts}){ Future<Map<dynamic, dynamic>> open(String url,
{Map<dynamic, dynamic> urlParams, Map<dynamic, dynamic> exts}) {
Map<dynamic, dynamic> properties = new Map<dynamic, dynamic>(); Map<dynamic, dynamic> properties = new Map<dynamic, dynamic>();
properties["url"] = url; properties["url"] = url;
properties["urlParams"] = urlParams; properties["urlParams"] = urlParams;
properties["exts"] = exts; properties["exts"] = exts;
return channel.invokeMethod<Map<dynamic,dynamic>>( return channel.invokeMethod<Map<dynamic, dynamic>>('openPage', properties);
'openPage', properties);
} }
Future<bool> close(String id,{Map<dynamic,dynamic> result,Map<dynamic,dynamic> exts}){ Future<bool> close(String id,
{Map<dynamic, dynamic> result, Map<dynamic, dynamic> exts}) {
assert(id != null); assert(id != null);
BoostContainerSettings settings = containerManager?.onstageSettings; BoostContainerSettings settings = containerManager?.onstageSettings;
Map<dynamic, dynamic> properties = new Map<dynamic, dynamic>(); Map<dynamic, dynamic> properties = new Map<dynamic, dynamic>();
if(exts == null){ if (exts == null) {
exts = Map<dynamic,dynamic>(); exts = Map<dynamic, dynamic>();
} }
exts["params"] = settings.params; exts["params"] = settings.params;
if(!exts.containsKey("animated")){ if (!exts.containsKey("animated")) {
exts["animated"] = true; exts["animated"] = true;
} }
...@@ -155,28 +158,30 @@ class FlutterBoost { ...@@ -155,28 +158,30 @@ class FlutterBoost {
return channel.invokeMethod<bool>('closePage', properties); return channel.invokeMethod<bool>('closePage', properties);
} }
Future<bool> closeCurrent({Map<String,dynamic> result,Map<String,dynamic> exts}) { Future<bool> closeCurrent(
{Map<String, dynamic> result, Map<String, dynamic> exts}) {
BoostContainerSettings settings = containerManager?.onstageSettings; BoostContainerSettings settings = containerManager?.onstageSettings;
if(exts == null){ if (exts == null) {
exts = Map<String,dynamic>(); exts = Map<String, dynamic>();
} }
exts["params"] = settings.params; exts["params"] = settings.params;
if(!exts.containsKey("animated")){ if (!exts.containsKey("animated")) {
exts["animated"] = true; exts["animated"] = true;
} }
return close(settings.uniqueId,result: result,exts: exts); return close(settings.uniqueId, result: result, exts: exts);
} }
Future<bool> closeByContext(BuildContext context,{Map<String,dynamic> result,Map<String,dynamic> exts}) { Future<bool> closeByContext(BuildContext context,
{Map<String, dynamic> result, Map<String, dynamic> exts}) {
BoostContainerSettings settings = containerManager?.onstageSettings; BoostContainerSettings settings = containerManager?.onstageSettings;
if(exts == null){ if (exts == null) {
exts = Map<String,dynamic>(); exts = Map<String, dynamic>();
} }
exts["params"] = settings.params; exts["params"] = settings.params;
if(!exts.containsKey("animated")){ if (!exts.containsKey("animated")) {
exts["animated"] = true; exts["animated"] = true;
} }
return close(settings.uniqueId,result: result,exts: exts); return close(settings.uniqueId, result: result, exts: exts);
} }
///register for Container changed callbacks ///register for Container changed callbacks
...@@ -189,8 +194,6 @@ class FlutterBoost { ...@@ -189,8 +194,6 @@ class FlutterBoost {
_observersHolder.addObserver<BoostContainerLifeCycleObserver>(observer); _observersHolder.addObserver<BoostContainerLifeCycleObserver>(observer);
///register callbacks for Navigators push & pop ///register callbacks for Navigators push & pop
VoidCallback addBoostNavigatorObserver(BoostNavigatorObserver observer) => void addBoostNavigatorObserver(NavigatorObserver observer) =>
_observersHolder.addObserver<BoostNavigatorObserver>(observer); ContainerNavigatorObserver.boostObservers.add(observer);
} }
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
*/ */
class Logger { class Logger {
static void log(String msg) { static void log(String msg) {
assert((){ assert(() {
print('FlutterBoost#$msg'); print('FlutterBoost#$msg');
return true; return true;
}()); }());
......
name: flutter_boost name: flutter_boost
description: A next-generation Flutter-Native hybrid solution. FlutterBoost is a Flutter plugin which enables hybrid integration of Flutter for your existing native apps with minimum efforts. description: A next-generation Flutter-Native hybrid solution. FlutterBoost is a Flutter plugin which enables hybrid integration of Flutter for your existing native apps with minimum efforts.
version: 0.1.63 version: 1.12.13+1
author: Alibaba Xianyu author: Alibaba Xianyu
homepage: https://github.com/alibaba/flutter_boost homepage: https://github.com/alibaba/flutter_boost
environment: environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0" sdk: ">=2.2.2 <3.0.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
dev_dependencies:
pedantic: ^1.8.0
flutter_test:
sdk: flutter
mockito: 4.1.1
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec # following page: https://www.dartlang.org/tools/pub/pubspec
......
import 'package:flutter_boost/channel/boost_channel.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
const MethodChannel channel = MethodChannel('flutter_boost');
final List<MethodCall> log = <MethodCall>[];
dynamic response;
channel.setMockMethodCallHandler((MethodCall methodCall) async {
print(methodCall);
log.add(methodCall);
return response;
});
tearDown(() {
log.clear();
});
group('boost_channel', () {
response = null;
test('sendEvent successfully', () async {
Map msg1 = Map<dynamic,dynamic>();
BoostChannel().sendEvent("name", msg1);
Map msg = Map<dynamic,dynamic>();
msg["name"] = "name";
msg["arguments"] = msg1;
expect(
log,
<Matcher>[isMethodCall('__event__', arguments: msg)],
);
});
test('invokeMethod successfully', () async {
Map msg = <dynamic,dynamic>{};
msg["test"] = "test";
BoostChannel().invokeMethod<dynamic>("__event__1", msg);
// expect(e, isException);
expect(
log,
<Matcher>[isMethodCall('__event__1', arguments: msg)],
);
});
test('invokeListMethod successfully', () async {
Map msg = <dynamic,dynamic>{};
msg["test"] = "test";
var bb = await BoostChannel().invokeListMethod<dynamic>("__event__1", msg);
expect(
log,
<Matcher>[isMethodCall('__event__1', arguments: msg)],
);
});
test('invokeMapMethod successfully', () async {
Map msg = <dynamic,dynamic>{};
msg["test"] = "test";
BoostChannel().invokeMapMethod<dynamic,dynamic>("__event__1", msg);
expect(
log,
<Matcher>[isMethodCall('__event__1', arguments: msg)],
);
});
test('invokeMapMethod successfully', () async {
Map msg = <dynamic,dynamic>{};
msg["test"] = "test";
BoostChannel().invokeMapMethod<dynamic,dynamic>("__event__1", msg);
expect(
log,
<Matcher>[isMethodCall('__event__1', arguments: msg)],
);
});
test('addEventListener successfully', () async {
Function test = BoostChannel().addEventListener(
"addEventListener", (String name, Map arguments) async => "test");
print("xxx" + test.toString());
expect(
test.toString(),
"Closure: () => Null",
);
});
test('addMethodHandler successfully', () async {
Function test = BoostChannel().addMethodHandler((
MethodCall call) async => "test");
expect(
test.toString(),
"Closure: () => Null",
);
});
});
}
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_boost/container/boost_container.dart';
import 'package:flutter_test/flutter_test.dart';
class FirstWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.pushNamed(context, '/second');
},
child: Container(
color: const Color(0xFFFFFF00),
child: const Text('X'),
),
);
}
}
class SecondWidget extends StatefulWidget {
@override
SecondWidgetState createState() => SecondWidgetState();
}
class SecondWidgetState extends State<SecondWidget> {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => Navigator.pop(context),
child: Container(
color: const Color(0xFFFF00FF),
child: const Text('Y'),
),
);
}
}
typedef ExceptionCallback = void Function(dynamic exception);
class ThirdWidget extends StatelessWidget {
const ThirdWidget({this.targetKey, this.onException});
final Key targetKey;
final ExceptionCallback onException;
@override
Widget build(BuildContext context) {
return GestureDetector(
key: targetKey,
onTap: () {
try {
Navigator.of(context);
} catch (e) {
onException(e);
}
},
behavior: HitTestBehavior.opaque,
);
}
}
class OnTapPage extends StatelessWidget {
const OnTapPage({Key key, this.id, this.onTap}) : super(key: key);
final String id;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Page $id')),
body: GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: Container(
child: Center(
child: Text(id, style: Theme.of(context).textTheme.display2),
),
),
),
);
}
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
testWidgets('Can navigator navigate to and from a stateful widget',
(WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/': (BuildContext context) => FirstWidget(), // X
'/second': (BuildContext context) => SecondWidget(), // Y
};
await tester.pumpWidget(MaterialApp(routes: routes));
expect(find.text('X'), findsOneWidget);
expect(find.text('Y', skipOffstage: false), findsNothing);
await tester.tap(find.text('X'));
await tester.pump();
expect(find.text('X'), findsOneWidget);
expect(find.text('Y', skipOffstage: false), isOffstage);
await tester.pump(const Duration(milliseconds: 10));
expect(find.text('X'), findsOneWidget);
expect(find.text('Y'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 10));
expect(find.text('X'), findsOneWidget);
expect(find.text('Y'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 10));
expect(find.text('X'), findsOneWidget);
expect(find.text('Y'), findsOneWidget);
await tester.pump(const Duration(seconds: 1));
expect(find.text('X'), findsNothing);
expect(find.text('X', skipOffstage: false), findsOneWidget);
expect(find.text('Y'), findsOneWidget);
await tester.tap(find.text('Y'));
expect(find.text('X'), findsNothing);
expect(find.text('Y'), findsOneWidget);
await tester.pump();
await tester.pump();
expect(find.text('X'), findsOneWidget);
expect(find.text('Y'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 10));
expect(find.text('X'), findsOneWidget);
expect(find.text('Y'), findsOneWidget);
await tester.pump(const Duration(seconds: 1));
expect(find.text('X'), findsOneWidget);
expect(find.text('Y', skipOffstage: false), findsNothing);
});
//
testWidgets('Navigator.of gracefully when not found in context',
(WidgetTester tester) async {
const Key targetKey = Key('foo');
dynamic exception;
final Widget widget = ThirdWidget(
targetKey: targetKey,
onException: (dynamic e) {
exception = e;
},
);
await tester.pumpWidget(widget);
await tester.pump(Duration(seconds: 1));
await tester.tap(find.byKey(targetKey));
await tester.pump(Duration(seconds: 1));
expect(exception, isInstanceOf<FlutterError>());
expect('$exception',
startsWith('Navigator operation requested with a context'));
});
//
// testWidgets('Navigator.of rootNavigator finds root Navigator',
// (WidgetTester tester) async {
// await tester.pumpWidget(MaterialApp(
// home: Material(
// child: Column(
// children: <Widget>[
// const SizedBox(
// height: 300.0,
// child: Text('Root page'),
// ),
// SizedBox(
// height: 300.0,
// child: Navigator(
// onGenerateRoute: (RouteSettings settings) {
// if (settings.isInitialRoute) {
// return MaterialPageRoute<void>(
// builder: (BuildContext context) {
// return RaisedButton(
// child: const Text('Next'),
// onPressed: () {
// BoostContainer.of(context).push(
// MaterialPageRoute<void>(
// builder: (BuildContext context) {
// return RaisedButton(
// child: const Text('Inner page'),
// onPressed: () {
// BoostContainer.of(context)
// .push(
// MaterialPageRoute<void>(
// builder: (BuildContext context) {
// return const Text('Dialog');
// }),
// );
// },
// );
// }),
// );
// },
// );
// },
// );
// }
// return null;
// },
// ),
// ),
// ],
// ),
// ),
// ));
////
//// await tester.tap(find.text('Next'));
//// await tester.pump();
//// await tester.pump(const Duration(milliseconds: 300));
//
// // Both elements are on screen.
// expect(find.text('Next'), findsOneWidget);
//// expect(tester.getTopLeft(find.text('Inner page')).dy, greaterThan(300.0));
////
//// await tester.tap(find.text('Inner page'));
//// await tester.pump();
//// await tester.pump(const Duration(milliseconds: 300));
////
//// // Dialog is pushed to the whole page and is at the top of the screen, not
//// // inside the inner page.
//// expect(tester.getTopLeft(find.text('Dialog')).dy, 0.0);
// });
}
import 'package:flutter_boost/container/boost_page_route.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
testWidgets('test iOS edge swipe then drop back at starting point works',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(platform: TargetPlatform.iOS),
onGenerateRoute: (RouteSettings settings) {
return BoostPageRoute<void>(
settings: settings,
builder: (BuildContext context) {
final String pageNumber = settings.name == '/' ? '1' : '2';
return Center(child: Text('Page $pageNumber'));
},
);
},
),
);
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(find.text('Page 1'), findsNothing);
expect(find.text('Page 2'), isOnstage);
final TestGesture gesture = await tester.startGesture(const Offset(5, 200));
await gesture.moveBy(const Offset(300, 0));
await tester.pump();
// Bring it exactly back such that there's nothing to animate when releasing.
await gesture.moveBy(const Offset(-300, 0));
await gesture.up();
await tester.pump();
expect(find.text('Page 1'), findsNothing);
expect(find.text('Page 2'), isOnstage);
});
group('Try to get the BoostPageRoute in the ancestor node', () {
testWidgets(
'obtain BoostPageRoute through the BoostPageRoute.of(context) method',
(WidgetTester tester) async {
dynamic boostPageRoute;
dynamic boostPageRouteFindByOfMethod;
await tester.pumpWidget(
MaterialApp(
onGenerateRoute: (RouteSettings settings) {
boostPageRoute = BoostPageRoute<void>(
settings: settings,
builder: (BuildContext context) => Builder(
builder: (context) {
return FloatingActionButton(
onPressed: () {
boostPageRouteFindByOfMethod = BoostPageRoute.of<dynamic>(context);
},
);
},
),
);
return boostPageRoute;
},
),
);
await tester.tap(find.byType(FloatingActionButton));
await tester.pump(Duration(seconds: 1));
// The route obtained from the ancestor node through the `of` method should be the same BoostPageRoute
// as the originally created BoostPageRoute
expect(boostPageRoute, boostPageRouteFindByOfMethod);
});
testWidgets(
'try to find BoostPageRoute through the BoostPageRoute.of(context) method, '
'but it doesn\'t exist, the method should throw an Exception',
(WidgetTester tester) async {
dynamic contextCache;
await tester.pumpWidget(
MaterialApp(
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<dynamic>(
settings: settings,
builder: (context) => Builder(
builder: (context) => FloatingActionButton(
onPressed: () {
contextCache = context;
},
),
),
);
},
),
);
await tester.tap(find.byType(FloatingActionButton));
await tester.pump(Duration(seconds: 1));
expect(() => BoostPageRoute.of<dynamic>(contextCache), throwsException);
});
testWidgets(
'obtain BoostPageRoute through the BoostPageRoute.tryOf(context) method',
(WidgetTester tester) async {
dynamic boostPageRoute;
dynamic boostPageRouteFindByOfMethod;
await tester.pumpWidget(
MaterialApp(
onGenerateRoute: (RouteSettings settings) {
boostPageRoute = BoostPageRoute<void>(
settings: settings,
builder: (BuildContext context) => Builder(
builder: (context) {
return FloatingActionButton(
onPressed: () {
boostPageRouteFindByOfMethod =
BoostPageRoute.tryOf<dynamic>(context);
},
);
},
),
);
return boostPageRoute;
},
),
);
await tester.tap(find.byType(FloatingActionButton));
await tester.pump(Duration(seconds: 1));
// The route obtained from the ancestor node through the `tryOf` method should be the same BoostPageRoute
// as the originally created BoostPageRoute
expect(boostPageRoute, boostPageRouteFindByOfMethod);
});
});
testWidgets(
'try to find BoostPageRoute through the BoostPageRoute.tryOf(context) method, '
'but it doesn\'t exist, the method should return null',
(WidgetTester tester) async {
dynamic boostPageRouteFindByOfMethod;
await tester.pumpWidget(
MaterialApp(
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<dynamic>(
settings: settings,
builder: (BuildContext context) => Builder(
builder: (context) {
return FloatingActionButton(
onPressed: () {
boostPageRouteFindByOfMethod =
BoostPageRoute.tryOf<dynamic>(context);
},
);
},
),
);
},
),
);
await tester.tap(find.byType(FloatingActionButton));
await tester.pump(Duration(seconds: 1));
expect(boostPageRouteFindByOfMethod, null);
});
}
import 'dart:ui';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:flutter_boost/channel/boost_channel.dart';
import 'package:flutter_boost/container/container_coordinator.dart';
import 'package:flutter_boost/flutter_boost.dart';
import 'dart:typed_data';
class MockBoostChannel extends BoostChannel implements Mock {
MethodHandler get testHandler => _testHandler;
EventListener get testEventListener => _testEventListener;
MethodHandler _testHandler;
EventListener _testEventListener;
VoidCallback addEventListener(String name, EventListener listener) {
_testEventListener = listener;
return super.addEventListener(name, listener);
}
VoidCallback addMethodHandler(MethodHandler handler) {
_testHandler = handler;
return super.addMethodHandler(handler);
}
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
const MessageCodec<dynamic> jsonMessage = JSONMessageCodec();
test('test onMethodCall', () async {
// Initialize all bindings because defaultBinaryMessenger.send() needs a window.
TestWidgetsFlutterBinding.ensureInitialized();
MockBoostChannel boostChannel = MockBoostChannel();
ContainerCoordinator(boostChannel);
final Map arguments =<dynamic,dynamic> {};
arguments["pageName"] = "pageName";
arguments["params"] = <dynamic,dynamic>{};
arguments["uniqueId"] = "xxxxx";
MethodCall call = MethodCall('didInitPageContainer', arguments);
try {
boostChannel.testHandler(call);
} catch (e) {
expect(e, isAssertionError);
}
MethodCall call2 = MethodCall('willShowPageContainer', arguments);
try {
boostChannel.testHandler(call2);
} catch (e) {
expect(e, isNoSuchMethodError);
}
MethodCall call3 = MethodCall('didShowPageContainer', arguments);
try {
boostChannel.testHandler(call3);
} catch (e) {
expect(e, isNoSuchMethodError);
}
MethodCall call4 = MethodCall('willDisappearPageContainer', arguments);
try {
boostChannel.testHandler(call4);
} catch (e) {
expect(e, isNoSuchMethodError);
}
MethodCall call5 = MethodCall('onNativePageResult', arguments);
try {
boostChannel.testHandler(call5);
} catch (e) {
expect(e, isNoSuchMethodError);
}
MethodCall call6 = MethodCall('didDisappearPageContainer', arguments);
try {
boostChannel.testHandler(call6);
} catch (e) {
expect(e, isNoSuchMethodError);
}
MethodCall call7 = MethodCall('willDeallocPageContainer', arguments);
try {
boostChannel.testHandler(call7);
} catch (e) {
expect(e, isNoSuchMethodError);
}
Map arg = <dynamic,dynamic>{'type': 'backPressedCallback'};
try {
boostChannel.testEventListener("lifecycle", arg);
} catch (e) {
expect(e, isNoSuchMethodError);
}
Map arg2 = <dynamic,dynamic>{'type': 'foreground'};
try {
boostChannel.testEventListener("lifecycle", arg2);
} catch (e) {
expect(e, isNoSuchMethodError);
}
Map arg3 = <dynamic,dynamic>{'type': 'background'};
try {
boostChannel.testEventListener("lifecycle", arg3);
} catch (e) {
expect(e, isNoSuchMethodError);
}
Map arg4 = <dynamic,dynamic>{'type': 'scheduleFrame'};
try {
boostChannel.testEventListener("lifecycle", arg4);
} catch (e) {
expect(e, isNoSuchMethodError);
}
});
}
import 'package:flutter_boost/container/container_manager.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_boost/flutter_boost.dart';
import 'package:flutter_boost/container/container_coordinator.dart';
final GlobalKey scaffoldKey = GlobalKey();
class FirstRouteWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text('First'),
onPressed: () {
print("open second page!");
FlutterBoost.singleton.open("second").then((Map value) {
print(
"call me when page is finished. did recieve second route result $value");
});
},
),
],
),
),
);
}
}
class SecondRouteWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Route'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text('Second'),
onPressed: () {
print("open second page!");
FlutterBoost.singleton.open("second").then((Map value) {
print(
"call me when page is finished. did recieve second route result $value");
});
},
),
],
),
),
);
}
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
FlutterBoost.singleton.registerPageBuilders({
'first': (pageName, params, _) => FirstRouteWidget(),
'second': (pageName, params, _) => SecondRouteWidget(),
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Boost example',
key: scaffoldKey,
builder: (BuildContext context, Widget child) {
assert(child is Navigator, 'child must be Navigator, what is wrong?');
final BoostContainerManager manager = BoostContainerManager(
initNavigator: child,
);
return manager;
},
home: Container());
}
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
testWidgets('test iOS edge swipe then drop back at starting point works',
(WidgetTester tester) async {
await tester.pumpWidget(MyApp());
expect(find.text('First'), findsNothing);
});
group(
'Try to get the ContainerManagerState in the ancestor node',
() {
testWidgets(
'through the `BoostContainerManager.of(context)` method',
(WidgetTester tester) async {
var builderContext;
FlutterBoost.singleton.registerPageBuilders({
'context': (pageName, params, _) => Builder(
builder: (context) {
return FloatingActionButton(
onPressed: () {
builderContext = context;
},
);
},
),
});
await tester.pumpWidget(
MaterialApp(
builder: FlutterBoost.init(),
home: Container(),
),
);
//open context page
ContainerCoordinator.singleton
.nativeContainerDidShow("context", {}, "1000000");
await tester.pump(Duration(seconds: 1));
expect(find.byType(FloatingActionButton), findsOneWidget);
//get the context of the Builder
await tester.tap(find.byType(FloatingActionButton));
final isFind = BoostContainerManager.of(builderContext) != null;
expect(isFind, true,
reason: '`BoostContainerManager.of` should be able to '
'find `ContainerManagerState` in `FlutterBoost.init()` based on the context of the `Builder`');
},
);
// testWidgets(
// 'through the `BoostContainerManager.of(context)` method',
// (WidgetTester tester) async {
// var builderContext;
//
// await tester.pumpWidget(
// MaterialApp(
// home: Builder(
// builder: (context) {
// return FloatingActionButton(
// onPressed: () {
// builderContext = context;
// },
// );
// },
// ),
// ),
// );
//
// expect(find.byType(FloatingActionButton), findsOneWidget);
//
// //get the context of the Builder
// await tester.tap(find.byType(FloatingActionButton));
//
// expect(BoostContainerManager.of(builderContext), isAssertionError);
// },
// );
testWidgets(
'through the `BoostContainerManager.tryOf(context)` method',
(WidgetTester tester) async {
var builderContext;
FlutterBoost.singleton.registerPageBuilders({
'context': (pageName, params, _) => Builder(
builder: (context) {
return FloatingActionButton(
onPressed: () {
builderContext = context;
},
);
},
),
});
await tester.pumpWidget(
MaterialApp(
builder: FlutterBoost.init(),
home: Container(),
),
);
//open context page
ContainerCoordinator.singleton
.nativeContainerDidShow("context", {}, "1000000");
await tester.pump(Duration(seconds: 1));
expect(find.byType(FloatingActionButton), findsOneWidget);
//get the context of the Builder
await tester.tap(find.byType(FloatingActionButton));
final isFind = BoostContainerManager.tryOf(builderContext) != null;
expect(isFind, true,
reason: '`BoostContainerManager.tryOf` should be able to '
'find `ContainerManagerState` in `FlutterBoost.init()` based on the context of the `Builder`');
},
);
},
);
group('ContainerManagerState', () {
testWidgets(
'containerCounts should change based on the number of pages',
(WidgetTester tester) async {
var builderContext;
FlutterBoost.singleton.registerPageBuilders({
'context': (pageName, params, _) => Builder(
builder: (context) {
return FloatingActionButton(
onPressed: () {
builderContext = context;
},
);
},
),
});
await tester.pumpWidget(
MaterialApp(
builder: FlutterBoost.init(),
home: Container(),
),
);
//open first context page
ContainerCoordinator.singleton
.nativeContainerDidShow("context", {}, "1000000");
await tester.pump(Duration(seconds: 1));
//get the context of the Builder
await tester.tap(find.byType(FloatingActionButton));
final containerManagerState = BoostContainerManager.of(builderContext);
expect(containerManagerState.containerCounts, 1,
reason: '1 page shown');
//open second context page
ContainerCoordinator.singleton
.nativeContainerDidShow("context", {}, "2000000");
await tester.pump(Duration(seconds: 1));
expect(containerManagerState.containerCounts, 2,
reason: '2 page shown');
//pop second context page
containerManagerState.pop();
await tester.pump(Duration(seconds: 1));
expect(containerManagerState.containerCounts, 1,
reason: 'second context page closed, Only one page left');
//pop last context page
containerManagerState.pop();
await tester.pump(Duration(seconds: 1));
expect(containerManagerState.containerCounts, 0,
reason: 'last context page closed, no page left');
},
);
});
}
import 'package:flutter_boost/container/container_manager.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_boost/flutter_boost.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
// test('test onMethodCall', () async {
// FlutterBoost.singleton
// .registerDefaultPageBuilder((pageName, params, _) => Container());
// FlutterBoost.singleton.addContainerObserver(
// (ContainerOperation operation, BoostContainerSettings settings) {});
//
// FlutterBoost.singleton.addBoostContainerLifeCycleObserver(
// (ContainerLifeCycle state, BoostContainerSettings settings) {});
//
// FlutterBoost.singleton.addBoostNavigatorObserver(NavigatorObserver());
//
// try {
// FlutterBoost.singleton.open("url");
// } catch (e) {
// expect(e, isException);
// }
// try {
// FlutterBoost.singleton.close("url");
// } catch (e) {
// expect(e, isNoSuchMethodError);
// }
// try {
// FlutterBoost.singleton.closeCurrent(result: <String,dynamic>{}, exts: <String,dynamic>{});
// } catch (e) {
// expect(e, isNoSuchMethodError);
// }
//
// try {
// FlutterBoost.singleton.closeByContext(null, result: <String,dynamic>{}, exts: <String,dynamic>{});
// } catch (e) {
// expect(e, isNoSuchMethodError);
// }
// });
}
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_boost/flutter_boost.dart';
import 'package:flutter_test/flutter_test.dart';
import 'page_widgets.dart';
import 'package:flutter_boost/container/container_coordinator.dart';
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
FlutterBoost.singleton.registerPageBuilders({
'embeded': (pageName, params, _) => EmbededFirstRouteWidget(),
'first': (pageName, params, _) => FirstRouteWidget(),
'second': (pageName, params, _) => SecondRouteWidget(),
'tab': (pageName, params, _) => TabRouteWidget(),
'flutterFragment': (pageName, params, _) => FragmentRouteWidget(params),
'flutterPage': (pageName, params, _) {
print("flutterPage params:$params");
return FlutterRouteWidget(params: params);
},
});
FlutterBoost.singleton
.addBoostNavigatorObserver(TestBoostNavigatorObserver());
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Boost example',
builder: FlutterBoost.init(postPush: _onRoutePushed),
home: Container());
}
void _onRoutePushed(
String pageName, String uniqueId, Map params, Route route, Future _) {}
}
class TestBoostNavigatorObserver extends NavigatorObserver {
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
print("flutterboost#didPush");
}
void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
print("flutterboost#didPop");
}
void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) {
print("flutterboost#didRemove");
}
void didReplace({Route<dynamic> newRoute, Route<dynamic> oldRoute}) {
print("flutterboost#didReplace");
}
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
testWidgets('test iOS edge swipe then drop back at starting point works',
(WidgetTester tester) async {
//push app
await tester.pumpWidget(
MyApp(),
);
//open firt page
ContainerCoordinator.singleton
.nativeContainerDidShow("first", <dynamic,dynamic>{}, "1000000");
await tester.pump(const Duration(seconds: 1));
expect(find.text('First'), findsOneWidget);
//open second page firt(1000000)->second(2000000)
ContainerCoordinator.singleton
.nativeContainerDidShow("second", <dynamic,dynamic>{}, "2000000");
await tester.pump(const Duration(seconds: 1));
expect(find.text('Second'), findsOneWidget);
await tester.pump(const Duration(seconds: 1));
//close sencod page firt(1000000)
FlutterBoost.containerManager?.remove("2000000");
await tester.pump(const Duration(seconds: 1));
expect(find.text('First'), findsOneWidget);
// second page ,but pageId is 2000001 firt(1000000)->second(2000001)
ContainerCoordinator.singleton
.nativeContainerDidShow("second", <dynamic,dynamic>{}, "2000001");
await tester.pump(const Duration(seconds: 1));
expect(find.text('Second'), findsOneWidget);
await tester.pump(const Duration(seconds: 1));
//reopen firt page second(2000001)->firt(1000000)
ContainerCoordinator.singleton
.nativeContainerDidShow("first",<dynamic,dynamic> {}, "1000000");
await tester.pump(const Duration(seconds: 1));
expect(find.text('First'), findsOneWidget);
//reopen firt page second(2000001)->firt(1000000)
// reopen second page and pageId is 2000001 firt(1000000)->second(2000001)
ContainerCoordinator.singleton
.nativeContainerDidShow("second", <dynamic,dynamic>{}, "2000001");
await tester.pump(const Duration(seconds: 1));
expect(find.text('Second'), findsOneWidget);
await tester.pump(const Duration(seconds: 1));
//close firt(1000000) page second(2000001)
FlutterBoost.containerManager?.remove("1000000");
await tester.pump(const Duration(seconds: 1));
expect(find.text('Second'), findsOneWidget);
// open second(2000003)
ContainerCoordinator.singleton
.nativeContainerDidShow("second", <dynamic,dynamic>{}, "2000003");
await tester.idle();
expect(find.text('Second'), findsOneWidget);
});
}
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_boost/flutter_boost.dart';
class FirstRouteWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: Center(
child:
Column(
mainAxisAlignment: MainAxisAlignment.center,
children:
<Widget>[
RaisedButton(
child: Text('First'),
onPressed: () {
print("open second page!");
FlutterBoost.singleton.open("second").then((Map value) {
print(
"call me when page is finished. did recieve second route result $value");
});
},
),
RaisedButton(
child: Text('Present second route'),
onPressed: () {
print("Present second page!");
FlutterBoost.singleton.open("second",urlParams:<dynamic,dynamic>{"present":true}).then((Map value) {
print(
"call me when page is finished. did recieve second route result $value");
});
},
),
],
),
),
);
}
}
class EmbededFirstRouteWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text('Open second route'),
onPressed: () {
print("open second page!");
FlutterBoost.singleton.open("second").then((Map value) {
print(
"call me when page is finished. did recieve second route result $value");
});
},
),
),
);
}
}
class SecondRouteWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second"),
),
body: Center(
child: RaisedButton(
onPressed: () {
// Navigate back to first route when tapped.
BoostContainerSettings settings =
BoostContainer.of(context).settings;
FlutterBoost.singleton.close(settings.uniqueId,
result:<dynamic,dynamic>{"result": "data from second"});
},
child: Text('Go back with result!'),
),
),
);
}
}
class TabRouteWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Tab Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
FlutterBoost.singleton.open("second");
},
child: Text('Open second route'),
),
),
);
}
}
class FlutterRouteWidget extends StatefulWidget {
FlutterRouteWidget({this.params,this.message});
final Map params;
final String message;
@override
_FlutterRouteWidgetState createState() => _FlutterRouteWidgetState();
}
class _FlutterRouteWidgetState extends State<FlutterRouteWidget> {
final TextEditingController _usernameController = TextEditingController();
@override
Widget build(BuildContext context) {
final String message=widget.message;
return Scaffold(
appBar: AppBar(
brightness:Brightness.light,
backgroundColor: Colors.white,
textTheme:new TextTheme(title: TextStyle(color: Colors.black)) ,
title: Text('flutter_boost_example'),
),
body: SingleChildScrollView(
child:Container(
margin: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
margin: const EdgeInsets.only(top: 10.0,bottom: 20.0),
child: Text(
message ?? "This is a flutter activity \n params:${widget.params}",
style: TextStyle(fontSize: 28.0, color: Colors.blue),
),
alignment: AlignmentDirectional.center,
),
// Expanded(child: Container()),
const CupertinoTextField(
prefix: Icon(
CupertinoIcons.person_solid,
color: CupertinoColors.lightBackgroundGray,
size: 28.0,
),
padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 12.0),
clearButtonMode: OverlayVisibilityMode.editing,
textCapitalization: TextCapitalization.words,
autocorrect: false,
decoration: BoxDecoration(
border: Border(bottom: BorderSide(width: 0.0, color: CupertinoColors.inactiveGray)),
),
placeholder: 'Name',
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'open native page',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton
.open("sample://nativePage", urlParams: <dynamic,dynamic>{
"query": {"aaa": "bbb"}
}),
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'open first',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton
.open("first", urlParams: <dynamic,dynamic>{
"query": {"aaa": "bbb"}
}),
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'open second',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton
.open("second", urlParams: <dynamic,dynamic>{
"query": {"aaa": "bbb"}
}),
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'open tab',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton
.open("tab", urlParams: <dynamic,dynamic>{
"query": {"aaa": "bbb"}
}),
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'open flutter page',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
///后面的参数会在native的IPlatform.startActivity方法回调中拼接到url的query部分。
///例如:sample://nativePage?aaa=bbb
onTap: () => FlutterBoost.singleton
.open("sample://flutterPage", urlParams:<dynamic,dynamic> {
"query": {"aaa": "bbb"}
}),
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'push flutter widget',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
onTap: () {
Navigator.push<dynamic>(context,
MaterialPageRoute<dynamic>(builder: (_) => PushWidget()));
},
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'push Platform demo',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
onTap: () {
},
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'open flutter fragment page',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
onTap: () => FlutterBoost.singleton
.open("sample://flutterFragmentPage"),
),
],
),
),
),
);
}
}
class FragmentRouteWidget extends StatelessWidget {
final Map params;
FragmentRouteWidget(this.params);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('flutter_boost_example'),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
margin: const EdgeInsets.only(top: 80.0),
child: Text(
"This is a flutter fragment",
style: TextStyle(fontSize: 28.0, color: Colors.blue),
),
alignment: AlignmentDirectional.center,
),
Container(
margin: const EdgeInsets.only(top: 32.0),
child: Text(
params['tag'] ?? '',
style: TextStyle(fontSize: 28.0, color: Colors.red),
),
alignment: AlignmentDirectional.center,
),
Expanded(child: Container()),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'open native page',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
onTap: () => FlutterBoost.singleton.open("sample://nativePage"),
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.all(8.0),
color: Colors.yellow,
child: Text(
'open flutter page',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
onTap: () => FlutterBoost.singleton.open("sample://flutterPage"),
),
InkWell(
child: Container(
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 80.0),
color: Colors.yellow,
child: Text(
'open flutter fragment page',
style: TextStyle(fontSize: 22.0, color: Colors.black),
)),
onTap: () =>
FlutterBoost.singleton.open("sample://flutterFragmentPage"),
)
],
),
);
}
}
class PushWidget extends StatefulWidget {
@override
_PushWidgetState createState() => _PushWidgetState();
}
class _PushWidgetState extends State<PushWidget> {
VoidCallback _backPressedListenerUnsub;
@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
void didChangeDependencies() {
// TODO: implement didChangeDependencies
super.didChangeDependencies();
// if (_backPressedListenerUnsub == null) {
// _backPressedListenerUnsub =
// BoostContainer.of(context).addBackPressedListener(() {
// if (BoostContainer.of(context).onstage &&
// ModalRoute.of(context).isCurrent) {
// Navigator.pop(context);
// }
// });
// }
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
_backPressedListenerUnsub?.call();
}
@override
Widget build(BuildContext context) {
return FlutterRouteWidget(message: "Pushed Widget");
}
}
name: boost_test
description: A new Flutter package.
version: 0.0.1
author:
homepage:
environment:
sdk: '>=2.2.0 <3.0.0'
dependencies:
flutter:
sdk: flutter
test: ^1.5.1
dev_dependencies:
flutter_test:
sdk: flutter
flutter_driver:
sdk: flutter
e2e: ^0.2.1
mockito: 4.1.1
flutter_boost:
path: ../
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# To add assets to your package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.io/assets-and-images/#from-packages
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.io/assets-and-images/#resolution-aware.
# To add custom fonts to your package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.io/custom-fonts/#from-packages
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment