├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── libraries │ ├── Dart_SDK.xml │ ├── Flutter_Plugins.xml │ └── Flutter_for_Android.xml ├── misc.xml ├── modules.xml ├── runConfigurations │ └── example_lib_main_dart.xml ├── vcs.xml └── workspace.xml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── .gitignore ├── .idea │ ├── .name │ ├── caches │ │ └── build_file_checksums.ser │ ├── codeStyles │ │ └── Project.xml │ ├── gradle.xml │ ├── misc.xml │ ├── modules.xml │ └── runConfigurations.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── taobao │ │ └── hybridstackmanager │ │ ├── FlutterActivityChecker.java │ │ ├── FlutterUtils.java │ │ ├── FlutterWrapperActivity.java │ │ ├── HybridStackManager.java │ │ ├── XFlutterActivityDelegate.java │ │ ├── XFlutterView.java │ │ ├── XURLRouter.java │ │ └── XURLRouterHandler.java │ └── res │ ├── drawable │ └── launch_background.xml │ ├── layout │ └── flutter_layout.xml │ └── values │ ├── strings.xml │ └── styles.xml ├── example ├── .gitignore ├── .idea │ ├── codeStyles │ │ └── Project.xml │ ├── libraries │ │ ├── Dart_SDK.xml │ │ ├── Flutter_Plugins.xml │ │ └── Flutter_for_Android.xml │ ├── misc.xml │ ├── modules.xml │ ├── runConfigurations │ │ └── main_dart.xml │ └── workspace.xml ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── .idea │ │ ├── caches │ │ │ └── build_file_checksums.ser │ │ ├── codeStyles │ │ │ └── Project.xml │ │ ├── gradle.xml │ │ ├── misc.xml │ │ ├── modules.xml │ │ └── runConfigurations.xml │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── hybridstackmanagerexample │ │ │ │ ├── MainActivity.java │ │ │ │ └── XDemoActivity.java │ │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── layout │ │ │ └── placeholder.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── hybrid_stack_manager_example.iml ├── hybrid_stack_manager_example_android.iml ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ │ ├── Info.plist │ │ ├── XDemoController.h │ │ ├── XDemoController.m │ │ ├── XRootController.h │ │ ├── XRootController.m │ │ └── main.m ├── lib │ ├── app_config.dart │ ├── fdemo.dart │ ├── main.dart │ └── my_app.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── flutter_chann_plugin.iml ├── flutter_chann_plugin_android.iml ├── hybrid_stack_manager.iml ├── hybrid_stack_manager_android.iml ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── FlutterViewWrapperController.h │ ├── FlutterViewWrapperController.m │ ├── HybridStackManager.h │ ├── HybridStackManager.m │ ├── UIViewController+URLRouter.h │ ├── UIViewController+URLRouter.m │ ├── XFlutterModule.h │ ├── XFlutterModule.m │ ├── XFlutterViewController.h │ ├── XFlutterViewController.m │ ├── XURLRouter.h │ └── XURLRouter.m ├── LICENSE ├── README.md └── hybrid_stack_manager.podspec.json ├── lib ├── hybrid_stack_manager.dart ├── hybrid_stack_manager_plugin.dart ├── router.dart ├── router_option.dart └── utils.dart └── pubspec.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | pubspec.lock 7 | 8 | build/ 9 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/libraries/Flutter_Plugins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/Flutter_for_Android.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations/example_lib_main_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 39 | 40 | 41 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 87 | 88 | 89 | 90 | 91 | 92 | 106 | 107 | 108 | 109 | 110 | 111 | 123 | 124 | 130 | 131 | 132 | 133 | 151 | 157 | 158 | 166 | 167 | 172 | 173 | 175 | 176 | 177 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 1534214521988 186 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.0.1] - 2018-08-15 2 | 3 | * First tag, support hybrid stack management between native(ios/android) and flutter. 4 | 5 | ## [0.0.2] - 2018-08-15 6 | 7 | * Change environment support(sdk&flutter) 8 | 9 | ## [0.0.3] - 2018-08-21 10 | 11 | * Add License 12 | * Process an AssertError when using flutter v0.5.8+ 13 | * Change all display tet into English 14 | 15 | ## [0.0.4] - 2018-08-21 16 | * Change README. 17 | 18 | ## [0.0.5] - 2018-09-05 19 | * Add TODO where developers may need to add their customized implementations for non-json-serializable objects into json-serializable ones. 20 | 21 | ## [0.0.6] - 2018-09-25 22 | * Repository relocated. 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 KyleWong, http://kangwang1988.github.io 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hybrid_stack_manager 2 | 3 | In hybrid scenarios where there are flutter pages and native pages, and they can jump to flutter/native at will. In other words, hybrid stack management would be the first important problem we should consider. This package can manage the hybrid stack and supports any jumping between flutter/native and native/flutter. 4 | 5 | # Architecture 6 | 7 | ![](https://raw.githubusercontent.com/kangwang1988/kangwang1988.github.io/master/img/hybrid-stack-manangement.png) 8 | 9 | # Snapshot 10 | 11 | iOS 12 | 13 | ![hybrid_stack_management_ios](https://raw.githubusercontent.com/kangwang1988/kangwang1988.github.io/master/img/hybrid_stack_management_ios.gif) 14 | 15 | Android 16 | 17 | ![hybrid_stack_management_android](https://raw.githubusercontent.com/kangwang1988/kangwang1988.github.io/master/img/hybrid_stack_management_android.gif) 18 | 19 | # Usage 20 | 21 | Add dependency in pubspec.yaml: 22 | 23 | hybrid_stack_manager:0.0.6 24 | 25 | After getting the package using "flutter packages get",you can check the examples within the package to see how to use it. 26 | 27 | 28 | ## Usage in iOS side 29 | 30 | 1.Construct a navigationController as the rootWindow's rootViewController for later use when pushing. 31 | 32 | 2.Set the XURLRouter's nativeOpenUrlHandler where you can implement your own business related router logic,as below: 33 | 34 | ``` 35 | [[XURLRouter sharedInstance] setNativeOpenUrlHandler:^UIViewController *(NSString *url,NSDictionary *query,NSDictionary *params){ 36 | NSURL *tmpUrl = [NSURL URLWithString:url]; 37 | if([@"ndemo" isEqualToString:tmpUrl.host]){ 38 | return [XDemoController new]; 39 | } 40 | return nil; 41 | }]; 42 | ``` 43 | 3. All scheme's query will be checked whehter to jump Flutter(flutter=true) or Native(flutter=false/nil)。 This is a universal logic applying in ios/android/flutter. As below: 44 | ``` 45 | void XOpenURLWithQueryAndParams(NSString *url,NSDictionary *query,NSDictionary *params){ 46 | NSURL *tmpUrl = [NSURL URLWithString:url]; 47 | UINavigationController *rootNav = (UINavigationController*)[UIApplication sharedApplication].delegate.window.rootViewController; 48 | if(![kOpenUrlPrefix isEqualToString:tmpUrl.scheme]) 49 | return; 50 | if([[query objectForKey:@"flutter"] boolValue]){ 51 | [[XFlutterModule sharedInstance] openURL:url query:query params:params]; 52 | return; 53 | } 54 | NativeOpenUrlHandler handler = [XURLRouter sharedInstance].nativeOpenUrlHandler; 55 | if(handler!=nil) 56 | { 57 | UIViewController *vc = handler(url,query,params); 58 | if(vc!=nil) 59 | [rootNav pushViewController:vc animated:YES]; 60 | } 61 | } 62 | ``` 63 | 64 | ## Usage in Android side 65 | 66 | 1.Set the XURLRouter's appContextcontext with the ApplicationContext for later use when jumping. 67 | ``` 68 | XURLRouter.sharedInstance().setAppContext(getApplicationContext()); 69 | ``` 70 | 2.Set XURLRouter's NativeRouterHandler with one always existing instance. This instance should implements XURLRouterHandler and implement method:openUrlWithQueryAndParams where you can implement your own business related router logic,as below: 71 | ``` 72 | void setupNativeOpenUrlHandler(){ 73 | XURLRouter.sharedInstance().setNativeRouterHandler(this); 74 | } 75 | public Class openUrlWithQueryAndParams(String url, HashMap query, HashMap params){ 76 | Uri tmpUri = Uri.parse(url); 77 | if("ndemo".equals(tmpUri.getHost())){ 78 | return XDemoActivity.class; 79 | } 80 | return null; 81 | } 82 | ``` 83 | 84 | # Usage in Flutter side 85 | 86 | 1.Init a global key for later use to fetch a context and pass it to the Router. 87 | 2.Set the Router's routerWidgetHandler where Flutter side router logic is implemented,as below: 88 | ``` 89 | Router.sharedInstance().routerWidgetHandler = 90 | ({RouterOption routeOption, Key key}) { 91 | if (routeOption.url == "hrd://fdemo") { 92 | return new FDemoWidget(routeOption, key: key); 93 | } 94 | return null; 95 | }; 96 | return _singleton; 97 | ``` 98 | 99 | # Attention 100 | 1.In Flutter,the NavigatorState class located in flutter/lib/src/widgets/navigator.dart is modified by adding a getter function to fetch the history as below: 101 | ``` 102 | List> get history => _history; 103 | ``` 104 | 2.In iOS,I reuse the XFlutterViewController singleton which is embedded in FlutterViewWrapperController with the help of addChildVC/removeFromParentVC。It is necessary to ensure that the viewWill/DidAppear/Disappear call could be passed from ParentVC Appear to ChildVC(Especially the viewWillAppear: and viewDidDisappear:)。 105 | ``` 106 | - (BOOL)shouldAutomaticallyForwardAppearanceMethods{ 107 | return TRUE; 108 | } 109 | ``` 110 | 111 | 3.Environment 112 | 113 | ``` 114 | KyleWongdeMacBook-Pro:ios kylewong$ /Users/kylewong/Codes/Flutter/official/flutter/bin/flutter doctor -v 115 | [✓] Flutter (Channel unknown, v0.6.0, on Mac OS X 10.14 18A365a, locale en-CN) 116 | • Flutter version 0.6.0 at /Users/kylewong/Codes/Flutter/official/flutter 117 | • Framework revision 9299c02cf7 (5 days ago), 2018-08-16 00:35:12 +0200 118 | • Engine revision e3687f70c7 119 | • Dart version 2.1.0-dev.0.0.flutter-be6309690f 120 | 121 | [✓] Android toolchain - develop for Android devices (Android SDK 27.0.3) 122 | • Android SDK at /Users/kylewong/Library/Android/sdk 123 | • Android NDK at /Users/kylewong/Library/Android/sdk/ndk-bundle 124 | • Platform android-27, build-tools 27.0.3 125 | • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java 126 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1024-b01) 127 | • All Android licenses accepted. 128 | 129 | [!] iOS toolchain - develop for iOS devices (Xcode 9.4.1) 130 | • Xcode at /Applications/Xcode.app/Contents/Developer 131 | • Xcode 9.4.1, Build version 9F2000 132 | • ios-deploy 1.9.2 133 | ! CocoaPods out of date (1.5.0 is recommended). 134 | CocoaPods is used to retrieve the iOS platform side's plugin code that responds to your plugin usage on the Dart side. 135 | Without resolving iOS dependencies with CocoaPods, plugins will not work on iOS. 136 | For more info, see https://flutter.io/platform-plugins 137 | To upgrade: 138 | brew upgrade cocoapods 139 | pod setup 140 | 141 | [✓] Android Studio (version 3.1) 142 | • Android Studio at /Applications/Android Studio.app/Contents 143 | • Flutter plugin version 26.0.1 144 | • Dart plugin version 173.4700 145 | • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1024-b01) 146 | 147 | [!] VS Code (version 1.25.1) 148 | • VS Code at /Applications/Visual Studio Code.app/Contents 149 | • Flutter extension not installed; install from 150 | https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter 151 | 152 | [✓] Connected devices (1 available) 153 | • Nexus 5X • 00d25786e6b71602 • android-arm64 • Android 8.1.0 (API 27) 154 | ``` 155 | Though flutter beta v0.6.0 is used in my environment. In fact, this hybrid stack management logic works even in v0.3.1 and above. 156 | 157 | # Contact me 158 | 159 | [Contact me](mailto:kang.wang1988@gmail.com) 160 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/.idea/.name: -------------------------------------------------------------------------------- 1 | hybrid_stack_manager -------------------------------------------------------------------------------- /android/.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/android/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /android/.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /android/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | -------------------------------------------------------------------------------- /android/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /android/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.taobao.hybridstackmanager' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:2.3.0' 12 | } 13 | } 14 | 15 | rootProject.allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | apply plugin: 'com.android.library' 23 | 24 | android { 25 | compileSdkVersion = 24 26 | buildToolsVersion = "26.0.2" 27 | defaultConfig { 28 | minSdkVersion 14 29 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 30 | } 31 | lintOptions { 32 | disable 'InvalidPackage' 33 | abortOnError false 34 | } 35 | } 36 | 37 | 38 | // publish到aar的版本按下列顺序读取 39 | // 1、从gradlew命令的参数中读取 40 | // 2、从flutter工程根目录下的sync_tools的android_aar_version_config配置中读取 41 | // 3、从下面ext中读取 42 | ext { 43 | groupId = 'com.taobao.hybridstackmanager' 44 | artifactId = "hybridstackmanager" 45 | version '1.0.1-SNAPSHOT' 46 | } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Aug 14 10:43:01 CST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'hybrid_stack_manager' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 5 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /android/src/main/java/com/taobao/hybridstackmanager/FlutterActivityChecker.java: -------------------------------------------------------------------------------- 1 | package com.taobao.hybridstackmanager; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Created by kylewong on 19/03/2018. 8 | */ 9 | 10 | public interface FlutterActivityChecker { 11 | 12 | public boolean isActive(); 13 | 14 | public void openUrl(String url); 15 | 16 | public void setCurFlutterRouteName(String curFlutterRouteName); 17 | 18 | public void popCurActivity(); 19 | } 20 | -------------------------------------------------------------------------------- /android/src/main/java/com/taobao/hybridstackmanager/FlutterUtils.java: -------------------------------------------------------------------------------- 1 | package com.taobao.hybridstackmanager; 2 | import android.os.Build; 3 | 4 | public class FlutterUtils { 5 | public static boolean isSupportFlutter(){ 6 | if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN){ 7 | //Flutter supports android with apilevel 16 and above. 8 | return true; 9 | } else{ 10 | return false; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/src/main/java/com/taobao/hybridstackmanager/FlutterWrapperActivity.java: -------------------------------------------------------------------------------- 1 | package com.taobao.hybridstackmanager; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.res.Configuration; 7 | import android.graphics.Bitmap; 8 | import android.net.Uri; 9 | import android.os.Bundle; 10 | import android.os.Handler; 11 | import android.util.Log; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.FrameLayout; 15 | import android.widget.ImageView; 16 | 17 | import com.taobao.hybridstackmanager.XFlutterActivityDelegate.ViewFactory; 18 | 19 | import java.lang.reflect.InvocationTargetException; 20 | import java.lang.reflect.Method; 21 | import java.util.HashMap; 22 | import io.flutter.plugin.common.PluginRegistry; 23 | import io.flutter.view.FlutterNativeView; 24 | import io.flutter.view.FlutterView; 25 | 26 | 27 | public class FlutterWrapperActivity extends Activity implements PluginRegistry,ViewFactory,FlutterView.Provider, FlutterActivityChecker { 28 | private static XFlutterActivityDelegate delegate; 29 | private static XFlutterView flutterView; 30 | private static FlutterNativeView nativeView; 31 | private XFlutterActivityDelegate eventDelegate; 32 | private PluginRegistry pluginRegistry; 33 | private boolean isActive; 34 | private String curFlutterRouteName; 35 | private static int flutterWrapperInstCnt=0; 36 | //Flutter Activity Related Work 37 | private ImageView fakeSnapImgView; 38 | private Bitmap lastbitmap; 39 | private String pageName = ""; 40 | private HashMap spm = null; 41 | 42 | public void setPageName(String pageName){ 43 | this.pageName = pageName; 44 | } 45 | 46 | public void setSpm(HashMap spm){ 47 | this.spm = spm; 48 | } 49 | 50 | /** 51 | * Returns the Flutter view used by this activity; will be null before 52 | * {@link #onCreate(Bundle)} is called. 53 | */ 54 | @Override 55 | public XFlutterView getFlutterView() { 56 | return createFlutterView(this); 57 | } 58 | 59 | /** 60 | * Hook for subclasses to customize the creation of the 61 | * {@code FlutterView}. 62 | * 63 | *

