Commit e8361f8f authored by yangwu.jia's avatar yangwu.jia

Merge branch 'master' into v1.12.13-hotfixes

# Conflicts:
#	android/build.gradle
#	android/src/main/java/com/idlefish/flutterboost/Platform.java
parents 7aaec780 eada40d8
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.9.1-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
## 1.9.1+1
Rename the version number and start supporting androidx by default, Based on the flutter 1.9.1 - hotfixs。
fixed bugs
## 0.1.66
Fixed bugs
......
[![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">
<img src="flutter_boost.png">
<b></b><br>
......@@ -20,24 +22,23 @@ You need to add Flutter to your project before moving on.The version of the flut
# 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
2. 0.1.51--0.1.59 is a bugfix for 0.1.50
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
4. 0.1.61--0.1.69 is a bugfix for 0.1.60
| Flutter Boost Version | Support Flutter SDK Version | Description | Support AndroidX? |
| --------------------- | --------------------------- | ------------------------------------------------------------ | ------------------ |
| 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 |
| 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 |
| 1.9.1+1 | 1.9.1-hotfixes | Rename the version number and start supporting androidx by default | 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
......@@ -47,19 +48,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:
support branch
androidx branch
```json
flutter_boost:
git:
url: 'https://github.com/alibaba/flutter_boost.git'
ref: '0.1.64'
ref: ' 1.9.1+1'
```
androidx branch
support branch
```json
flutter_boost:
git:
url: 'https://github.com/alibaba/flutter_boost.git'
ref: 'v0.1.61-androidx-hotfixes'
ref: 'task/task_v1.9.1_support_hotfixes'
```
......
......@@ -20,29 +20,27 @@
请阅读这篇文章:
<a href="Frequently Asked Question.md">FAQ</a>
# boost 版本说明
1. 0.1.50 是基于flutter v1.5.4-hotfixes 分支,android 如果其他flutter版本或者分支 会编译错误
2. 0.1.51--0.1.54 是对0.1.50的bugfix
3. 0.1.60 是基于flutter v1.9.1-hotfixes 分支,android如果其他flutter分支会编译错误,该版本不支持andriodx
4. 0.1.61-- 0.1.64 是对0.1.60 的bugfix
| Flutter Boost 版本 | 支持的 Flutter SDK 版本 | Description | 是否支持 AndroidX? |
| ----------------------- | ----------------------- | ------------------------------------------------------------ | ------------------- |
| 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 |
| 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 |
| 1.9.1+1 | 1.9.1-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 | 是否支持 AndroidX? |
| v1.9.1-hotfixes | 1.9.1-hotfixes | 支持androidx,修复bug后会定期发布新版本 | Yes |
| task/task_v1.9.1_support_hotfixes| 1.9.1-hotfixes | 支持support包,不支持androidx | NO |
| v1.12.13-hotfixes | 1.12.13-hotfixes | 支持androidx,目前还没发布的版本,用分支形式引入 | Yes |
| task/task_v1.12.13_support_hotfixes| 1.12.13-hotfixes | 支持support包,不支持androidx | NO |
.
# 安装
......@@ -50,22 +48,19 @@
打开pubspec.yaml并将以下行添加到依赖项:
support分支
androidx branch
```json
flutter_boost:
git:
url: 'https://github.com/alibaba/flutter_boost.git'
ref: '0.1.64'
ref: ' 1.9.1+1'
```
androidx分支
support branch
```json
flutter_boost:
git:
url: 'https://github.com/alibaba/flutter_boost.git'
ref: 'v0.1.61-androidx-hotfixes'
ref: 'task/task_v1.9.1_support_hotfixes'
```
......
......@@ -26,6 +26,7 @@ public class FlutterBoost {
private FlutterEngine mEngine;
private Activity mCurrentActiveActivity;
private PluginRegistry mRegistry;
private boolean mEnterActivityCreate =false;
static FlutterBoost sInstance = null;
private long FlutterPostFrameCallTime = 0;
......@@ -56,6 +57,10 @@ public class FlutterBoost {
@Override
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;
if (mPlatform.whenEngineStart() == ConfigBuilder.ANY_ACTIVITY_CREATED) {
doInitialFlutter();
......@@ -70,6 +75,9 @@ public class FlutterBoost {
@Override
public void onActivityStarted(Activity activity) {
if (!mEnterActivityCreate){
return;
}
if (mCurrentActiveActivity == null) {
Debuger.log("Application entry foreground");
......@@ -84,16 +92,24 @@ public class FlutterBoost {
@Override
public void onActivityResumed(Activity activity) {
if (!mEnterActivityCreate){
return;
}
mCurrentActiveActivity = activity;
}
@Override
public void onActivityPaused(Activity activity) {
if (!mEnterActivityCreate){
return;
}
}
@Override
public void onActivityStopped(Activity activity) {
if (!mEnterActivityCreate){
return;
}
if (mCurrentActiveActivity == activity) {
Debuger.log("Application entry background");
......@@ -108,11 +124,16 @@ public class FlutterBoost {
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
if (!mEnterActivityCreate){
return;
}
}
@Override
public void onActivityDestroyed(Activity activity) {
if (!mEnterActivityCreate){
return;
}
if (mCurrentActiveActivity == activity) {
Debuger.log("Application entry background");
......@@ -141,6 +162,9 @@ public class FlutterBoost {
if (mEngine != null) return;
if (mPlatform.lifecycleListener != null) {
mPlatform.lifecycleListener.beforeCreateEngine();
}
FlutterEngine flutterEngine = createEngine();
if (mPlatform.lifecycleListener != null) {
mPlatform.lifecycleListener.onEngineCreated();
......@@ -337,6 +361,9 @@ public class FlutterBoost {
public interface BoostLifecycleListener {
void beforeCreateEngine();
void onEngineCreated();
void onPluginsRegistered();
......
......@@ -191,8 +191,13 @@ public class XTextInputPlugin {
if (isInputConnectionLocked) {
return lastInputConnection;
}
lastInputConnection = platformViewsController.getPlatformViewById(inputTarget.id).onCreateInputConnection(outAttrs);
return lastInputConnection;
View platformView = platformViewsController.getPlatformViewById(inputTarget.id);
if (platformView != null) {
lastInputConnection = platformView.onCreateInputConnection(outAttrs);
return lastInputConnection;
} else {
return null;
}
}
outAttrs.inputType = inputTypeFromTextInputType(
......
......@@ -11,15 +11,33 @@ class FirstRouteWidget extends StatelessWidget {
title: Text('First Route'),
),
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");
});
},
child:
Column(
mainAxisAlignment: MainAxisAlignment.center,
children:
<Widget>[
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");
});
},
),
RaisedButton(
child: Text('Present second route'),
onPressed: () {
print("Present second page!");
FlutterBoost.singleton.open("second",urlParams:{"present":true}).then((Map value) {
print(
"call me when page is finished. did recieve second route result $value");
});
},
),
],
),
),
);
......
......@@ -53,10 +53,10 @@
}else{
[_engine runWithEntrypoint:nil];
}
_dummy = [[FLBFlutterViewContainer alloc] initWithEngine:_engine
nibName:nil
bundle:nil];
_dummy.name = kIgnoreMessageWithName;
// _dummy = [[FLBFlutterViewContainer alloc] initWithEngine:_engine
// nibName:nil
// bundle:nil];
// _dummy.name = kIgnoreMessageWithName;
}
return self;
......@@ -100,7 +100,7 @@
- (void)atacheToViewController:(FlutterViewController *)vc
{
if(_engine.viewController != vc){
[(FLBFlutterViewContainer *)_engine.viewController surfaceUpdated:NO];
// [(FLBFlutterViewContainer *)_engine.viewController surfaceUpdated:NO];
_engine.viewController = vc;
}
}
......@@ -114,8 +114,8 @@
- (void)prepareEngineIfNeeded
{
[(FLBFlutterViewContainer *)_engine.viewController surfaceUpdated:NO];
NSLog(@"[XDEBUG]---surface changed--reset-");
// [(FLBFlutterViewContainer *)_engine.viewController surfaceUpdated:NO];
// NSLog(@"[XDEBUG]---surface changed--reset-");
// [self detach];
}
......
......@@ -51,6 +51,9 @@
if(self = [super initWithEngine:FLUTTER_APP.flutterProvider.engine
nibName:_flbNibName
bundle:_flbNibBundle]){
//NOTES:在present页面时,默认是全屏,如此可以触发底层VC的页面事件。否则不会触发而导致异常
self.modalPresentationStyle = UIModalPresentationFullScreen;
[self _setup];
}
return self;
......@@ -219,6 +222,13 @@ static NSUInteger kInstanceCounter = 0;
uniqueId:self.uniqueIDString];
[[[UIApplication sharedApplication] keyWindow] endEditing:YES];
[super viewWillDisappear:animated];
//NOTES:因为UIViewController在present view后dismiss其页面的view disappear会发生在下一个页面view appear之后,从而让当前engine持有的vc inactive,此处可驱使其重新resume
if (![self.uniqueIDString isEqualToString:[(FLBFlutterViewContainer*)FLUTTER_VC uniqueIDString]])
{
[FLUTTER_APP resume];
}
}
......@@ -229,6 +239,14 @@ static NSUInteger kInstanceCounter = 0;
params:_params
uniqueId:self.uniqueIDString];
[super viewDidDisappear:animated];
//NOTES:因为UIViewController在present view后dismiss其页面的view disappear会发生在下一个页面view appear之后,导致当前engine持有的VC被surfaceUpdate(NO),从而销毁底层的raster。此处是考虑到这种情形,重建surface
if (FLUTTER_VC.beingPresented || self.beingDismissed || ![self.uniqueIDString isEqualToString:[(FLBFlutterViewContainer*)FLUTTER_VC uniqueIDString]])
{
[FLUTTER_APP resume];
[(FLBFlutterViewContainer*)FLUTTER_VC surfaceUpdated:YES];
}
// instead of calling [super viewDidDisappear:animated];, call super's super
// struct objc_super target = {
// .super_class = class_getSuperclass([FlutterViewController class]),
......
......@@ -21,6 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
......@@ -36,23 +37,14 @@ class BoostPageRoute<T> extends MaterialPageRoute<T> {
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(
{Key stubKey,
this.pageName,
{this.pageName,
this.params,
this.uniqueId,
this.animated,
this.builder,
this.settings})
: super(
builder: (BuildContext context) => Stub(stubKey, builder(context)),
settings: settings);
: super(builder: builder, settings: settings);
static BoostPageRoute<T> of<T>(BuildContext context) {
final Route<T> route = ModalRoute.of(context);
......@@ -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;
}
......@@ -44,6 +44,7 @@ typedef void PostPushRoute(
String url, String uniqueId, Map params, Route route, Future result);
class FlutterBoost {
static final FlutterBoost _instance = FlutterBoost();
final GlobalKey<ContainerManagerState> containerManagerKey =
GlobalKey<ContainerManagerState>();
......@@ -55,38 +56,31 @@ class FlutterBoost {
static ContainerManagerState get containerManager =>
_instance.containerManagerKey.currentState;
static void onPageStart() {
WidgetsBinding.instance.addPostFrameCallback((_) {
singleton.channel.invokeMethod<Map>('pageOnStart').then((Map pageInfo) {
if (pageInfo == null || pageInfo.isEmpty) return;
if (pageInfo.containsKey("name") &&
pageInfo.containsKey("params") &&
pageInfo.containsKey("uniqueId")) {
ContainerCoordinator.singleton.nativeContainerDidShow(
pageInfo["name"], pageInfo["params"], pageInfo["uniqueId"]);
}
});
});
}
static TransitionBuilder init(
{TransitionBuilder builder,
PrePushRoute prePush,
PostPushRoute postPush}) {
if (Platform.isAndroid) {
onPageStart();
}
assert(() {
() async {
if (Platform.isIOS) {
onPageStart();
}
}();
PrePushRoute prePush,
PostPushRoute postPush}) {
if(Platform.isAndroid){
WidgetsBinding.instance.addPostFrameCallback((_){
return true;
}());
singleton.channel.invokeMethod<Map>('pageOnStart').then((Map pageInfo){
if (pageInfo == null || pageInfo.isEmpty) return;
if (pageInfo.containsKey("name") &&
pageInfo.containsKey("params") &&
pageInfo.containsKey("uniqueId")) {
ContainerCoordinator.singleton.nativeContainerDidShow(
pageInfo["name"], pageInfo["params"], pageInfo["uniqueId"]);
}
});
});
}
return (BuildContext context, Widget child) {
assert(child is Navigator, 'child must be Navigator, what is wrong?');
......@@ -108,7 +102,7 @@ class FlutterBoost {
BoostChannel get channel => _boostChannel;
FlutterBoost() {
FlutterBoost(){
ContainerCoordinator(_boostChannel);
}
......@@ -122,29 +116,30 @@ class FlutterBoost {
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>();
properties["url"] = url;
properties["urlParams"] = urlParams;
properties["exts"] = exts;
return channel.invokeMethod<Map<dynamic, dynamic>>('openPage', properties);
return channel.invokeMethod<Map<dynamic,dynamic>>(
'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);
BoostContainerSettings settings = containerManager?.onstageSettings;
Map<dynamic, dynamic> properties = new Map<dynamic, dynamic>();
if (exts == null) {
exts = Map<dynamic, dynamic>();
if(exts == null){
exts = Map<dynamic,dynamic>();
}
exts["params"] = settings.params;
if (!exts.containsKey("animated")) {
if(!exts.containsKey("animated")){
exts["animated"] = true;
}
......@@ -160,30 +155,28 @@ class FlutterBoost {
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;
if (exts == null) {
exts = Map<String, dynamic>();
if(exts == null){
exts = Map<String,dynamic>();
}
exts["params"] = settings.params;
if (!exts.containsKey("animated")) {
if(!exts.containsKey("animated")){
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;
if (exts == null) {
exts = Map<String, dynamic>();
if(exts == null){
exts = Map<String,dynamic>();
}
exts["params"] = settings.params;
if (!exts.containsKey("animated")) {
if(!exts.containsKey("animated")){
exts["animated"] = true;
}
return close(settings.uniqueId, result: result, exts: exts);
return close(settings.uniqueId,result: result,exts: exts);
}
///register for Container changed callbacks
......
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.
version: 0.1.66
version: 1.9.1+1
author: Alibaba Xianyu
homepage: https://github.com/alibaba/flutter_boost
......@@ -12,6 +12,12 @@ dependencies:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
mockito: 4.1.1
# For information on the generic Dart part of this file, see the
# 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() {
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();
BoostChannel().sendEvent("name", msg1);
Map msg = Map();
msg["name"] = "name";
msg["arguments"] = msg1;
expect(
log,
<Matcher>[isMethodCall('__event__', arguments: msg)],
);
});
test('invokeMethod successfully', () async {
Map msg = {};
msg["test"] = "test";
BoostChannel().invokeMethod("__event__1", msg);
// expect(e, isException);
expect(
log,
<Matcher>[isMethodCall('__event__1', arguments: msg)],
);
});
test('invokeListMethod successfully', () async {
Map msg = {};
msg["test"] = "test";
var bb = await BoostChannel().invokeListMethod("__event__1", msg);
expect(
log,
<Matcher>[isMethodCall('__event__1', arguments: msg)],
);
});
test('invokeMapMethod successfully', () async {
Map msg = {};
msg["test"] = "test";
BoostChannel().invokeMapMethod("__event__1", msg);
expect(
log,
<Matcher>[isMethodCall('__event__1', arguments: msg)],
);
});
test('invokeMapMethod successfully', () async {
Map msg = {};
msg["test"] = "test";
BoostChannel().invokeMapMethod("__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 fails 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.tap(find.byKey(targetKey));
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 {
var boostPageRoute;
var 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(context);
},
);
},
),
);
return boostPageRoute;
},
),
);
await tester.tap(find.byType(FloatingActionButton));
// 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 {
var contextCache;
await tester.pumpWidget(
MaterialApp(
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute(
settings: settings,
builder: (context) => Builder(
builder: (context) => FloatingActionButton(
onPressed: () {
contextCache = context;
},
),
),
);
},
),
);
await tester.tap(find.byType(FloatingActionButton));
expect(() => BoostPageRoute.of(contextCache), throwsException);
});
testWidgets(
'obtain BoostPageRoute through the `BoostPageRoute.tryOf(context)` method',
(WidgetTester tester) async {
var boostPageRoute;
var 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(context);
},
);
},
),
);
return boostPageRoute;
},
),
);
await tester.tap(find.byType(FloatingActionButton));
// 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 {
var boostPageRouteFindByOfMethod;
await tester.pumpWidget(
MaterialApp(
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute(
settings: settings,
builder: (BuildContext context) => Builder(
builder: (context) {
return FloatingActionButton(
onPressed: () {
boostPageRouteFindByOfMethod =
BoostPageRoute.tryOf(context);
},
);
},
),
);
},
),
);
await tester.tap(find.byType(FloatingActionButton));
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;
super.addEventListener(name, listener);
}
VoidCallback addMethodHandler(MethodHandler handler) {
_testHandler = handler;
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 = {};
arguments["pageName"] = "pageName";
arguments["params"] = {};
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 = {'type': 'backPressedCallback'};
try {
boostChannel.testEventListener("lifecycle", arg);
} catch (e) {
expect(e, isNoSuchMethodError);
}
Map arg2 = {'type': 'foreground'};
try {
boostChannel.testEventListener("lifecycle", arg2);
} catch (e) {
expect(e, isNoSuchMethodError);
}
Map arg3 = {'type': 'background'};
try {
boostChannel.testEventListener("lifecycle", arg3);
} catch (e) {
expect(e, isNoSuchMethodError);
}
Map arg4 = {'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';
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 _onRoutePushed(
String pageName, String uniqueId, Map params, Route route, Future _) {}
}
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);
});
}
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();
testWidgets('test iOS edge swipe then drop back at starting point works',
(WidgetTester tester) async {
//push app
});
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, isNoSuchMethodError);
}
try {
FlutterBoost.singleton.close("url");
} catch (e) {
expect(e, isNoSuchMethodError);
}
try {
FlutterBoost.singleton.closeCurrent(result: {}, exts: {});
} catch (e) {
expect(e, isNoSuchMethodError);
}
try {
FlutterBoost.singleton.closeByContext(null, result: {}, exts: {});
} 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", {}, "1000000");
await tester.pump(const Duration(seconds: 1));
expect(find.text('First'), findsOneWidget);
//open second page firt(1000000)->second(2000000)
ContainerCoordinator.singleton
.nativeContainerDidShow("second", {}, "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", {}, "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", {}, "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", {}, "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", {}, "2000003");
await tester.idle();
expect(find.text('Second'), findsOneWidget);
});
}
This diff is collapsed.
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