The default implementation returns {@code null}, which will cause the 64 | * activity to use a newly instantiated full-screen view.

65 | */ 66 | @Override 67 | public XFlutterView createFlutterView(Context context) { 68 | if(flutterView!=null) 69 | return flutterView; 70 | flutterView = new XFlutterView(this,null,createFlutterNativeView()); 71 | return flutterView; 72 | } 73 | 74 | /** 75 | * Hook for subclasses to customize the creation of the 76 | * {@code FlutterNativeView}. 77 | * 78 | *

The default implementation returns {@code null}, which will cause the 79 | * activity to use a newly instantiated native view object.

80 | */ 81 | @Override 82 | public FlutterNativeView createFlutterNativeView() { 83 | if(nativeView!=null) 84 | return nativeView; 85 | nativeView = new FlutterNativeView(this.getApplicationContext()); 86 | return nativeView; 87 | } 88 | 89 | private boolean isFlutterViewAttachedOnMe(){ 90 | FrameLayout rootView = (FrameLayout) findViewById(R.id.flutter_rootview); 91 | XFlutterView flutterView = getFlutterView(); 92 | ViewGroup priorParent = (ViewGroup) flutterView.getParent(); 93 | return rootView == priorParent; 94 | } 95 | 96 | @Override 97 | public final boolean hasPlugin(String key) { 98 | return pluginRegistry.hasPlugin(key); 99 | } 100 | 101 | @Override 102 | public final T valuePublishedByPlugin(String pluginKey) { 103 | return pluginRegistry.valuePublishedByPlugin(pluginKey); 104 | } 105 | @Override 106 | public final Registrar registrarFor(String pluginKey) { 107 | return pluginRegistry.registrarFor(pluginKey); 108 | } 109 | @Override 110 | protected void onCreate(Bundle savedInstanceState) { 111 | boolean firstLaunch = (nativeView==null?true:false); 112 | 113 | super.onCreate(savedInstanceState); 114 | checkIfInitActivityDelegate(); 115 | eventDelegate.onCreate(savedInstanceState); 116 | 117 | if(firstLaunch){ 118 | eventDelegate.runFlutterBundle(); 119 | Class c = null; 120 | try { 121 | c = Class.forName("io.flutter.plugins.GeneratedPluginRegistrant"); 122 | Method method = c.getMethod("registerWith",PluginRegistry.class); 123 | method.invoke(null,pluginRegistry); 124 | } catch (ClassNotFoundException e) { 125 | e.printStackTrace(); 126 | } catch (NoSuchMethodException e) { 127 | e.printStackTrace(); 128 | } catch (InvocationTargetException e) { 129 | e.printStackTrace(); 130 | } catch (IllegalAccessException e) { 131 | e.printStackTrace(); 132 | } 133 | } 134 | else{ 135 | try { 136 | flutterView.registerReceiver(); 137 | }catch (Exception e){ 138 | Log.e( "FlutterWrapperActivity ","onCreate flutterView.registerReceiver error" ); 139 | } 140 | } 141 | setContentView(R.layout.flutter_layout); 142 | checkIfAddFlutterView(); 143 | fakeSnapImgView = (ImageView) findViewById(R.id.flutter_snap_imageview); 144 | fakeSnapImgView.setVisibility(View.GONE); 145 | //Process Intent Extra 146 | Intent intent = getIntent(); 147 | Bundle bundle = intent.getExtras(); 148 | Uri uri = intent.getData(); 149 | if(uri!=null){ 150 | HybridStackManager.sharedInstance().openUrlFromFlutter(uri.toString(),null,null); 151 | } 152 | else if(bundle!=null){ 153 | HybridStackManager.sharedInstance().openUrlFromFlutter(intent.getStringExtra("url"),(HashMap)intent.getSerializableExtra("query"),(HashMap)intent.getSerializableExtra("params")); 154 | } 155 | flutterWrapperInstCnt++; 156 | } 157 | 158 | 159 | @Override 160 | protected void onResume() { 161 | super.onResume(); 162 | fakeSnapImgView.setVisibility(View.GONE); 163 | checkIfAddFlutterView(); 164 | if(isFlutterViewAttachedOnMe()) 165 | eventDelegate.onResume(); 166 | HybridStackManager.sharedInstance().curFlutterActivity = this; 167 | isActive = true; 168 | if(lastbitmap!=null && curFlutterRouteName!=null && curFlutterRouteName.length()>0 ){ 169 | HybridStackManager.sharedInstance().methodChannel.invokeMethod("popToRouteNamed",curFlutterRouteName); 170 | } 171 | } 172 | 173 | private void destorybitmap() { 174 | if (lastbitmap != null && !lastbitmap.isRecycled()) { 175 | lastbitmap.recycle(); 176 | lastbitmap = null; 177 | } 178 | fakeSnapImgView.setImageBitmap(null); 179 | } 180 | 181 | @Override 182 | protected void onDestroy() { 183 | // eventDelegate.onDestroy(); 184 | flutterWrapperInstCnt--; 185 | if(flutterWrapperInstCnt==0){ 186 | HybridStackManager.sharedInstance().methodChannel.invokeMethod("popToRoot",null); 187 | } 188 | isActive = false; 189 | try { 190 | destorybitmap(); 191 | flutterView.unregisterReceiver(); 192 | }catch (Exception e){ 193 | Log.e( "FlutterWrapperActivity ","onDestroy flutterView.unregisterReceiver error" ); 194 | } 195 | super.onDestroy(); 196 | } 197 | 198 | @Override 199 | public void onBackPressed() { 200 | popCurActivity(); 201 | } 202 | 203 | @Override 204 | protected void onPause() { 205 | super.onPause(); 206 | if(isFlutterViewAttachedOnMe()) 207 | eventDelegate.onPause(); 208 | isActive = false; 209 | } 210 | 211 | @Override 212 | protected void onStart(){ 213 | super.onStart(); 214 | eventDelegate.onStart(); 215 | HybridStackManager.sharedInstance().curFlutterActivity = this; 216 | isActive = true; 217 | } 218 | 219 | @Override 220 | protected void onStop() { 221 | FrameLayout rootView = (FrameLayout) findViewById(R.id.flutter_rootview); 222 | XFlutterView flutterView = getFlutterView(); 223 | ViewGroup priorParent = (ViewGroup) flutterView.getParent(); 224 | if(isFlutterViewAttachedOnMe()) 225 | eventDelegate.onStop(); 226 | super.onStop(); 227 | if(super.isFinishing()){ 228 | HybridStackManager.sharedInstance().methodChannel.invokeMethod("popRouteNamed",curFlutterRouteName); 229 | if (priorParent == rootView) { 230 | priorParent.removeView(flutterView); 231 | } 232 | } 233 | } 234 | 235 | @Override 236 | protected void onPostResume() { 237 | super.onPostResume(); 238 | if(isFlutterViewAttachedOnMe()) 239 | eventDelegate.onPostResume(); 240 | } 241 | 242 | // @Override - added in API level 23 243 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { 244 | if(isFlutterViewAttachedOnMe()) 245 | eventDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults); 246 | } 247 | 248 | @Override 249 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 250 | if(!(isFlutterViewAttachedOnMe() && eventDelegate.onActivityResult(requestCode, resultCode, data))){ 251 | super.onActivityResult(requestCode, resultCode, data); 252 | } 253 | } 254 | 255 | @Override 256 | protected void onNewIntent(Intent intent) { 257 | if(isFlutterViewAttachedOnMe()) 258 | eventDelegate.onNewIntent(intent); 259 | } 260 | 261 | @Override 262 | public void onUserLeaveHint() { 263 | if(isFlutterViewAttachedOnMe()) 264 | eventDelegate.onUserLeaveHint(); 265 | } 266 | 267 | @Override 268 | public void onTrimMemory(int level) { 269 | if(isFlutterViewAttachedOnMe()) 270 | eventDelegate.onTrimMemory(level); 271 | } 272 | 273 | @Override 274 | public void onLowMemory() { 275 | if(isFlutterViewAttachedOnMe()) 276 | eventDelegate.onLowMemory(); 277 | } 278 | 279 | @Override 280 | public void onConfigurationChanged(Configuration newConfig) { 281 | super.onConfigurationChanged(newConfig); 282 | if(isFlutterViewAttachedOnMe()) 283 | eventDelegate.onConfigurationChanged(newConfig); 284 | } 285 | 286 | //ActivityDelegate Related 287 | void checkIfInitActivityDelegate(){ 288 | if(nativeView==null){ 289 | Intent intent = getIntent(); 290 | Bundle bundle = intent.getExtras(); 291 | Uri uri = intent.getData(); 292 | HashMap arguments = new HashMap(); 293 | if(uri!=null){ 294 | arguments = HybridStackManager.assembleChanArgs(uri.toString(),null,null); 295 | } 296 | else if(bundle!=null){ 297 | arguments = HybridStackManager.assembleChanArgs(intent.getStringExtra("url"),(HashMap)intent.getSerializableExtra("query"),(HashMap)intent.getSerializableExtra("params")); 298 | } 299 | HybridStackManager.sharedInstance().mainEntryParams = arguments; 300 | } 301 | if(delegate == null) { 302 | delegate = new XFlutterActivityDelegate(this, this); 303 | } 304 | else { 305 | delegate.resetActivity(this); 306 | } 307 | eventDelegate = delegate; 308 | pluginRegistry = delegate; 309 | } 310 | 311 | @Override 312 | public boolean isActive() { 313 | return isActive; 314 | } 315 | 316 | public void openUrl(String url) { 317 | HybridStackManager.sharedInstance().curFlutterActivity = null; 318 | if(url.contains("flutter=true")){ 319 | Intent intent = new Intent(FlutterWrapperActivity.this, FlutterWrapperActivity.class); 320 | intent.setAction(Intent.ACTION_RUN); 321 | intent.setData(Uri.parse(url)); 322 | this.innerStartActivity(intent,true); 323 | } 324 | else{ 325 | Uri tmpUri = Uri.parse(url); 326 | String tmpUrl = String.format("%s://%s",tmpUri.getScheme(),tmpUri.getHost()); 327 | HashMap query = new HashMap(); 328 | for(String key : tmpUri.getQueryParameterNames()){ 329 | query.put(key,tmpUri.getQueryParameter(key)); 330 | } 331 | XURLRouter.sharedInstance().openUrlWithQueryAndParams(tmpUrl,query,null); 332 | saveFinishSnapshot(false); 333 | } 334 | } 335 | 336 | public void innerStartActivity(Intent intent,boolean showSnapshot){ 337 | this.startActivity(intent); 338 | saveFinishSnapshot(showSnapshot); 339 | } 340 | 341 | 342 | @Override 343 | public void setCurFlutterRouteName(String curFlutterRouteName){ 344 | this.curFlutterRouteName = curFlutterRouteName; 345 | } 346 | 347 | @Override 348 | public void popCurActivity() { 349 | finish(); 350 | saveFinishSnapshot(true); 351 | } 352 | 353 | //Flutter View Related Logic 354 | void checkIfAddFlutterView() { 355 | final FrameLayout rootView = (FrameLayout) findViewById(R.id.flutter_rootview); 356 | final XFlutterView flutterView = getFlutterView(); 357 | ViewGroup priorParent = (ViewGroup) flutterView.getParent(); 358 | if (priorParent == rootView) { 359 | return; 360 | } 361 | final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, 362 | FrameLayout.LayoutParams.MATCH_PARENT); 363 | final FlutterWrapperActivity activity = this; 364 | if (priorParent != null) { 365 | priorParent.removeView(flutterView); 366 | final Handler handler = new Handler(); 367 | handler.postDelayed(new Runnable() { 368 | @Override 369 | public void run() { 370 | //Do something after delay of 20ms 371 | if (flutterView.getParent() == null && activity.isActive==true ) { 372 | rootView.addView(flutterView, params); 373 | flutterView.resetActivity(activity); 374 | } 375 | } 376 | }, 20); 377 | } 378 | else{ 379 | rootView.addView(flutterView, params); 380 | flutterView.resetActivity(activity); 381 | } 382 | } 383 | 384 | void saveFinishSnapshot(boolean showSnapshot){ 385 | XFlutterView fv = getFlutterView(); 386 | lastbitmap = fv.getBitmap(); 387 | fakeSnapImgView.setImageBitmap(lastbitmap); 388 | if(showSnapshot) 389 | fakeSnapImgView.setVisibility(View.VISIBLE); 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /android/src/main/java/com/taobao/hybridstackmanager/HybridStackManager.java: -------------------------------------------------------------------------------- 1 | package com.taobao.hybridstackmanager; 2 | import android.net.Uri; 3 | 4 | import java.net.URLEncoder; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import android.os.Build; 9 | import io.flutter.plugin.common.MethodCall; 10 | import io.flutter.plugin.common.MethodChannel; 11 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 12 | import io.flutter.plugin.common.MethodChannel.Result; 13 | import io.flutter.plugin.common.PluginRegistry.Registrar; 14 | 15 | /** 16 | * hybridstackmanager 17 | */ 18 | public class HybridStackManager implements MethodCallHandler { 19 | /** 20 | * Plugin registration. 21 | */ 22 | private static HybridStackManager hybridStackManager = null; 23 | public MethodChannel methodChannel; 24 | public FlutterActivityChecker curFlutterActivity; 25 | public HashMap mainEntryParams; 26 | public HashMap deviceInfoParams; 27 | 28 | public static HybridStackManager sharedInstance() { 29 | if (hybridStackManager != null) { return hybridStackManager; } 30 | hybridStackManager = new HybridStackManager(); 31 | return hybridStackManager; 32 | } 33 | 34 | public static void registerWith(Registrar registrar) { 35 | hybridStackManager = HybridStackManager.sharedInstance(); 36 | hybridStackManager.methodChannel = new MethodChannel(registrar.messenger(), "hybrid_stack_manager"); 37 | hybridStackManager.methodChannel.setMethodCallHandler(hybridStackManager); 38 | } 39 | 40 | public static HashMap assembleChanArgs(String url, HashMap query, HashMap params) { 41 | HashMap arguments = new HashMap(); 42 | Uri uri = Uri.parse(url); 43 | String tmpUrl = String.format("%s://%s", uri.getScheme(), uri.getHost()); 44 | HashMap tmpQuery = new HashMap(); 45 | if (query != null) { tmpQuery.putAll(query); } 46 | for (String key : uri.getQueryParameterNames()) { 47 | tmpQuery.put(key, uri.getQueryParameter(key)); 48 | } 49 | 50 | HashMap tmpParams = new HashMap(); 51 | if (params != null) { tmpQuery.putAll(params); } 52 | 53 | if (tmpUrl != null) { arguments.put("url", tmpUrl); } 54 | if (tmpQuery != null) { arguments.put("query", tmpQuery); } 55 | if (tmpParams != null) { arguments.put("params", tmpParams); } 56 | return arguments; 57 | } 58 | 59 | public static String concatUrl(String url, HashMap query, HashMap params) { 60 | // assert(params==null||params.size()==0); 61 | Uri uri = Uri.parse(url); 62 | Uri.Builder builder = uri.buildUpon(); 63 | if (query != null) { 64 | for (Object key : query.keySet()) { 65 | Object value = query.get(key); 66 | if (value != null) { 67 | final String str; 68 | str = value.toString(); 69 | builder.appendQueryParameter(String.valueOf(key),str); 70 | } 71 | } 72 | } 73 | return builder.build().toString(); 74 | } 75 | 76 | public void openUrlFromFlutter(String url, HashMap query, HashMap params) { 77 | HybridStackManager.sharedInstance().methodChannel.invokeMethod("openURLFromFlutter", 78 | assembleChanArgs(url, query, params)); 79 | } 80 | 81 | @Override 82 | public void onMethodCall(MethodCall call, Result result) { 83 | if (call.method.equals("openUrlFromNative")) { 84 | if (curFlutterActivity != null && curFlutterActivity.isActive()) { 85 | HashMap openUrlInfo = (HashMap)call.arguments; 86 | String url = (String)openUrlInfo.get("url"); 87 | HashMap query = (HashMap)openUrlInfo.get("query"); 88 | HashMap params = (HashMap)openUrlInfo.get("params"); 89 | String concatUrl = concatUrl(url, query, params); 90 | curFlutterActivity.openUrl(concatUrl); 91 | } 92 | result.success("OK"); 93 | } else if (call.method.equals("getMainEntryParams")) { 94 | if (mainEntryParams == null) { mainEntryParams = new HashMap(); } 95 | result.success(mainEntryParams); 96 | // mainEntryParams = null; 97 | } else if (call.method.equals("updateCurFlutterRoute")) { 98 | String curRouteName = (String)call.arguments; 99 | if (curFlutterActivity != null && curFlutterActivity.isActive()) { 100 | curFlutterActivity.setCurFlutterRouteName(curRouteName); 101 | } 102 | result.success("OK"); 103 | } else if (call.method.equals("popCurPage")) { 104 | if (curFlutterActivity != null && curFlutterActivity.isActive()) { 105 | curFlutterActivity.popCurActivity(); 106 | } 107 | result.success("OK"); 108 | }else { 109 | result.notImplemented(); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /android/src/main/java/com/taobao/hybridstackmanager/XFlutterActivityDelegate.java: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | package com.taobao.hybridstackmanager; 6 | 7 | import android.animation.Animator; 8 | import android.animation.AnimatorListenerAdapter; 9 | import android.app.Activity; 10 | import android.app.Application; 11 | import android.content.Context; 12 | import android.content.Intent; 13 | import android.content.pm.ActivityInfo; 14 | import android.content.pm.ApplicationInfo; 15 | import android.content.pm.PackageManager; 16 | import android.content.pm.PackageManager.NameNotFoundException; 17 | import android.content.res.Configuration; 18 | import android.content.res.Resources.NotFoundException; 19 | import android.graphics.drawable.Drawable; 20 | import android.os.Build; 21 | import android.os.Bundle; 22 | import android.util.Log; 23 | import android.util.TypedValue; 24 | import android.view.View; 25 | import android.view.ViewGroup; 26 | import android.view.Window; 27 | import android.view.WindowManager.LayoutParams; 28 | import io.flutter.plugin.common.PluginRegistry; 29 | import io.flutter.plugin.platform.PlatformPlugin; 30 | import io.flutter.util.Preconditions; 31 | import io.flutter.view.FlutterMain; 32 | import io.flutter.view.FlutterNativeView; 33 | import io.flutter.view.FlutterView; 34 | import java.util.ArrayList; 35 | import io.flutter.app.FlutterActivityEvents; 36 | 37 | /** 38 | * Class that performs the actual work of tying Android {@link Activity} 39 | * instances to Flutter. 40 | * 41 | *

This exists as a dedicated class (as opposed to being integrated directly 42 | * into {@link FlutterActivity}) to facilitate applications that don't wish 43 | * to subclass {@code FlutterActivity}. The most obvious example of when this 44 | * may come in handy is if an application wishes to subclass the Android v4 45 | * support library's {@code FragmentActivity}.

46 | * 47 | *

Usage:

48 | *

To wire this class up to your activity, simply forward the events defined 49 | * in {@link FlutterActivityEvents} from your activity to an instance of this 50 | * class. Optionally, you can make your activity implement 51 | * {@link PluginRegistry} and/or {@link io.flutter.view.FlutterView.Provider} 52 | * and forward those methods to this class as well.

53 | */ 54 | public final class XFlutterActivityDelegate 55 | implements FlutterActivityEvents, 56 | FlutterView.Provider, 57 | PluginRegistry { 58 | private static final String SPLASH_SCREEN_META_DATA_KEY = "io.flutter.app.android.SplashScreenUntilFirstFrame"; 59 | private static final String TAG = "FlutterActivityDelegate"; 60 | private static final LayoutParams matchParent = 61 | new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 62 | 63 | /** 64 | * Specifies the mechanism by which Flutter views are created during the 65 | * operation of a {@code FlutterActivityDelegate}. 66 | * 67 | *

A delegate's view factory will be consulted during 68 | * {@link #onCreate(Bundle)}. If it returns {@code null}, then the delegate 69 | * will fall back to instantiating a new full-screen {@code FlutterView}.

70 | * 71 | *

A delegate's native view factory will be consulted during 72 | * {@link #onCreate(Bundle)}. If it returns {@code null}, then the delegate 73 | * will fall back to instantiating a new {@code FlutterNativeView}. This is 74 | * useful for applications to override to reuse the FlutterNativeView held 75 | * e.g. by a pre-existing background service.

76 | */ 77 | public interface ViewFactory { 78 | FlutterView createFlutterView(Context context); 79 | FlutterNativeView createFlutterNativeView(); 80 | } 81 | 82 | private Activity activity; 83 | private final ViewFactory viewFactory; 84 | private FlutterView flutterView; 85 | private View launchView; 86 | 87 | public XFlutterActivityDelegate(Activity activity, ViewFactory viewFactory) { 88 | this.activity = Preconditions.checkNotNull(activity); 89 | this.viewFactory = Preconditions.checkNotNull(viewFactory); 90 | } 91 | 92 | @Override 93 | public FlutterView getFlutterView() { 94 | return flutterView; 95 | } 96 | 97 | public void resetActivity(Activity activity){ 98 | this.activity = activity; 99 | } 100 | 101 | // The implementation of PluginRegistry forwards to flutterView. 102 | @Override 103 | public boolean hasPlugin(String key) { 104 | return flutterView.getPluginRegistry().hasPlugin(key); 105 | } 106 | 107 | @Override 108 | @SuppressWarnings("unchecked") 109 | public T valuePublishedByPlugin(String pluginKey) { 110 | return (T) flutterView.getPluginRegistry().valuePublishedByPlugin(pluginKey); 111 | } 112 | 113 | @Override 114 | public Registrar registrarFor(String pluginKey) { 115 | return flutterView.getPluginRegistry().registrarFor(pluginKey); 116 | } 117 | 118 | @Override 119 | public boolean onRequestPermissionsResult( 120 | int requestCode, String[] permissions, int[] grantResults) { 121 | return flutterView.getPluginRegistry().onRequestPermissionsResult(requestCode, permissions, grantResults); 122 | } 123 | 124 | /* 125 | * Method onRequestPermissionResult(int, String[], int[]) was made 126 | * unavailable on 2018-02-28, following deprecation. This comment is left as 127 | * a temporary tombstone for reference, to be removed on 2018-03-28 (or at 128 | * least four weeks after release of unavailability). 129 | * 130 | * https://github.com/flutter/flutter/wiki/Changelog#typo-fixed-in-flutter-engine-android-api 131 | */ 132 | 133 | @Override 134 | public boolean onActivityResult(int requestCode, int resultCode, Intent data) { 135 | return flutterView.getPluginRegistry().onActivityResult(requestCode, resultCode, data); 136 | } 137 | 138 | @Override 139 | public void onCreate(Bundle savedInstanceState) { 140 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 141 | Window window = activity.getWindow(); 142 | window.addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 143 | window.setStatusBarColor(0x40000000); 144 | window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI); 145 | } 146 | 147 | String[] args = getArgsFromIntent(activity.getIntent()); 148 | FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), args); 149 | 150 | flutterView = viewFactory.createFlutterView(activity); 151 | if (flutterView == null) { 152 | FlutterNativeView nativeView = viewFactory.createFlutterNativeView(); 153 | flutterView = new FlutterView(activity, null, nativeView); 154 | flutterView.setLayoutParams(matchParent); 155 | activity.setContentView(flutterView); 156 | launchView = createLaunchView(); 157 | if (launchView != null) { 158 | addLaunchView(); 159 | } 160 | } 161 | } 162 | 163 | public void runFlutterBundle(){ 164 | // When an activity is created for the first time, we direct the 165 | // FlutterView to re-use a pre-existing Isolate rather than create a new 166 | // one. This is so that an Isolate coming in from the ViewFactory is 167 | // used. 168 | final boolean reuseIsolate = true; 169 | 170 | if (loadIntent(activity.getIntent(), reuseIsolate)) { 171 | return; 172 | } 173 | String appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext()); 174 | if (appBundlePath != null) { 175 | flutterView.runFromBundle(appBundlePath, null, "main", reuseIsolate); 176 | } 177 | } 178 | 179 | @Override 180 | public void onNewIntent(Intent intent) { 181 | // Only attempt to reload the Flutter Dart code during development. Use 182 | // the debuggable flag as an indicator that we are in development mode. 183 | if (!isDebuggable() || !loadIntent(intent)) { 184 | flutterView.getPluginRegistry().onNewIntent(intent); 185 | } 186 | } 187 | 188 | private boolean isDebuggable() { 189 | return (activity.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; 190 | } 191 | 192 | @Override 193 | public void onPause() { 194 | // Application app = (Application) activity.getApplicationContext(); 195 | // if (app instanceof FlutterApplication) { 196 | // FlutterApplication flutterApp = (FlutterApplication) app; 197 | // if (this.equals(flutterApp.getCurrentActivity())) { 198 | // Log.i(TAG, "onPause setting current activity to null"); 199 | // flutterApp.setCurrentActivity(null); 200 | // } 201 | // } 202 | if (flutterView != null) { 203 | flutterView.onPause(); 204 | } 205 | } 206 | 207 | //@Override 208 | public void onStart() { 209 | if (flutterView != null) { 210 | flutterView.onStart(); 211 | } 212 | } 213 | 214 | 215 | @Override 216 | public void onResume() { 217 | Application app = (Application) activity.getApplicationContext(); 218 | // if (app instanceof FlutterApplication) { 219 | // FlutterApplication flutterApp = (FlutterApplication) app; 220 | // Log.i(TAG, "onResume setting current activity to this"); 221 | // flutterApp.setCurrentActivity(activity); 222 | // } else { 223 | // Log.i(TAG, "onResume app wasn't a FlutterApplication!!"); 224 | // } 225 | } 226 | 227 | //@Override 228 | public void onStop() { 229 | flutterView.onStop(); 230 | } 231 | 232 | @Override 233 | public void onPostResume() { 234 | if (flutterView != null) { 235 | flutterView.onPostResume(); 236 | } 237 | } 238 | 239 | @Override 240 | public void onDestroy() { 241 | Application app = (Application) activity.getApplicationContext(); 242 | // if (app instanceof FlutterApplication) { 243 | // FlutterApplication flutterApp = (FlutterApplication) app; 244 | // if (this.equals(flutterApp.getCurrentActivity())) { 245 | // Log.i(TAG, "onDestroy setting current activity to null"); 246 | // flutterApp.setCurrentActivity(null); 247 | // } 248 | // } 249 | if (flutterView != null) { 250 | final boolean detach = 251 | flutterView.getPluginRegistry().onViewDestroy(flutterView.getFlutterNativeView()); 252 | if (detach) { 253 | // Detach, but do not destroy the FlutterView if a plugin 254 | // expressed interest in its FlutterNativeView. 255 | flutterView.detach(); 256 | } else { 257 | flutterView.destroy(); 258 | } 259 | } 260 | } 261 | 262 | @Override 263 | public boolean onBackPressed() { 264 | if (flutterView != null) { 265 | flutterView.popRoute(); 266 | return true; 267 | } 268 | return false; 269 | } 270 | 271 | @Override 272 | public void onUserLeaveHint() { 273 | flutterView.getPluginRegistry().onUserLeaveHint(); 274 | } 275 | 276 | @Override 277 | public void onTrimMemory(int level) { 278 | // Use a trim level delivered while the application is running so the 279 | // framework has a chance to react to the notification. 280 | if (level == TRIM_MEMORY_RUNNING_LOW) { 281 | flutterView.onMemoryPressure(); 282 | } 283 | } 284 | 285 | @Override 286 | public void onLowMemory() { 287 | flutterView.onMemoryPressure(); 288 | } 289 | 290 | @Override 291 | public void onConfigurationChanged(Configuration newConfig) { 292 | } 293 | 294 | private static String[] getArgsFromIntent(Intent intent) { 295 | // Before adding more entries to this list, consider that arbitrary 296 | // Android applications can generate intents with extra data and that 297 | // there are many security-sensitive args in the binary. 298 | ArrayList args = new ArrayList(); 299 | if (intent.getBooleanExtra("trace-startup", false)) { 300 | args.add("--trace-startup"); 301 | } 302 | if (intent.getBooleanExtra("start-paused", false)) { 303 | args.add("--start-paused"); 304 | } 305 | if (intent.getBooleanExtra("use-test-fonts", false)) { 306 | args.add("--use-test-fonts"); 307 | } 308 | if (intent.getBooleanExtra("enable-dart-profiling", false)) { 309 | args.add("--enable-dart-profiling"); 310 | } 311 | if (intent.getBooleanExtra("enable-software-rendering", false)) { 312 | args.add("--enable-software-rendering"); 313 | } 314 | if (intent.getBooleanExtra("skia-deterministic-rendering", false)) { 315 | args.add("--skia-deterministic-rendering"); 316 | } 317 | if (intent.getBooleanExtra("trace-skia", false)) { 318 | args.add("--trace-skia"); 319 | } 320 | if (!args.isEmpty()) { 321 | String[] argsArray = new String[args.size()]; 322 | return args.toArray(argsArray); 323 | } 324 | return null; 325 | } 326 | 327 | private boolean loadIntent(Intent intent) { 328 | final boolean reuseIsolate = false; 329 | return loadIntent(intent, reuseIsolate); 330 | } 331 | 332 | private boolean loadIntent(Intent intent, boolean reuseIsolate) { 333 | String action = intent.getAction(); 334 | if (Intent.ACTION_RUN.equals(action)) { 335 | String route = intent.getStringExtra("route"); 336 | String appBundlePath = intent.getDataString(); 337 | if (appBundlePath == null) { 338 | // Fall back to the installation path if no bundle path 339 | // was specified. 340 | appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext()); 341 | } 342 | if (route != null) { 343 | flutterView.setInitialRoute(route); 344 | } 345 | flutterView.runFromBundle(appBundlePath, intent.getStringExtra("snapshot"), "main", reuseIsolate); 346 | return true; 347 | } 348 | 349 | return false; 350 | } 351 | 352 | /** 353 | * Creates a {@link View} containing the same {@link Drawable} as the one set as the 354 | * {@code windowBackground} of the parent activity for use as a launch splash view. 355 | * 356 | * Returns null if no {@code windowBackground} is set for the activity. 357 | */ 358 | private View createLaunchView() { 359 | if (!showSplashScreenUntilFirstFrame()) { 360 | return null; 361 | } 362 | final Drawable launchScreenDrawable = getLaunchScreenDrawableFromActivityTheme(); 363 | if (launchScreenDrawable == null) { 364 | return null; 365 | } 366 | final View view = new View(activity); 367 | view.setLayoutParams(matchParent); 368 | view.setBackground(launchScreenDrawable); 369 | return view; 370 | } 371 | 372 | /** 373 | * Extracts a {@link Drawable} from the parent activity's {@code windowBackground}. 374 | * 375 | * {@code android:windowBackground} is specifically reused instead of a other attributes 376 | * because the Android framework can display it fast enough when launching the app as opposed 377 | * to anything defined in the Activity subclass. 378 | * 379 | * Returns null if no {@code windowBackground} is set for the activity. 380 | */ 381 | @SuppressWarnings("deprecation") 382 | private Drawable getLaunchScreenDrawableFromActivityTheme() { 383 | TypedValue typedValue = new TypedValue(); 384 | if (!activity.getTheme().resolveAttribute( 385 | android.R.attr.windowBackground, 386 | typedValue, 387 | true)) {; 388 | return null; 389 | } 390 | if (typedValue.resourceId == 0) { 391 | return null; 392 | } 393 | try { 394 | return activity.getResources().getDrawable(typedValue.resourceId); 395 | } catch (NotFoundException e) { 396 | Log.e(TAG, "Referenced launch screen windowBackground resource does not exist"); 397 | return null; 398 | } 399 | } 400 | 401 | /** 402 | * Let the user specify whether the activity's {@code windowBackground} is a launch screen 403 | * and should be shown until the first frame via a tag in the activity. 404 | */ 405 | private Boolean showSplashScreenUntilFirstFrame() { 406 | try { 407 | ActivityInfo activityInfo = activity.getPackageManager().getActivityInfo( 408 | activity.getComponentName(), 409 | PackageManager.GET_META_DATA|PackageManager.GET_ACTIVITIES); 410 | Bundle metadata = activityInfo.metaData; 411 | return metadata != null && metadata.getBoolean(SPLASH_SCREEN_META_DATA_KEY); 412 | } catch (NameNotFoundException e) { 413 | return false; 414 | } 415 | } 416 | 417 | /** 418 | * Show and then automatically animate out the launch view. 419 | * 420 | * If a launch screen is defined in the user application's AndroidManifest.xml as the 421 | * activity's {@code windowBackground}, display it on top of the {@link FlutterView} and 422 | * remove the activity's {@code windowBackground}. 423 | * 424 | * Fade it out and remove it when the {@link FlutterView} renders its first frame. 425 | */ 426 | private void addLaunchView() { 427 | if (launchView == null) { 428 | return; 429 | } 430 | 431 | activity.addContentView(launchView, matchParent); 432 | flutterView.addFirstFrameListener(new FlutterView.FirstFrameListener() { 433 | @Override 434 | public void onFirstFrame() { 435 | XFlutterActivityDelegate.this.launchView.animate() 436 | .alpha(0f) 437 | // Use Android's default animation duration. 438 | .setListener(new AnimatorListenerAdapter() { 439 | @Override 440 | public void onAnimationEnd(Animator animation) { 441 | // Views added to an Activity's addContentView is always added to its 442 | // root FrameLayout. 443 | ((ViewGroup) XFlutterActivityDelegate.this.launchView.getParent()) 444 | .removeView(XFlutterActivityDelegate.this.launchView); 445 | XFlutterActivityDelegate.this.launchView = null; 446 | } 447 | }); 448 | 449 | XFlutterActivityDelegate.this.flutterView.removeFirstFrameListener(this); 450 | } 451 | }); 452 | 453 | // Resets the activity theme from the one containing the launch screen in the window 454 | // background to a blank one since the launch screen is now in a view in front of the 455 | // FlutterView. 456 | // 457 | // We can make this configurable if users want it. 458 | activity.setTheme(android.R.style.Theme_Black_NoTitleBar); 459 | } 460 | } -------------------------------------------------------------------------------- /android/src/main/java/com/taobao/hybridstackmanager/XFlutterView.java: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | package com.taobao.hybridstackmanager; 5 | 6 | import android.app.Activity; 7 | import android.content.BroadcastReceiver; 8 | import android.content.Context; 9 | import android.content.IntentFilter; 10 | import android.util.AttributeSet; 11 | 12 | import java.lang.reflect.Field; 13 | import java.util.List; 14 | 15 | import io.flutter.plugin.common.ActivityLifecycleListener; 16 | import io.flutter.plugin.common.JSONMethodCodec; 17 | import io.flutter.plugin.common.MethodChannel; 18 | import io.flutter.plugin.platform.PlatformPlugin; 19 | import io.flutter.view.FlutterNativeView; 20 | import io.flutter.view.FlutterView; 21 | 22 | /** 23 | * An Android view containing a Flutter app. 24 | */ 25 | public class XFlutterView extends FlutterView 26 | { 27 | public XFlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView){ 28 | super(context,attrs,nativeView); 29 | } 30 | public void registerReceiver() { 31 | try { 32 | //Register Receiver 33 | Field privateField0 = FlutterView.class.getDeclaredField("mDiscoveryReceiver"); 34 | privateField0.setAccessible(true); 35 | BroadcastReceiver mDiscoveryReceiver = (BroadcastReceiver) privateField0.get(this); 36 | if (mDiscoveryReceiver != null) { 37 | if ((getContext().getApplicationInfo().flags & 2) != 0 && mDiscoveryReceiver != null) { 38 | getContext().registerReceiver(mDiscoveryReceiver, new IntentFilter("io.flutter.view.DISCOVER")); 39 | } 40 | } 41 | } catch (NoSuchFieldException e) { 42 | e.printStackTrace(); 43 | } 44 | catch (IllegalAccessException e) { 45 | e.printStackTrace(); 46 | } 47 | } 48 | public void unregisterReceiver(){ 49 | try { 50 | //UnRegister Receiver 51 | Field privateField0 = FlutterView.class.getDeclaredField("mDiscoveryReceiver"); 52 | privateField0.setAccessible(true); 53 | BroadcastReceiver mDiscoveryReceiver = (BroadcastReceiver)privateField0.get(this); 54 | if (mDiscoveryReceiver != null) { 55 | this.getContext().unregisterReceiver(mDiscoveryReceiver); 56 | } 57 | } catch (NoSuchFieldException e) { 58 | e.printStackTrace(); 59 | } 60 | catch (IllegalAccessException e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | public void resetActivity(Activity activity){ 65 | try { 66 | Field privateField1 = FlutterView.class.getDeclaredField("mNativeView"); 67 | privateField1.setAccessible(true); 68 | FlutterNativeView mNativeView = (FlutterNativeView)privateField1.get(this); 69 | try { 70 | mNativeView.attachViewAndActivity(this, activity); 71 | } 72 | catch (AssertionError ae){ 73 | System.out.println("In new implementation for FlutterPluginRegistry,AssertionError is thrown when try to attach twice, it doesn't matter even we ignore it."); 74 | } 75 | 76 | Field privateField2 = FlutterView.class.getDeclaredField("mActivityLifecycleListeners"); 77 | privateField2.setAccessible(true); 78 | List mActivityLifecycleListener = (List)privateField2.get(this); 79 | mActivityLifecycleListener.clear(); 80 | PlatformPlugin platformPlugin = new PlatformPlugin(activity); 81 | MethodChannel flutterPlatformChannel = new MethodChannel(this, "flutter/platform", JSONMethodCodec.INSTANCE); 82 | flutterPlatformChannel.setMethodCallHandler(platformPlugin); 83 | addActivityLifecycleListener(platformPlugin); 84 | } catch (NoSuchFieldException e) { 85 | e.printStackTrace(); 86 | } 87 | catch (IllegalAccessException e) { 88 | e.printStackTrace(); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /android/src/main/java/com/taobao/hybridstackmanager/XURLRouter.java: -------------------------------------------------------------------------------- 1 | package com.taobao.hybridstackmanager; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | import java.util.HashMap; 7 | 8 | public class XURLRouter { 9 | final static String kOpenUrlPrefix = "hrd"; 10 | static XURLRouter sRouterInst; 11 | Context mAppContext; 12 | XURLRouterHandler mNativeRouterHandler; 13 | public static XURLRouter sharedInstance(){ 14 | if(sRouterInst==null){ 15 | sRouterInst = new XURLRouter(); 16 | } 17 | return sRouterInst; 18 | } 19 | public void setAppContext(Context context){ 20 | mAppContext = context; 21 | } 22 | public void setNativeRouterHandler(XURLRouterHandler handler){ 23 | mNativeRouterHandler = handler; 24 | } 25 | public boolean openUrlWithQueryAndParams(String url, HashMap query, HashMap params){ 26 | Uri tmpUri = Uri.parse(url); 27 | if(!kOpenUrlPrefix.equals(tmpUri.getScheme())) 28 | return false; 29 | if(query!=null && query.containsKey("flutter") && (Boolean) query.get("flutter")){ 30 | Intent intent = new Intent(mAppContext,FlutterWrapperActivity.class); 31 | intent.setData(Uri.parse(url)); 32 | intent.setAction(Intent.ACTION_VIEW); 33 | mAppContext.startActivity(intent); 34 | return true; 35 | } 36 | if(mNativeRouterHandler!=null) { 37 | Class activityCls = mNativeRouterHandler.openUrlWithQueryAndParams(url, query, params); 38 | Intent intent = new Intent(mAppContext,activityCls); 39 | intent.setData(Uri.parse(url)); 40 | intent.setAction(Intent.ACTION_VIEW); 41 | mAppContext.startActivity(intent); 42 | } 43 | return false; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /android/src/main/java/com/taobao/hybridstackmanager/XURLRouterHandler.java: -------------------------------------------------------------------------------- 1 | package com.taobao.hybridstackmanager; 2 | 3 | import java.util.HashMap; 4 | 5 | public interface XURLRouterHandler { 6 | public Class openUrlWithQueryAndParams(String url, HashMap query, HashMap params); 7 | } 8 | -------------------------------------------------------------------------------- /android/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/src/main/res/layout/flutter_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 14 | 19 | 20 | 25 | -------------------------------------------------------------------------------- /android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /android/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | 9 | .flutter-plugins 10 | -------------------------------------------------------------------------------- /example/.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /example/.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/.idea/libraries/Flutter_Plugins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/.idea/libraries/Flutter_for_Android.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /example/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | -------------------------------------------------------------------------------- /example/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /example/.idea/runConfigurations/main_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: c7ea3ca377e909469c68f2ab878a5bc53d3cf66b 8 | channel: beta 9 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # hybrid_stack_manager_example 2 | 3 | Demonstrates how to use the hybrid_stack_manager plugin. 4 | 5 | ## Getting Started 6 | 7 | For help getting started with Flutter, view our online 8 | [documentation](https://flutter.io/). 9 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.class 3 | .gradle 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | GeneratedPluginRegistrant.java 11 | -------------------------------------------------------------------------------- /example/android/.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/android/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /example/android/.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /example/android/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /example/android/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /example/android/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | apply plugin: 'com.android.application' 15 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 16 | 17 | android { 18 | compileSdkVersion 27 19 | 20 | lintOptions { 21 | disable 'InvalidPackage' 22 | } 23 | 24 | defaultConfig { 25 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 26 | applicationId "com.example.hybridstackmanagerexample" 27 | minSdkVersion 16 28 | targetSdkVersion 27 29 | versionCode 1 30 | versionName "1.0" 31 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 32 | } 33 | 34 | buildTypes { 35 | release { 36 | // TODO: Add your own signing config for the release build. 37 | // Signing with the debug keys for now, so `flutter run --release` works. 38 | signingConfig signingConfigs.debug 39 | } 40 | } 41 | } 42 | 43 | flutter { 44 | source '../..' 45 | } 46 | 47 | dependencies { 48 | testImplementation 'junit:junit:4.12' 49 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 50 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 51 | } 52 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 15 | 19 | 25 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/hybridstackmanagerexample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.hybridstackmanagerexample; 2 | 3 | import android.app.Activity; 4 | import android.net.Uri; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | import android.widget.Button; 8 | import android.widget.LinearLayout; 9 | import com.taobao.hybridstackmanager.*; 10 | 11 | import java.util.HashMap; 12 | 13 | public class MainActivity extends Activity implements XURLRouterHandler { 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | XURLRouter.sharedInstance().setAppContext(getApplicationContext()); 18 | setContentView(R.layout.placeholder); 19 | setTitle("Native Root Page"); 20 | setupOperationBtns(); 21 | setupNativeOpenUrlHandler(); 22 | } 23 | 24 | void setupOperationBtns(){ 25 | LinearLayout layout = findViewById(R.id.native_root); 26 | final Button btn=new Button(this); 27 | btn.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); 28 | btn.setText("Click to jump Flutter"); 29 | btn.setOnClickListener(new View.OnClickListener() { 30 | @Override 31 | public void onClick(View v) { 32 | HashMap m = new HashMap(); 33 | m.put("flutter",true); 34 | XURLRouter.sharedInstance().openUrlWithQueryAndParams("hrd://fdemo",m,null); 35 | 36 | } 37 | }); 38 | layout.addView(btn); 39 | } 40 | void setupNativeOpenUrlHandler(){ 41 | XURLRouter.sharedInstance().setNativeRouterHandler(this); 42 | } 43 | public Class openUrlWithQueryAndParams(String url, HashMap query, HashMap params){ 44 | Uri tmpUri = Uri.parse(url); 45 | if("ndemo".equals(tmpUri.getHost())){ 46 | return XDemoActivity.class; 47 | } 48 | return null; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/hybridstackmanagerexample/XDemoActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.hybridstackmanagerexample; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.Button; 7 | import android.widget.LinearLayout; 8 | 9 | import com.taobao.hybridstackmanager.XURLRouter; 10 | 11 | import java.util.HashMap; 12 | 13 | public class XDemoActivity extends Activity{ 14 | static int sNativeActivityIdx = 0; 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.placeholder); 18 | setupOperationBtns(); 19 | sNativeActivityIdx++; 20 | setTitle(String.format("Native Demo Page(%d)",sNativeActivityIdx)); 21 | } 22 | 23 | void setupOperationBtns(){ 24 | LinearLayout layout = findViewById(R.id.native_root); 25 | Button btn=new Button(this); 26 | btn.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); 27 | btn.setText("Click to jump Flutter"); 28 | btn.setOnClickListener(new View.OnClickListener() { 29 | @Override 30 | public void onClick(View v) { 31 | HashMap m = new HashMap(); 32 | m.put("flutter",true); 33 | XURLRouter.sharedInstance().openUrlWithQueryAndParams("hrd://fdemo",m,null); 34 | 35 | } 36 | }); 37 | layout.addView(btn); 38 | 39 | btn=new Button(this); 40 | btn.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); 41 | btn.setText("Click to jump native"); 42 | btn.setOnClickListener(new View.OnClickListener() { 43 | @Override 44 | public void onClick(View v) { 45 | XURLRouter.sharedInstance().openUrlWithQueryAndParams("hrd://ndemo",null,null); 46 | 47 | } 48 | }); 49 | layout.addView(btn); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/layout/placeholder.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.0.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.injected.testOnly=false -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /example/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/hybrid_stack_manager_example.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/hybrid_stack_manager_example_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/app.flx 37 | /Flutter/app.zip 38 | /Flutter/flutter_assets/ 39 | /Flutter/App.framework 40 | /Flutter/Flutter.framework 41 | /Flutter/Generated.xcconfig 42 | /ServiceDefinitions.json 43 | 44 | Pods/ 45 | .symlinks/ 46 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | def parse_KV_file(file, separator='=') 8 | file_abs_path = File.expand_path(file) 9 | if !File.exists? file_abs_path 10 | return []; 11 | end 12 | pods_ary = [] 13 | skip_line_start_symbols = ["#", "/"] 14 | File.foreach(file_abs_path) { |line| 15 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 16 | plugin = line.split(pattern=separator) 17 | if plugin.length == 2 18 | podname = plugin[0].strip() 19 | path = plugin[1].strip() 20 | podpath = File.expand_path("#{path}", file_abs_path) 21 | pods_ary.push({:name => podname, :path => podpath}); 22 | else 23 | puts "Invalid plugin specification: #{line}" 24 | end 25 | } 26 | return pods_ary 27 | end 28 | 29 | target 'Runner' do 30 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 31 | # referring to absolute paths on developers' machines. 32 | system('rm -rf .symlinks') 33 | system('mkdir -p .symlinks/plugins') 34 | 35 | # Flutter Pods 36 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 37 | if generated_xcode_build_settings.empty? 38 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 39 | end 40 | generated_xcode_build_settings.map { |p| 41 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 42 | symlink = File.join('.symlinks', 'flutter') 43 | File.symlink(File.dirname(p[:path]), symlink) 44 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 45 | end 46 | } 47 | 48 | # Plugin Pods 49 | plugin_pods = parse_KV_file('../.flutter-plugins') 50 | plugin_pods.map { |p| 51 | symlink = File.join('.symlinks', 'plugins', p[:name]) 52 | File.symlink(p[:path], symlink) 53 | pod p[:name], :path => File.join(symlink, 'ios') 54 | } 55 | end 56 | 57 | post_install do |installer| 58 | installer.pods_project.targets.each do |target| 59 | target.build_configurations.each do |config| 60 | config.build_settings['ENABLE_BITCODE'] = 'NO' 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | #import "XRootController.h" 4 | #import 5 | #import "XDemoController.h" 6 | 7 | @interface AppDelegate(UIGestureRecognizerDelegate) 8 | @end 9 | 10 | @implementation AppDelegate 11 | - (BOOL)application:(UIApplication *)application 12 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 13 | // Override point for customization after application launch. 14 | UINavigationController *rootNav = [[UINavigationController alloc] initWithRootViewController:[XRootController new]]; 15 | rootNav.interactivePopGestureRecognizer.delegate = self; 16 | UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 17 | window.rootViewController = rootNav; 18 | [window makeKeyAndVisible]; 19 | self.window = window; 20 | [self setupNativeOpenUrlHandler]; 21 | return YES; 22 | } 23 | 24 | - (void)setupNativeOpenUrlHandler{ 25 | [[XURLRouter sharedInstance] setNativeOpenUrlHandler:^UIViewController *(NSString *url,NSDictionary *query,NSDictionary *params){ 26 | NSURL *tmpUrl = [NSURL URLWithString:url]; 27 | if([@"ndemo" isEqualToString:tmpUrl.host]){ 28 | return [XDemoController new]; 29 | } 30 | return nil; 31 | }]; 32 | } 33 | 34 | #pragma mark - UIGestureRecognizerDelegate 35 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{ 36 | return TRUE; 37 | } 38 | @end 39 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | hybrid_stack_manager_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UISupportedInterfaceOrientations 28 | 29 | UIInterfaceOrientationPortrait 30 | UIInterfaceOrientationLandscapeLeft 31 | UIInterfaceOrientationLandscapeRight 32 | 33 | UISupportedInterfaceOrientations~ipad 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationPortraitUpsideDown 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationLandscapeRight 39 | 40 | UIViewControllerBasedStatusBarAppearance 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /example/ios/Runner/XDemoController.h: -------------------------------------------------------------------------------- 1 | // 2 | // XDemoController.h 3 | // Runner 4 | // 5 | // Created by KyleWong on 2018/8/13. 6 | // Copyright © 2018 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface XDemoController : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /example/ios/Runner/XDemoController.m: -------------------------------------------------------------------------------- 1 | // 2 | // XDemoController.m 3 | // Runner 4 | // 5 | // Created by KyleWong on 2018/8/13. 6 | // Copyright © 2018 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "XDemoController.h" 10 | #import 11 | 12 | static NSInteger sNativeVCIdx = 0; 13 | 14 | @interface XDemoController () 15 | @end 16 | 17 | @implementation XDemoController 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | sNativeVCIdx++; 22 | NSString *title = [NSString stringWithFormat:@"Native demo page(%ld)",(long)sNativeVCIdx]; 23 | self.title = title; 24 | // Do any additional setup after loading the view. 25 | } 26 | 27 | - (void)viewWillAppear:(BOOL)animated{ 28 | [super viewWillAppear:animated]; 29 | [self.navigationController setNavigationBarHidden:NO animated:NO]; 30 | } 31 | 32 | - (void)loadView{ 33 | UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds]; 34 | [view setBackgroundColor:[UIColor whiteColor]]; 35 | UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 200, 40)]; 36 | [btn setTitle:@"Click to jump Native" forState:UIControlStateNormal]; 37 | [view addSubview:btn]; 38 | [btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 39 | [btn setCenter:CGPointMake(view.center.x, view.center.y-50)]; 40 | [btn addTarget:self action:@selector(onJumpNativePressed) forControlEvents:UIControlEventTouchUpInside]; 41 | 42 | btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 200, 40)]; 43 | [btn setTitle:@"Click to jump Flutter" forState:UIControlStateNormal]; 44 | [view addSubview:btn]; 45 | [btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 46 | [btn setCenter:CGPointMake(view.center.x, view.center.y+50)]; 47 | [btn addTarget:self action:@selector(onJumpFlutterPressed) forControlEvents:UIControlEventTouchUpInside]; 48 | 49 | self.view = view; 50 | } 51 | 52 | - (void)onJumpNativePressed{ 53 | XOpenURLWithQueryAndParams(@"hrd://ndemo", nil, nil); 54 | } 55 | 56 | - (void)onJumpFlutterPressed{ 57 | XOpenURLWithQueryAndParams(@"hrd://fdemo", @{@"flutter":@(true)}, nil); 58 | } 59 | @end 60 | -------------------------------------------------------------------------------- /example/ios/Runner/XRootController.h: -------------------------------------------------------------------------------- 1 | // 2 | // XRootController.h 3 | // Runner 4 | // 5 | // Created by KyleWong on 2018/8/13. 6 | // Copyright © 2018 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface XRootController : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /example/ios/Runner/XRootController.m: -------------------------------------------------------------------------------- 1 | // 2 | // XRootController.m 3 | // Runner 4 | // 5 | // Created by KyleWong on 2018/8/13. 6 | // Copyright © 2018 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "XRootController.h" 10 | #import 11 | 12 | @interface XRootController () 13 | 14 | @end 15 | 16 | @implementation XRootController 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | self.title = @"Native root page"; 21 | // Do any additional setup after loading the view. 22 | } 23 | 24 | - (void)loadView{ 25 | UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds]; 26 | [view setBackgroundColor:[UIColor whiteColor]]; 27 | UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 200, 40)]; 28 | [btn setTitle:@"Click to jump Flutter" forState:UIControlStateNormal]; 29 | [view addSubview:btn]; 30 | [btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 31 | [btn setCenter:view.center]; 32 | [btn addTarget:self action:@selector(onJumpFlutterPressed) forControlEvents:UIControlEventTouchUpInside]; 33 | self.view = view; 34 | } 35 | 36 | - (void)onJumpFlutterPressed{ 37 | XOpenURLWithQueryAndParams(@"hrd://fdemo", @{@"flutter":@(true)}, nil); 38 | } 39 | @end 40 | -------------------------------------------------------------------------------- /example/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/lib/app_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hybrid_stack_manager/hybrid_stack_manager_plugin.dart'; 3 | 4 | import 'fdemo.dart'; 5 | 6 | class AppConfig { 7 | static final AppConfig _singleton = new AppConfig._internal(); 8 | static final GlobalKey gHomeItemPageWidgetKey = 9 | new GlobalKey(debugLabel: "[KWLM]"); 10 | static AppConfig sharedInstance() { 11 | Router.sharedInstance().globalKeyForRouter = gHomeItemPageWidgetKey; 12 | Router.sharedInstance().routerWidgetHandler = 13 | ({RouterOption routeOption, Key key}) { 14 | if (routeOption.url == "hrd://fdemo") { 15 | return new FDemoWidget(routeOption, key: key); 16 | } 17 | return null; 18 | }; 19 | return _singleton; 20 | } 21 | 22 | AppConfig._internal() {} 23 | } 24 | -------------------------------------------------------------------------------- /example/lib/fdemo.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hybrid_stack_manager/hybrid_stack_manager_plugin.dart'; 3 | 4 | class FDemoWidget extends StatelessWidget { 5 | RouterOption routeOption; 6 | FDemoWidget(RouterOption option, {Key key}) : super(key: key) { 7 | routeOption = option; 8 | } 9 | Widget build(BuildContext context) { 10 | Map m = Utils.parseUniquePageName(routeOption.userInfo); 11 | return new Scaffold( 12 | appBar: new AppBar( 13 | leading: new GestureDetector( 14 | child: new Icon(Icons.arrow_back), 15 | onTap: () { 16 | HybridStackManagerPlugin.hybridStackManagerPlugin.popCurPage(); 17 | }), 18 | // Here we take the value from the MyHomePage object that was created by 19 | // the App.build method, and use it to set our appbar title. 20 | title: new Text("Flutter Page(${m["id"]})"), 21 | ), 22 | body: new Center( 23 | // Center is a layout widget. It takes a single child and positions it 24 | // in the middle of the parent. 25 | child: new Column( 26 | // Column is also layout widget. It takes a list of children and 27 | // arranges them vertically. By default, it sizes itself to fit its 28 | // children horizontally, and tries to be as tall as its parent. 29 | // 30 | // Invoke "debug paint" (press "p" in the console where you ran 31 | // "flutter run", or select "Toggle Debug Paint" from the Flutter tool 32 | // window in IntelliJ) to see the wireframe for each widget. 33 | // 34 | // Column has various properties to control how it sizes itself and 35 | // how it positions its children. Here we use mainAxisAlignment to 36 | // center the children vertically; the main axis here is the vertical 37 | // axis because Columns are vertical (the cross axis would be 38 | // horizontal). 39 | mainAxisAlignment: MainAxisAlignment.center, 40 | children: [ 41 | new SizedBox(width: 1.0, height: 100.0), 42 | new GestureDetector( 43 | child: new Text("Click to open FlutterPage"), 44 | onTap: () { 45 | HybridStackManagerPlugin.hybridStackManagerPlugin 46 | .openUrlFromNative( 47 | url: "hrd://fdemo", query: {"flutter": true}); 48 | }, 49 | ), 50 | new SizedBox(width: 1.0, height: 100.0), 51 | new GestureDetector( 52 | child: new Text("Click to open NativePage"), 53 | onTap: () { 54 | HybridStackManagerPlugin.hybridStackManagerPlugin 55 | .openUrlFromNative(url: "hrd://ndemo"); 56 | }, 57 | ) 58 | ], 59 | ), 60 | ), 61 | floatingActionButton: 62 | null // This trailing comma makes auto-formatting nicer for build methods. 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hybrid_stack_manager/hybrid_stack_manager_plugin.dart'; 3 | 4 | import 'app_config.dart'; 5 | import 'my_app.dart'; 6 | 7 | void main() async { 8 | AppConfig.sharedInstance(); 9 | HybridStackManagerPlugin plugin = 10 | HybridStackManagerPlugin.hybridStackManagerPlugin; 11 | Map args = await plugin.getMainEntryParams(); 12 | runApp(new MyApp()); 13 | if (args != null && args["url"] != null) { 14 | RouterOption routeOption = new RouterOption( 15 | url: args["url"], query: args["query"], params: args["params"]); 16 | Router.sharedInstance().pushPageWithOptionsFromFlutter( 17 | routeOption: routeOption, animated: false); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/lib/my_app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'app_config.dart'; 4 | 5 | class MyApp extends StatefulWidget { 6 | MyApp(); 7 | State createState() { 8 | return new MyAppState(); 9 | } 10 | } 11 | 12 | class MyAppState extends State { 13 | @override 14 | void initState() { 15 | // TODO: implement initState 16 | super.initState(); 17 | } 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | ThemeData themeData = new ThemeData( 22 | primarySwatch: Colors.blue, 23 | ); 24 | return new MaterialApp( 25 | title: '混合栈Demo', 26 | theme: themeData, 27 | home: new MyHomeWidget(key: AppConfig.gHomeItemPageWidgetKey), 28 | ); 29 | } 30 | } 31 | 32 | class MyHomeWidget extends StatefulWidget { 33 | MyHomeWidget({Key key}) : super(key: key); 34 | 35 | @override 36 | State createState() { 37 | return new MyHomeWidgetState(); 38 | } 39 | } 40 | 41 | class MyHomeWidgetState extends State { 42 | @override 43 | Widget build(BuildContext context) { 44 | return new Container( 45 | color: Colors.white, 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: hybrid_stack_manager_example 2 | description: Demonstrates how to use the hybrid_stack_manager plugin. 3 | 4 | dependencies: 5 | flutter: 6 | sdk: flutter 7 | 8 | # The following adds the Cupertino Icons font to your application. 9 | # Use with the CupertinoIcons class for iOS style icons. 10 | cupertino_icons: ^0.1.2 11 | 12 | dev_dependencies: 13 | flutter_test: 14 | sdk: flutter 15 | 16 | hybrid_stack_manager: 17 | path: ../ 18 | 19 | # For information on the generic Dart part of this file, see the 20 | # following page: https://www.dartlang.org/tools/pub/pubspec 21 | 22 | # The following section is specific to Flutter. 23 | flutter: 24 | 25 | # The following line ensures that the Material Icons font is 26 | # included with your application, so that you can use the icons in 27 | # the material Icons class. 28 | uses-material-design: true 29 | 30 | # To add assets to your application, add an assets section, like this: 31 | # assets: 32 | # - images/a_dot_burr.jpeg 33 | # - images/a_dot_ham.jpeg 34 | 35 | # An image asset can refer to one or more resolution-specific "variants", see 36 | # https://flutter.io/assets-and-images/#resolution-aware. 37 | 38 | # For details regarding adding assets from package dependencies, see 39 | # https://flutter.io/assets-and-images/#from-packages 40 | 41 | # To add custom fonts to your application, add a fonts section here, 42 | # in this "flutter" section. Each entry in this list should have a 43 | # "family" key with the font family name, and a "fonts" key with a 44 | # list giving the asset and other descriptors for the font. For 45 | # example: 46 | # fonts: 47 | # - family: Schyler 48 | # fonts: 49 | # - asset: fonts/Schyler-Regular.ttf 50 | # - asset: fonts/Schyler-Italic.ttf 51 | # style: italic 52 | # - family: Trajan Pro 53 | # fonts: 54 | # - asset: fonts/TrajanPro.ttf 55 | # - asset: fonts/TrajanPro_Bold.ttf 56 | # weight: 700 57 | # 58 | # For details regarding fonts from package dependencies, 59 | # see https://flutter.io/custom-fonts/#from-packages 60 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter 3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to 4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties 5 | // are correct. 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | import 'package:hybrid_stack_manager_example/my_app.dart'; 10 | 11 | void main() { 12 | testWidgets('Verify Platform version', (WidgetTester tester) async { 13 | // Build our app and trigger a frame. 14 | await tester.pumpWidget(new MyApp()); 15 | 16 | // Verify that platform version is retrieved. 17 | expect( 18 | find.byWidgetPredicate( 19 | (Widget widget) => 20 | widget is Text && widget.data.startsWith('Running on:'), 21 | ), 22 | findsOneWidget); 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /flutter_chann_plugin.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /flutter_chann_plugin_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /hybrid_stack_manager.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /hybrid_stack_manager_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | 13 | *.pbxuser 14 | *.mode1v3 15 | *.mode2v3 16 | *.perspectivev3 17 | 18 | !default.pbxuser 19 | !default.mode1v3 20 | !default.mode2v3 21 | !default.perspectivev3 22 | 23 | xcuserdata 24 | 25 | *.moved-aside 26 | 27 | *.pyc 28 | *sync/ 29 | Icon? 30 | .tags* 31 | 32 | -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/FlutterViewWrapperController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlutterViewWrapperController.h 3 | // Runner 4 | // 5 | // Created by 正物 on 08/03/2018. 6 | // Copyright © 2018 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "XFlutterViewController.h" 11 | #import "UIViewController+URLRouter.h" 12 | 13 | typedef void (^FlutterViewWillAppearBlock) (void); 14 | 15 | @interface FlutterViewWrapperController : UIViewController 16 | + (XFlutterViewController *)flutterVC; 17 | @property(nonatomic,copy) NSString *curFlutterRouteName; 18 | @property(nonatomic,copy) FlutterViewWillAppearBlock viewWillAppearBlock; 19 | @end 20 | -------------------------------------------------------------------------------- /ios/Classes/FlutterViewWrapperController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlutterViewWrapperController.m 3 | // Runner 4 | // 5 | // Created by 正物 on 08/03/2018. 6 | // Copyright © 2018 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "FlutterViewWrapperController.h" 10 | #import 11 | #import "HybridStackManager.h" 12 | 13 | typedef NS_ENUM(NSInteger,FlutterVCSwitchCategory){ 14 | FlutterVCSwitchCategoryOK, 15 | }; 16 | 17 | typedef void (^FlutterWrapperHandleBlock)(); 18 | 19 | @interface FlutterViewWrapperController () 20 | @property (nonatomic,strong) UIImageView *fakeSnapImgView; 21 | @property(nonatomic,strong) UIImage *lastSnapshot; 22 | @property(nonatomic,copy) NSString *lastFlutterRouteName; 23 | @end 24 | 25 | @implementation FlutterViewWrapperController 26 | #pragma mark - LifeCycle 27 | - (instancetype)initWithURL:(NSURL *)url query:(NSDictionary *)query nativeParams:(NSDictionary *)nativeParams { 28 | self = [super initWithURL:url query:query nativeParams:nativeParams]; 29 | if (self) { 30 | } 31 | return self; 32 | } 33 | 34 | - (void)loadView{ 35 | UIView *view = [[UIView alloc] init]; 36 | [view setBackgroundColor:[UIColor whiteColor]]; 37 | self.view = view; 38 | } 39 | 40 | - (BOOL)shouldAutomaticallyForwardAppearanceMethods{ 41 | return TRUE; 42 | } 43 | 44 | - (void)viewDidLoad { 45 | [super viewDidLoad]; 46 | // Do any additional setup after loading the view. 47 | self.fakeSnapImgView = [[UIImageView alloc] initWithFrame:self.view.bounds]; 48 | self.fakeSnapImgView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 49 | [self.fakeSnapImgView setBackgroundColor:[UIColor clearColor]]; 50 | [self.view addSubview:self.fakeSnapImgView]; 51 | } 52 | 53 | - (void)didReceiveMemoryWarning{ 54 | [super didReceiveMemoryWarning]; 55 | XFlutterViewController *flutterVC = [FlutterViewWrapperController flutterVC]; 56 | if([[flutterVC parentViewController] isEqual:self]){ 57 | [flutterVC didReceiveMemoryWarning]; 58 | } 59 | } 60 | 61 | -(void)viewWillAppear:(BOOL)animated{ 62 | [super viewWillAppear:animated]; 63 | [self.navigationController setNavigationBarHidden:YES animated:NO]; 64 | if(self.viewWillAppearBlock){ 65 | self.viewWillAppearBlock(); 66 | self.viewWillAppearBlock = nil; 67 | } 68 | if(!self.lastSnapshot){ 69 | dispatch_async(dispatch_get_main_queue(), ^{ 70 | [self addChildFlutterVC]; 71 | }); 72 | } 73 | } 74 | 75 | -(void)viewDidAppear:(BOOL)animated { 76 | [super viewDidAppear:animated]; 77 | [self addChildFlutterVC]; 78 | if(self.curFlutterRouteName.length && self.lastSnapshot){ 79 | [[HybridStackManager sharedInstance].methodChannel invokeMethod:@"popToRouteNamed" arguments:self.curFlutterRouteName]; 80 | } 81 | [[FlutterViewWrapperController flutterVC].view setUserInteractionEnabled:TRUE]; 82 | } 83 | 84 | - (void)viewWillDisappear:(BOOL)animated{ 85 | [super viewWillDisappear:animated]; 86 | UINavigationController *rootNav = (UINavigationController*)[UIApplication sharedApplication].delegate.window.rootViewController; 87 | NSArray *curStackAry = rootNav.viewControllers; 88 | NSInteger idx = [curStackAry indexOfObject:self]; 89 | if(idx != NSNotFound && idx != curStackAry.count-1){ 90 | [self saveSnapshot]; 91 | } 92 | [[FlutterViewWrapperController flutterVC].view setUserInteractionEnabled:FALSE]; 93 | } 94 | 95 | - (void)viewDidDisappear:(BOOL)animated { 96 | [super viewDidDisappear:animated]; 97 | UINavigationController *rootNav = (UINavigationController*)[UIApplication sharedApplication].delegate.window.rootViewController; 98 | NSArray *ary = [rootNav.viewControllers filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { 99 | if([evaluatedObject isKindOfClass:[FlutterViewWrapperController class]]) 100 | return TRUE; 101 | return FALSE; 102 | }]]; 103 | if(!ary.count){ 104 | [[HybridStackManager sharedInstance].methodChannel invokeMethod:@"popToRoot" arguments:nil]; 105 | } 106 | 107 | NSArray *curStackAry = rootNav.viewControllers; 108 | NSInteger idx = [curStackAry indexOfObject:self]; 109 | if(idx == NSNotFound){ 110 | [[HybridStackManager sharedInstance].methodChannel invokeMethod:@"popRouteNamed" arguments:self.lastFlutterRouteName]; 111 | } 112 | } 113 | #pragma mark - Child/Parent VC 114 | - (void)showFlutterViewOverSnapshot{ 115 | XFlutterViewController *flutterVC = [FlutterViewWrapperController flutterVC]; 116 | BOOL priorIsMyChild = (flutterVC.parentViewController == self); 117 | if(self.lastSnapshot){ 118 | [self.view bringSubviewToFront:self.fakeSnapImgView]; 119 | } 120 | flutterVC.view.frame = self.view.bounds; 121 | flutterVC.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 122 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 123 | [self.view bringSubviewToFront:flutterVC.view]; 124 | self.lastSnapshot = nil; 125 | }); 126 | } 127 | 128 | - (void)addChildFlutterVC{ 129 | XFlutterViewController *flutterVC = [FlutterViewWrapperController flutterVC]; 130 | if(self == flutterVC.parentViewController){ 131 | [self showFlutterViewOverSnapshot]; 132 | return; 133 | } 134 | if( nil != flutterVC.parentViewController){ 135 | [self removeChildFlutterVC]; 136 | } 137 | [self.view addSubview:flutterVC.view]; 138 | [self addChildViewController:flutterVC]; 139 | [self showFlutterViewOverSnapshot]; 140 | } 141 | 142 | - (void)removeChildFlutterVC{ 143 | XFlutterViewController *flutterVC = [FlutterViewWrapperController flutterVC]; 144 | //Remove VC 145 | [flutterVC removeFromParentViewController]; 146 | [flutterVC.view removeFromSuperview]; 147 | } 148 | 149 | - (void)saveSnapshot{ 150 | XFlutterViewController *flutterVC = [FlutterViewWrapperController flutterVC]; 151 | if(flutterVC.parentViewController != self) 152 | return; 153 | if(self.lastSnapshot == nil){ 154 | UIGraphicsBeginImageContextWithOptions([UIScreen mainScreen].bounds.size, YES, 0); 155 | [flutterVC.view drawViewHierarchyInRect:flutterVC.view.bounds afterScreenUpdates:NO]; 156 | self.lastSnapshot = UIGraphicsGetImageFromCurrentImageContext(); 157 | UIGraphicsEndImageContext(); 158 | [self.fakeSnapImgView setImage:self.lastSnapshot]; 159 | [self.view bringSubviewToFront:self.fakeSnapImgView]; 160 | } 161 | } 162 | 163 | + (XFlutterViewController *)flutterVC{ 164 | static dispatch_once_t onceToken; 165 | static XFlutterViewController *sxFlutterVC; 166 | if(sxFlutterVC) 167 | return sxFlutterVC; 168 | dispatch_once(&onceToken, ^{ 169 | sxFlutterVC = [[XFlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil]; 170 | }); 171 | return sxFlutterVC; 172 | } 173 | @end 174 | -------------------------------------------------------------------------------- /ios/Classes/HybridStackManager.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface HybridStackManager : NSObject 4 | + (instancetype)sharedInstance; 5 | @property (nonatomic,strong) FlutterMethodChannel* methodChannel; 6 | @property (nonatomic,strong) NSDictionary* mainEntryParams; 7 | @end 8 | 9 | -------------------------------------------------------------------------------- /ios/Classes/HybridStackManager.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "HybridStackManager.h" 3 | #import "FlutterViewWrapperController.h" 4 | #import "XURLRouter.h" 5 | 6 | @interface HybridStackManager() 7 | @property (nonatomic,strong) NSObject* registrar; 8 | @end 9 | 10 | @implementation HybridStackManager 11 | + (instancetype)sharedInstance{ 12 | static HybridStackManager * sharedInst; 13 | static dispatch_once_t onceToken; 14 | dispatch_once(&onceToken, ^{ 15 | sharedInst = [[HybridStackManager alloc] init]; 16 | }); 17 | return sharedInst; 18 | } 19 | 20 | + (void)registerWithRegistrar:(NSObject*)registrar{ 21 | HybridStackManager* instance = [HybridStackManager sharedInstance]; 22 | instance.methodChannel = [FlutterMethodChannel 23 | methodChannelWithName:@"hybrid_stack_manager" 24 | binaryMessenger:[registrar messenger]]; 25 | [registrar addMethodCallDelegate:instance channel:instance.methodChannel]; 26 | instance.registrar = registrar; 27 | } 28 | 29 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { 30 | if ([@"openUrlFromNative" isEqualToString:call.method]) { 31 | NSDictionary *openUrlInfo = call.arguments; 32 | XOpenURLWithQueryAndParams(openUrlInfo[@"url"], openUrlInfo[@"query"],openUrlInfo[@"params"]); 33 | } 34 | else if([@"getMainEntryParams" isEqualToString:call.method]){ 35 | NSDictionary *params = self.mainEntryParams?:@{}; 36 | if([[[UIDevice currentDevice] systemVersion] compare:@"9.0" options:NSNumericSearch] == NSOrderedAscending){ 37 | NSMutableDictionary *mutDict = [NSMutableDictionary dictionary]; 38 | NSMutableDictionary *mutParams = [NSMutableDictionary dictionaryWithDictionary:params]; 39 | params=mutParams; 40 | } 41 | result(params); 42 | // self.mainEntryParams = nil; 43 | } 44 | else if([@"updateCurFlutterRoute" isEqualToString:call.method]){ 45 | NSString *curRouteName = call.arguments; 46 | UINavigationController *rootNav = (UINavigationController*)[UIApplication sharedApplication].delegate.window.rootViewController; 47 | UIViewController *topVC = rootNav.topViewController; 48 | if([topVC isKindOfClass:[FlutterViewWrapperController class]]){ 49 | FlutterViewWrapperController *flutterVC = topVC; 50 | [flutterVC setCurFlutterRouteName:curRouteName]; 51 | } 52 | } 53 | else if([@"popCurPage" isEqualToString:call.method]){ 54 | BOOL animated = YES; 55 | if (call.arguments && [call.arguments isKindOfClass:[NSNumber class]]) { 56 | animated = [(NSNumber *)call.arguments boolValue]; 57 | } 58 | UINavigationController *nav = (UINavigationController*)[UIApplication sharedApplication].delegate.window.rootViewController; 59 | if([nav.topViewController isKindOfClass:[FlutterViewWrapperController class]]){ 60 | [nav popViewControllerAnimated:animated]; 61 | } 62 | } 63 | else { 64 | result(FlutterMethodNotImplemented); 65 | } 66 | } 67 | @end 68 | -------------------------------------------------------------------------------- /ios/Classes/UIViewController+URLRouter.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+URLRouter.h 3 | // hybrid_stack_manager 4 | // 5 | // Created by KyleWong on 2018/8/13. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface UIViewController (URLRouter) 13 | - (instancetype)initWithURL:(NSURL *)url query:(NSDictionary *)query nativeParams:(NSDictionary *)nativeParams; 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /ios/Classes/UIViewController+URLRouter.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+URLRouter.m 3 | // hybrid_stack_manager 4 | // 5 | // Created by KyleWong on 2018/8/13. 6 | // 7 | 8 | #import "UIViewController+URLRouter.h" 9 | 10 | @implementation UIViewController (URLRouter) 11 | - (instancetype)initWithURL:(NSURL *)url query:(NSDictionary *)query nativeParams:(NSDictionary *)nativeParams{ 12 | if(self = [super init]){ 13 | 14 | } 15 | return self; 16 | } 17 | @end 18 | -------------------------------------------------------------------------------- /ios/Classes/XFlutterModule.h: -------------------------------------------------------------------------------- 1 | // 2 | // XFlutterModule.h 3 | // FleaMarket 4 | // 5 | // Created by 正物 on 2018/03/08. 6 | // Copyright © 2017 正物. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "FlutterViewWrapperController.h" 12 | 13 | @interface XFlutterModule : NSObject 14 | + (instancetype)new __attribute__((unavailable("Must use sharedInstance instead."))); 15 | - (instancetype)init __attribute__((unavailable("Must use sharedInstance instead."))); 16 | + (instancetype)sharedInstance; 17 | @property (nonatomic,assign) BOOL isInFlutterRootPage; 18 | @property (nonatomic,strong) XFlutterViewController *flutterVC; 19 | - (void)openURL:(NSString *)aUrl query:(NSDictionary *)query params:(NSDictionary *)params; 20 | - (void)warmupFlutter; 21 | @end 22 | -------------------------------------------------------------------------------- /ios/Classes/XFlutterModule.m: -------------------------------------------------------------------------------- 1 | // 2 | // XFlutterModule.m 3 | // FleaMarket 4 | // 5 | // Created by 正物 on 2018/03/08. 6 | // Copyright © 2017 正物. All rights reserved. 7 | // 8 | 9 | #import "XFlutterModule.h" 10 | #import "HybridStackManager.h" 11 | #import 12 | 13 | @interface XFlutterModule() 14 | { 15 | BOOL _isInFlutterRootPage; 16 | bool _isFlutterWarmedup; 17 | } 18 | @end 19 | 20 | @implementation XFlutterModule 21 | @synthesize isInFlutterRootPage = _isInFlutterRootPage; 22 | #pragma mark - XModuleProtocol 23 | + (instancetype)sharedInstance{ 24 | static XFlutterModule *sXFlutterModule; 25 | if(sXFlutterModule) 26 | return sXFlutterModule; 27 | static dispatch_once_t onceToken; 28 | dispatch_once(&onceToken, ^{ 29 | sXFlutterModule = [[[self class] alloc] initInstance]; 30 | [sXFlutterModule warmupFlutter]; 31 | }); 32 | return sXFlutterModule; 33 | } 34 | 35 | - (instancetype)initInstance{ 36 | if(self = [super init]){ 37 | _isInFlutterRootPage = TRUE; 38 | } 39 | return self; 40 | } 41 | 42 | - (XFlutterViewController *)flutterVC{ 43 | return [FlutterViewWrapperController flutterVC]; 44 | } 45 | 46 | - (void)warmupFlutter{ 47 | if(_isFlutterWarmedup) 48 | return; 49 | XFlutterViewController *flutterVC = [FlutterViewWrapperController flutterVC]; 50 | [flutterVC view]; 51 | [NSClassFromString(@"GeneratedPluginRegistrant") performSelector:NSSelectorFromString(@"registerWithRegistry:") withObject:flutterVC]; 52 | _isFlutterWarmedup = true; 53 | } 54 | 55 | + (NSDictionary *)parseParamsKV:(NSString *)aParamsStr{ 56 | NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 57 | NSArray *kvAry = [aParamsStr componentsSeparatedByString:@"&"]; 58 | for(NSString *kv in kvAry){ 59 | NSArray *ary = [kv componentsSeparatedByString:@"="]; 60 | if (ary.count == 2) { 61 | NSString *key = ary.firstObject; 62 | NSString *value = [ary.lastObject stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 63 | [dict setValue:value forKey:key]; 64 | } 65 | } 66 | return dict; 67 | } 68 | 69 | - (void)openURL:(NSString *)aUrl query:(NSDictionary *)query params:(NSDictionary *)params{ 70 | static BOOL sIsFirstPush = TRUE; 71 | //Process aUrl and Query Stuff. 72 | NSURL *url = [NSURL URLWithString:aUrl]; 73 | 74 | NSMutableDictionary *mQuery = [NSMutableDictionary dictionaryWithDictionary:query]; 75 | [mQuery addEntriesFromDictionary:[XFlutterModule parseParamsKV:url.query]]; 76 | NSMutableDictionary *mParams = [NSMutableDictionary dictionaryWithDictionary:params]; 77 | [mParams addEntriesFromDictionary:[XFlutterModule parseParamsKV:url.parameterString]]; 78 | NSString *pageUrl = [NSString stringWithFormat:@"%@://%@",url.scheme,url.host]; 79 | 80 | FlutterMethodChannel *methodChann = [HybridStackManager sharedInstance].methodChannel; 81 | NSMutableDictionary *arguments = [NSMutableDictionary dictionary]; 82 | [arguments setValue:pageUrl forKey:@"url"]; 83 | 84 | NSMutableDictionary *mutQuery = [NSMutableDictionary dictionary]; 85 | for(NSString *key in query.allKeys){ 86 | id value = [query objectForKey:key]; 87 | //[TODO]: Add customized implementations for non-json-serializable objects into json-serializable ones. 88 | [mutQuery setValue:value forKey:key]; 89 | } 90 | [arguments setValue:mutQuery forKey:@"query"]; 91 | 92 | NSMutableDictionary *mutParams = [NSMutableDictionary dictionary]; 93 | for(NSString *key in mParams.allKeys){ 94 | id value = [mParams objectForKey:key]; 95 | //[TODO]: Add customized implementations for non-json-serializable objects into json-serializable ones. 96 | [mutParams setValue:value forKey:key]; 97 | } 98 | [arguments setValue:mutParams forKey:@"params"]; 99 | 100 | [arguments setValue:@(0) forKey:@"animated"]; 101 | 102 | //Push 103 | UINavigationController *currentNavigation = (UINavigationController*)[UIApplication sharedApplication].delegate.window.rootViewController; 104 | FlutterViewWrapperController *viewController = [[FlutterViewWrapperController alloc] initWithURL:[NSURL URLWithString:aUrl] query:query nativeParams:params]; 105 | viewController.viewWillAppearBlock = ^(){ 106 | //Process first & later message sending according distinguishly. 107 | if(sIsFirstPush){ 108 | [HybridStackManager sharedInstance].mainEntryParams = arguments; 109 | sIsFirstPush = FALSE; 110 | } 111 | else{ 112 | [methodChann invokeMethod:@"openURLFromFlutter" arguments:arguments result:^(id _Nullable result) { 113 | }]; 114 | } 115 | }; 116 | [currentNavigation pushViewController:viewController animated:YES]; 117 | } 118 | 119 | #pragma mark - XFlutterModuleProtocol 120 | @end 121 | -------------------------------------------------------------------------------- /ios/Classes/XFlutterViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // XFlutterViewController.h 3 | // flutter_chann_plugin 4 | // 5 | // Created by 正物 on 18/03/2018. 6 | // 7 | 8 | #import 9 | 10 | @interface XFlutterViewController : FlutterViewController 11 | @end 12 | -------------------------------------------------------------------------------- /ios/Classes/XFlutterViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // XFlutterViewController.m 3 | // flutter_chann_plugin 4 | // 5 | // Created by 正物 on 18/03/2018. 6 | // 7 | 8 | #import "XFlutterViewController.h" 9 | 10 | @interface XFlutterViewController () 11 | @property (nonatomic,assign) BOOL enableViewWillAppear; 12 | @end 13 | 14 | @implementation XFlutterViewController 15 | 16 | - (void)viewDidLoad { 17 | [super viewDidLoad]; 18 | self.enableViewWillAppear = TRUE; 19 | // Do any additional setup after loading the view. 20 | } 21 | 22 | - (void)didReceiveMemoryWarning { 23 | [super didReceiveMemoryWarning]; 24 | // Dispose of any resources that can be recreated. 25 | } 26 | 27 | - (void)viewWillAppear:(BOOL)animated{ 28 | if(self.enableViewWillAppear == FALSE) 29 | return; 30 | [super viewWillAppear:animated]; 31 | self.enableViewWillAppear = FALSE; 32 | } 33 | 34 | - (void)viewDidAppear:(BOOL)animated{ 35 | [super viewDidAppear:animated]; 36 | } 37 | 38 | - (void)viewWillDisappear:(BOOL)animated{ 39 | [super viewWillDisappear:animated]; 40 | } 41 | 42 | - (void)viewDidDisappear:(BOOL)animated{ 43 | [super viewDidDisappear:animated]; 44 | self.enableViewWillAppear = TRUE; 45 | } 46 | /* 47 | #pragma mark - Navigation 48 | 49 | // In a storyboard-based application, you will often want to do a little preparation before navigation 50 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 51 | // Get the new view controller using [segue destinationViewController]. 52 | // Pass the selected object to the new view controller. 53 | } 54 | */ 55 | //- (UIEdgeInsets)paddingEdgeInsets{ 56 | // UIEdgeInsets edgeInsets = UIEdgeInsetsZero; 57 | // if (@available(iOS 11, *)) { 58 | // edgeInsets = UIEdgeInsetsMake(0, self.view.safeAreaInsets.left, self.view.safeAreaInsets.bottom, self.view.safeAreaInsets.right); 59 | // } else { 60 | // edgeInsets = UIEdgeInsetsZero; 61 | // } 62 | // return edgeInsets; 63 | //} 64 | @end 65 | 66 | -------------------------------------------------------------------------------- /ios/Classes/XURLRouter.h: -------------------------------------------------------------------------------- 1 | // 2 | // XURLRouter.h 3 | // Runner 4 | // 5 | // Created by KyleWong on 2018/8/13. 6 | // Copyright © 2018 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import 10 | #define kOpenUrlPrefix @"hrd" 11 | 12 | typedef UIViewController* (^NativeOpenUrlHandler)(NSString *,NSDictionary *,NSDictionary *); 13 | void XOpenURLWithQueryAndParams(NSString *url,NSDictionary *query,NSDictionary *params); 14 | 15 | @interface XURLRouter : NSObject 16 | @property (nonatomic,weak) NativeOpenUrlHandler nativeOpenUrlHandler; 17 | + (instancetype)sharedInstance; 18 | @end 19 | -------------------------------------------------------------------------------- /ios/Classes/XURLRouter.m: -------------------------------------------------------------------------------- 1 | // 2 | // XURLRouter.m 3 | // Runner 4 | // 5 | // Created by KyleWong on 2018/8/13. 6 | // Copyright © 2018 The Chromium Authors. All rights reserved. 7 | // 8 | 9 | #import "XURLRouter.h" 10 | #import "XFlutterModule.h" 11 | 12 | @implementation XURLRouter 13 | + (instancetype)sharedInstance{ 14 | static XURLRouter *sInstance; 15 | static dispatch_once_t onceToken; 16 | dispatch_once(&onceToken, ^{ 17 | sInstance = [XURLRouter new]; 18 | }); 19 | return sInstance; 20 | } 21 | @end 22 | 23 | void XOpenURLWithQueryAndParams(NSString *url,NSDictionary *query,NSDictionary *params){ 24 | NSURL *tmpUrl = [NSURL URLWithString:url]; 25 | UINavigationController *rootNav = (UINavigationController*)[UIApplication sharedApplication].delegate.window.rootViewController; 26 | if(![kOpenUrlPrefix isEqualToString:tmpUrl.scheme]) 27 | return; 28 | if([[query objectForKey:@"flutter"] boolValue]){ 29 | [[XFlutterModule sharedInstance] openURL:url query:query params:params]; 30 | return; 31 | } 32 | NativeOpenUrlHandler handler = [XURLRouter sharedInstance].nativeOpenUrlHandler; 33 | if(handler!=nil) 34 | { 35 | UIViewController *vc = handler(url,query,params); 36 | if(vc!=nil) 37 | [rootNav pushViewController:vc animated:YES]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ios/LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/ios/LICENSE -------------------------------------------------------------------------------- /ios/README.md: -------------------------------------------------------------------------------- 1 | ##XFlutterModule 2 | -------------------------------------------------------------------------------- /ios/hybrid_stack_manager.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hybrid_stack_manager", 3 | "version": "0.0.6", 4 | "summary": "hybrid_stack_manager", 5 | "description": "混合栈管理", 6 | "homepage": "https://github.com/FlutterRepo/hybrid_stack_manager.git", 7 | "license": { 8 | "type": "MIT", 9 | "file": "LICENSE" 10 | }, 11 | "authors": { 12 | "正物": "kang.wang1988@gmail.com" 13 | }, 14 | "platforms": { 15 | "ios": "8.0" 16 | }, 17 | "source": { 18 | "git": "git@github.com:FlutterRepo/hybrid_stack_manager.git", 19 | "tag": "0.0.6" 20 | }, 21 | "source_files": [ 22 | "Classes", 23 | "Classes/*.{h,m}", 24 | "Classes/**/*.{h,m}" 25 | ], 26 | "dependency": [ 27 | "Flutter" 28 | ], 29 | "exclude_files": "Classes/Exclude", 30 | "resources": "Resources/*", 31 | "prefix_header_contents": "", 32 | "requires_arc": true 33 | } -------------------------------------------------------------------------------- /lib/hybrid_stack_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/services.dart'; 3 | 4 | typedef Future MethodHandler(MethodCall call); 5 | 6 | class HybridStackManagerPlugin { 7 | static HybridStackManagerPlugin hybridStackManagerPlugin = 8 | new HybridStackManagerPlugin._internal(); 9 | MethodChannel _channel; 10 | MethodHandler _handler; 11 | void setMethodCallHandler(MethodHandler hdler) { 12 | _handler = hdler; 13 | _channel.setMethodCallHandler(_handler); 14 | } 15 | 16 | HybridStackManagerPlugin._internal() { 17 | _channel = new MethodChannel('hybrid_stack_manager'); 18 | } 19 | openUrlFromNative({String url, Map query, Map params, bool animated}) { 20 | _channel.invokeMethod("openUrlFromNative", { 21 | "url": url ?? "", 22 | "query": (query ?? {}), 23 | "params": (params ?? {}), 24 | "animated": animated ?? true 25 | }); 26 | } 27 | 28 | popCurPage({bool animated = true}) { 29 | _channel.invokeMethod("popCurPage", animated); 30 | } 31 | 32 | updateCurFlutterRoute(String curRouteName) { 33 | _channel.invokeMethod("updateCurFlutterRoute", curRouteName ?? ""); 34 | } 35 | 36 | Future getMainEntryParams() async { 37 | dynamic info = await _channel.invokeMethod("getMainEntryParams"); 38 | return new Future.sync(() => info as Map); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/hybrid_stack_manager_plugin.dart: -------------------------------------------------------------------------------- 1 | export 'hybrid_stack_manager.dart'; 2 | export 'router_option.dart'; 3 | export 'router.dart'; 4 | export 'utils.dart'; 5 | -------------------------------------------------------------------------------- /lib/router.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'router_option.dart'; 4 | import 'hybrid_stack_manager.dart'; 5 | import 'utils.dart'; 6 | 7 | typedef Widget FlutterWidgetHandler({RouterOption routeOption, Key key}); 8 | 9 | class XMaterialPageRoute extends MaterialPageRoute { 10 | final WidgetBuilder builder; 11 | final bool animated; 12 | Duration get transitionDuration { 13 | if (animated == true) return const Duration(milliseconds: 300); 14 | return const Duration(milliseconds: 0); 15 | } 16 | 17 | XMaterialPageRoute({ 18 | this.builder, 19 | this.animated, 20 | RouteSettings settings: const RouteSettings(), 21 | }) : super(builder: builder, settings: settings); 22 | } 23 | 24 | class Router extends Object { 25 | static final Router singleton = new Router._internal(); 26 | List flutterRootPageNameLst = new List(); 27 | String currentPageUrl = null; 28 | FlutterWidgetHandler routerWidgetHandler; 29 | GlobalKey globalKeyForRouter; 30 | static Router sharedInstance() { 31 | return singleton; 32 | } 33 | 34 | Router._internal() { 35 | HybridStackManagerPlugin.hybridStackManagerPlugin 36 | .setMethodCallHandler((MethodCall methodCall) { 37 | String method = methodCall.method; 38 | if (method == "openURLFromFlutter") { 39 | Map args = methodCall.arguments; 40 | if (args != null) { 41 | bool animated = (args["animated"] == 1); 42 | Router.sharedInstance().pushPageWithOptionsFromFlutter( 43 | routeOption: new RouterOption( 44 | url: args["url"], 45 | query: args["query"], 46 | params: args["params"]), 47 | animated: animated ?? false); 48 | } 49 | } else if (method == "popToRoot") { 50 | Router.sharedInstance().popToRoot(); 51 | } else if (method == "popToRouteNamed") { 52 | Router.sharedInstance().popToRouteNamed(methodCall.arguments); 53 | } else if (method == "popRouteNamed") { 54 | Router.sharedInstance().popRouteNamed(methodCall.arguments); 55 | } 56 | }); 57 | } 58 | popToRoot() { 59 | NavigatorState navState = Navigator.of(globalKeyForRouter.currentContext); 60 | List> navHistory = navState.history; 61 | int histLen = navHistory.length; 62 | for (int i = histLen - 1; i >= 1; i--) { 63 | Route route = navHistory.elementAt(i); 64 | navState.removeRoute(route); 65 | } 66 | } 67 | 68 | popToRouteNamed(String routeName) { 69 | NavigatorState navState = Navigator.of(globalKeyForRouter.currentContext); 70 | List> navHistory = navState.history; 71 | int histLen = navHistory.length; 72 | for (int i = histLen - 1; i >= 1; i--) { 73 | Route route = navHistory.elementAt(i); 74 | if (!(route is XMaterialPageRoute) || 75 | ((route as XMaterialPageRoute).settings.name != routeName)) { 76 | navState.removeRoute(route); 77 | } 78 | if ((route is XMaterialPageRoute) && 79 | ((route as XMaterialPageRoute).settings.name == routeName)) break; 80 | } 81 | } 82 | 83 | popRouteNamed(String routeName) { 84 | NavigatorState navState = Navigator.of(globalKeyForRouter.currentContext); 85 | List> navHistory = navState.history; 86 | int histLen = navHistory.length; 87 | for (int i = histLen - 1; i >= 1; i--) { 88 | Route route = navHistory.elementAt(i); 89 | if ((route is XMaterialPageRoute) && 90 | ((route as XMaterialPageRoute).settings.name == routeName)) { 91 | navState.removeRoute(route); 92 | break; 93 | } 94 | } 95 | } 96 | 97 | pushPageWithOptionsFromFlutter({RouterOption routeOption, bool animated}) { 98 | Widget page = 99 | Router.sharedInstance().pageFromOption(routeOption: routeOption); 100 | if (page != null) { 101 | XMaterialPageRoute pageRoute = new XMaterialPageRoute( 102 | settings: new RouteSettings(name: routeOption.userInfo), 103 | animated: animated, 104 | builder: (BuildContext context) { 105 | return page; 106 | }); 107 | 108 | Navigator.of(globalKeyForRouter.currentContext).push(pageRoute); 109 | HybridStackManagerPlugin.hybridStackManagerPlugin 110 | .updateCurFlutterRoute(routeOption.userInfo); 111 | } else { 112 | HybridStackManagerPlugin.hybridStackManagerPlugin.openUrlFromNative( 113 | url: routeOption.url, 114 | query: routeOption.query, 115 | params: routeOption.params); 116 | } 117 | NavigatorState navState = Navigator.of(globalKeyForRouter.currentContext); 118 | List> navHistory = navState.history; 119 | } 120 | 121 | pushPageWithOptionsFromNative({RouterOption routeOption, bool animated}) { 122 | HybridStackManagerPlugin.hybridStackManagerPlugin.openUrlFromNative( 123 | url: routeOption.url, 124 | query: routeOption.query, 125 | params: routeOption.params, 126 | animated: animated); 127 | } 128 | 129 | pageFromOption({RouterOption routeOption, Key key}) { 130 | try { 131 | currentPageUrl = routeOption.url + "?" + converUrl(routeOption.query); 132 | } catch (e) {} 133 | routeOption.userInfo = Utils.generateUniquePageName(routeOption.url); 134 | if (routerWidgetHandler != null) 135 | return routerWidgetHandler(routeOption: routeOption, key: key); 136 | } 137 | 138 | static String converUrl(Map query) { 139 | String tmpUrl = ""; 140 | if (query != null) { 141 | bool skipfirst = true; 142 | query.forEach((key, value) { 143 | if (skipfirst) { 144 | skipfirst = false; 145 | } else { 146 | tmpUrl = tmpUrl + "&"; 147 | } 148 | tmpUrl = tmpUrl + (key + "=" + value.toString()); 149 | }); 150 | } 151 | return Uri.encodeFull(tmpUrl); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /lib/router_option.dart: -------------------------------------------------------------------------------- 1 | class RouterOption { 2 | String url; 3 | Map query; 4 | Map params; 5 | String userInfo; 6 | RouterOption({this.url, this.query, this.params}); 7 | } 8 | -------------------------------------------------------------------------------- /lib/utils.dart: -------------------------------------------------------------------------------- 1 | class Utils extends Object { 2 | static int baseId = 100; 3 | static String pageNameSeperatorToken = "_"; 4 | 5 | static int generatePrimaryPageId() { 6 | return baseId++; 7 | } 8 | 9 | static Map parseUniquePageName(String pageName) { 10 | List components = pageName.split(pageNameSeperatorToken); 11 | if (components.length != 2) return null; 12 | return {"name": components[0], "id": components[1]}; 13 | } 14 | 15 | static String generateUniquePageName(String pageName) { 16 | return (pageName ?? "") + 17 | pageNameSeperatorToken + 18 | generatePrimaryPageId().toString(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: hybrid_stack_manager 2 | description: Hybrid stack management when using Flutter and Native(iOS/Android) simutaneously. 3 | version: 0.0.6 4 | author: KyleWong 5 | homepage: https://github.com/FlutterRepo/hybrid_stack_manager.git 6 | 7 | environment: 8 | sdk: ">=2.0.0-dev.48.0 <3.0.0" 9 | flutter: ">=0.3.1 <2.0.0" 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | # For information on the generic Dart part of this file, see the 15 | # following page: https://www.dartlang.org/tools/pub/pubspec 16 | 17 | # The following section is specific to Flutter. 18 | flutter: 19 | plugin: 20 | androidPackage: com.taobao.hybridstackmanager 21 | pluginClass: HybridStackManager 22 | 23 | # To add assets to your plugin package, add an assets section, like this: 24 | # assets: 25 | # - images/a_dot_burr.jpeg 26 | # - images/a_dot_ham.jpeg 27 | # 28 | # For details regarding assets in packages, see 29 | # https://flutter.io/assets-and-images/#from-packages 30 | # 31 | # An image asset can refer to one or more resolution-specific "variants", see 32 | # https://flutter.io/assets-and-images/#resolution-aware. 33 | 34 | # To add custom fonts to your plugin package, add a fonts section here, 35 | # in this "flutter" section. Each entry in this list should have a 36 | # "family" key with the font family name, and a "fonts" key with a 37 | # list giving the asset and other descriptors for the font. For 38 | # example: 39 | # fonts: 40 | # - family: Schyler 41 | # fonts: 42 | # - asset: fonts/Schyler-Regular.ttf 43 | # - asset: fonts/Schyler-Italic.ttf 44 | # style: italic 45 | # - family: Trajan Pro 46 | # fonts: 47 | # - asset: fonts/TrajanPro.ttf 48 | # - asset: fonts/TrajanPro_Bold.ttf 49 | # weight: 700 50 | # 51 | # For details regarding fonts in packages, see 52 | # https://flutter.io/custom-fonts/#from-packages 53 | --------------------------------------------------------------------------------