Added sample code for multiple flutters on iOS (#670)

pull/713/head
gaaclarke 5 years ago committed by GitHub
parent 241e2c1b26
commit 2825131b34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -17,6 +17,9 @@ existing Android and iOS apps.
existing Android app and demonstrates using
[Pigeon](https://pub.dev/packages/pigeon) to communicate between Flutter and
the host application.
* ['multiple_flutters'](./multiple_flutters) — Shows the usage of the Flutter
Engine Group APIs to embed multiple instances of Flutter into an existing app
with low memory cost.
## Goals for these samples

@ -0,0 +1,80 @@
# multiple_flutters
This is a sample that shows how to use the Flutter Engine Groups API to embed
multiple instances of Flutter into an existing Android or iOS project.
## Getting Started
For iOS instructions, see:
[multiple_flutters_ios](./multiple_flutters_ios/README.md).
For Android instructions, see:
[multiple_flutters_android](./multiple_flutters_android/README.md).
## Requirements
* Flutter -- after version 1991216543eb74e07c2de3746ca07c92071b19ac
* Android
* Android Studio
* iOS
* Xcode
* Cocoapods
## Flutter Engine Group
These examples use the Flutter Engine Group APIs on the host platform which
allows engines to share memory and CPU intensive resources. This leads to easier
embedding of Flutter into an existing app since multiple entrypoints can be
maintained via a FlutterFragment on Android or a UIViewController on iOS.
Before FlutterEngineGroup, users had to juggle the usage of a small number of
engines judiciously.
More info on those API's can be found in the source
code:
- iOS -
[FlutterEngineGroup.h](https://github.com/flutter/engine/blob/master/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h)
- Android -
[FlutterEngineGroup.java](https://github.com/flutter/engine/blob/master/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java)
## Important Files
### iOS
- [DataModel.swift](./multiple_flutters_ios/MultipleFluttersIos/HostViewController.swift)
— Observable data model that is the source of truth for the host platform and Flutter.
- [HostViewController.swift](./multiple_flutters_ios/MultipleFluttersIos/HostViewController.swift)
— A UIViewController that synchronizes to the DataModel.
- [main.dart](./multiple_flutters_module/lib/main.dart) — The Flutter source
code.
- [SingleFlutterViewController.swift](./multiple_flutters_ios/MultipleFluttersIos/SingleFlutterViewController.swift)
— A subclass of FlutterViewController that synchronizes with the DataModel.
- [DoubleFlutterViewController.swift](./multiple_flutters_ios/MultipleFluttersIos/DoubleFlutterViewController.swift)
— A UIViewController that embeds multiple Flutter instances.
### Android
## Data Synchronization Description
This sample code performs data synchronization to share data between the host
platform and multiple instances of Flutter by combining the
[Observer design pattern](https://en.wikipedia.org/wiki/Observer_pattern) and
[Flutter platform channels](https://flutter.dev/docs/development/platform-integration/platform-channels).
Here is how it works:
- The definitive source of truth for the data lives in the host platform data
model.
- Every host view displaying Flutter content maintains: a Flutter engine, a
bidirectional platform channel, and a subscription to the host data model.
- Flutter instances maintain a copy of the data they are interested in reading,
and this data is seeded by the host when the instance is first displayed.
- Mutations from Flutter code are sent to the host platform via the channel. The
host platform performs the mutations, and then notifies all host view
controllers and Flutter engines of the new value.
- Mutations from host code happen directly on the data model who notifies host
view controllers and Flutter engines of the new value.
This is just one possible way to synchronize the data between the host platform
and multiple Flutter instances. A more complete implementation is proposed in
the work of
[flutter/issues/72030](https://github.com/flutter/flutter/issues/72030).

@ -0,0 +1,6 @@
MultipleFluttersIos.xcworkspace/xcuserdata
MultipleFluttersIos.xcodeproj/project.xcworkspace/xcuserdata
Pods/Pods.xcodeproj/xcuserdata
MultipleFluttersIos.xcodeproj/xcuserdata
Pods/
MultipleFluttersIos.xcodeproj/xcshareddata/

@ -0,0 +1,457 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objects = {
/* Begin PBXBuildFile section */
0D61B1E225BB8A2300A1A590 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D61B1E125BB8A2300A1A590 /* AppDelegate.swift */; };
0D61B1E425BB8A2300A1A590 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D61B1E325BB8A2300A1A590 /* SceneDelegate.swift */; };
0D61B1E625BB8A2300A1A590 /* HostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D61B1E525BB8A2300A1A590 /* HostViewController.swift */; };
0D61B1E925BB8A2300A1A590 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0D61B1E725BB8A2300A1A590 /* Main.storyboard */; };
0D61B1EB25BB8A2400A1A590 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0D61B1EA25BB8A2400A1A590 /* Assets.xcassets */; };
0D61B1EE25BB8A2400A1A590 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0D61B1EC25BB8A2400A1A590 /* LaunchScreen.storyboard */; };
0D61B20125BB8FDF00A1A590 /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D61B20025BB8FDF00A1A590 /* DataModel.swift */; };
0D61B20825BBEA9E00A1A590 /* SingleFlutterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D61B20725BBEA9E00A1A590 /* SingleFlutterViewController.swift */; };
0D61B20B25BBF88700A1A590 /* DoubleFlutterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D61B20A25BBF88700A1A590 /* DoubleFlutterViewController.swift */; };
DA84F1C17CFA00606F7B401C /* Pods_MultipleFluttersIos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E03DC90CED28B2143B2CFB1 /* Pods_MultipleFluttersIos.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
0D61B1DE25BB8A2300A1A590 /* MultipleFluttersIos.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MultipleFluttersIos.app; sourceTree = BUILT_PRODUCTS_DIR; };
0D61B1E125BB8A2300A1A590 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
0D61B1E325BB8A2300A1A590 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
0D61B1E525BB8A2300A1A590 /* HostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostViewController.swift; sourceTree = "<group>"; };
0D61B1E825BB8A2300A1A590 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
0D61B1EA25BB8A2400A1A590 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
0D61B1ED25BB8A2400A1A590 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
0D61B1EF25BB8A2400A1A590 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0D61B20025BB8FDF00A1A590 /* DataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataModel.swift; sourceTree = "<group>"; };
0D61B20725BBEA9E00A1A590 /* SingleFlutterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleFlutterViewController.swift; sourceTree = "<group>"; };
0D61B20A25BBF88700A1A590 /* DoubleFlutterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoubleFlutterViewController.swift; sourceTree = "<group>"; };
4E03DC90CED28B2143B2CFB1 /* Pods_MultipleFluttersIos.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MultipleFluttersIos.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4F02FEA4EA8C75406BC5FFC5 /* Pods-MultipleFluttersIos.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MultipleFluttersIos.release.xcconfig"; path = "Target Support Files/Pods-MultipleFluttersIos/Pods-MultipleFluttersIos.release.xcconfig"; sourceTree = "<group>"; };
5EE6997543EAE7F85C674231 /* Pods-MultipleFluttersIos.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MultipleFluttersIos.debug.xcconfig"; path = "Target Support Files/Pods-MultipleFluttersIos/Pods-MultipleFluttersIos.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
0D61B1DB25BB8A2300A1A590 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DA84F1C17CFA00606F7B401C /* Pods_MultipleFluttersIos.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0D61B1D525BB8A2300A1A590 = {
isa = PBXGroup;
children = (
0D61B1E025BB8A2300A1A590 /* MultipleFluttersIos */,
0D61B1DF25BB8A2300A1A590 /* Products */,
2AB119CC8909334B007F8D1F /* Pods */,
A2F4F0BD46437369E87BA62F /* Frameworks */,
);
sourceTree = "<group>";
};
0D61B1DF25BB8A2300A1A590 /* Products */ = {
isa = PBXGroup;
children = (
0D61B1DE25BB8A2300A1A590 /* MultipleFluttersIos.app */,
);
name = Products;
sourceTree = "<group>";
};
0D61B1E025BB8A2300A1A590 /* MultipleFluttersIos */ = {
isa = PBXGroup;
children = (
0D61B1E125BB8A2300A1A590 /* AppDelegate.swift */,
0D61B1E325BB8A2300A1A590 /* SceneDelegate.swift */,
0D61B1E525BB8A2300A1A590 /* HostViewController.swift */,
0D61B20025BB8FDF00A1A590 /* DataModel.swift */,
0D61B1E725BB8A2300A1A590 /* Main.storyboard */,
0D61B1EA25BB8A2400A1A590 /* Assets.xcassets */,
0D61B1EC25BB8A2400A1A590 /* LaunchScreen.storyboard */,
0D61B1EF25BB8A2400A1A590 /* Info.plist */,
0D61B20725BBEA9E00A1A590 /* SingleFlutterViewController.swift */,
0D61B20A25BBF88700A1A590 /* DoubleFlutterViewController.swift */,
);
path = MultipleFluttersIos;
sourceTree = "<group>";
};
2AB119CC8909334B007F8D1F /* Pods */ = {
isa = PBXGroup;
children = (
5EE6997543EAE7F85C674231 /* Pods-MultipleFluttersIos.debug.xcconfig */,
4F02FEA4EA8C75406BC5FFC5 /* Pods-MultipleFluttersIos.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
A2F4F0BD46437369E87BA62F /* Frameworks */ = {
isa = PBXGroup;
children = (
4E03DC90CED28B2143B2CFB1 /* Pods_MultipleFluttersIos.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
0D61B1DD25BB8A2300A1A590 /* MultipleFluttersIos */ = {
isa = PBXNativeTarget;
buildConfigurationList = 0D61B1F225BB8A2400A1A590 /* Build configuration list for PBXNativeTarget "MultipleFluttersIos" */;
buildPhases = (
3BC074A3AF94322EF6E73F8B /* [CP] Check Pods Manifest.lock */,
C63B2D75D5C822F12D344293 /* [CP] Prepare Artifacts */,
9A714FC9CCEF3D4EFCAF0E2F /* [CP-User] Run Flutter Build multiple_flutters_module Script */,
0D61B1DA25BB8A2300A1A590 /* Sources */,
0D61B1DB25BB8A2300A1A590 /* Frameworks */,
0D61B1DC25BB8A2300A1A590 /* Resources */,
D93C82A75960A3ABB9D69453 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = MultipleFluttersIos;
productName = MultipleFluttersIos;
productReference = 0D61B1DE25BB8A2300A1A590 /* MultipleFluttersIos.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
0D61B1D625BB8A2300A1A590 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1220;
LastUpgradeCheck = 1220;
TargetAttributes = {
0D61B1DD25BB8A2300A1A590 = {
CreatedOnToolsVersion = 12.2;
};
};
};
buildConfigurationList = 0D61B1D925BB8A2300A1A590 /* Build configuration list for PBXProject "MultipleFluttersIos" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 0D61B1D525BB8A2300A1A590;
productRefGroup = 0D61B1DF25BB8A2300A1A590 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
0D61B1DD25BB8A2300A1A590 /* MultipleFluttersIos */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
0D61B1DC25BB8A2300A1A590 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0D61B1EE25BB8A2400A1A590 /* LaunchScreen.storyboard in Resources */,
0D61B1EB25BB8A2400A1A590 /* Assets.xcassets in Resources */,
0D61B1E925BB8A2300A1A590 /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3BC074A3AF94322EF6E73F8B /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-MultipleFluttersIos-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9A714FC9CCEF3D4EFCAF0E2F /* [CP-User] Run Flutter Build multiple_flutters_module Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
name = "[CP-User] Run Flutter Build multiple_flutters_module Script";
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -e\nset -u\nsource \"${SRCROOT}/../multiple_flutters_module/.ios/Flutter/flutter_export_environment.sh\"\nexport VERBOSE_SCRIPT_LOGGING=1 && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/xcode_backend.sh build";
};
C63B2D75D5C822F12D344293 /* [CP] Prepare Artifacts */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-MultipleFluttersIos/Pods-MultipleFluttersIos-artifacts-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Prepare Artifacts";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-MultipleFluttersIos/Pods-MultipleFluttersIos-artifacts-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MultipleFluttersIos/Pods-MultipleFluttersIos-artifacts.sh\"\n";
showEnvVarsInLog = 0;
};
D93C82A75960A3ABB9D69453 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-MultipleFluttersIos/Pods-MultipleFluttersIos-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-MultipleFluttersIos/Pods-MultipleFluttersIos-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MultipleFluttersIos/Pods-MultipleFluttersIos-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
0D61B1DA25BB8A2300A1A590 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0D61B20B25BBF88700A1A590 /* DoubleFlutterViewController.swift in Sources */,
0D61B1E625BB8A2300A1A590 /* HostViewController.swift in Sources */,
0D61B20125BB8FDF00A1A590 /* DataModel.swift in Sources */,
0D61B1E225BB8A2300A1A590 /* AppDelegate.swift in Sources */,
0D61B1E425BB8A2300A1A590 /* SceneDelegate.swift in Sources */,
0D61B20825BBEA9E00A1A590 /* SingleFlutterViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
0D61B1E725BB8A2300A1A590 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
0D61B1E825BB8A2300A1A590 /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
0D61B1EC25BB8A2400A1A590 /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
0D61B1ED25BB8A2400A1A590 /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
0D61B1F025BB8A2400A1A590 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
0D61B1F125BB8A2400A1A590 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.2;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
0D61B1F325BB8A2400A1A590 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 5EE6997543EAE7F85C674231 /* Pods-MultipleFluttersIos.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = MultipleFluttersIos/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.MultipleFluttersIos;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
0D61B1F425BB8A2400A1A590 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4F02FEA4EA8C75406BC5FFC5 /* Pods-MultipleFluttersIos.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = MultipleFluttersIos/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.MultipleFluttersIos;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
0D61B1D925BB8A2300A1A590 /* Build configuration list for PBXProject "MultipleFluttersIos" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0D61B1F025BB8A2400A1A590 /* Debug */,
0D61B1F125BB8A2400A1A590 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
0D61B1F225BB8A2400A1A590 /* Build configuration list for PBXNativeTarget "MultipleFluttersIos" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0D61B1F325BB8A2400A1A590 /* Debug */,
0D61B1F425BB8A2400A1A590 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 0D61B1D625BB8A2300A1A590 /* Project object */;
}

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:MultipleFluttersIos.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

@ -0,0 +1,47 @@
// Copyright 2021 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import Flutter
import UIKit
/// The app's UIApplicationDelegate.
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
let engines = FlutterEngineGroup(name: "multiple-flutters", project: nil)
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
#if DEBUG
let isDebug = true
#else
let isDebug = false
#endif
if isDebug {
NSLog(
"📣 NOTICE: the memory and CPU costs for Flutter engine groups are significantly greater in debug builds. See also: https://github.com/dart-lang/sdk/issues/36097"
)
} else {
NSLog(
"📣 NOTICE: the memory and CPU costs for Flutter engine groups are significantly less here than in debug builds. See also: https://github.com/dart-lang/sdk/issues/36097"
)
}
return true
}
func application(
_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
return UISceneConfiguration(
name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(
_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>
) {
}
}

@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="zV5-X7-f4o">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17505"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Navigation Controller-->
<scene sceneID="iJJ-8n-QPC">
<objects>
<navigationController id="zV5-X7-f4o" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" id="NiM-Jj-k9k">
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
<segue destination="BYZ-38-t0r" kind="relationship" relationship="rootViewController" id="PBq-fn-vqE"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="22r-TG-MDZ" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-1555" y="-191"/>
</scene>
<!--Host View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController storyboardIdentifier="NativeViewCount" id="BYZ-38-t0r" customClass="HostViewController" customModule="MultipleFluttersIos" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Count:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CgZ-JE-gda">
<rect key="frame" x="20" y="437.5" width="51" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="1234" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dKf-iz-CSn">
<rect key="frame" x="79" y="437.5" width="39" height="21"/>
<constraints>
<constraint firstAttribute="width" constant="39" id="Aw7-RN-dwx"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vVY-o2-UDW">
<rect key="frame" x="350" y="818" width="44" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="h7j-l3-taD"/>
<constraint firstAttribute="width" constant="44" id="sQd-lU-0zD"/>
</constraints>
<state key="normal" title="Next"/>
<connections>
<action selector="onNext" destination="BYZ-38-t0r" eventType="touchUpInside" id="XN9-kg-pAi"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ohd-Fe-iAE">
<rect key="frame" x="350" y="772" width="44" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="8Nc-hA-ZbV"/>
<constraint firstAttribute="width" constant="44" id="P0Q-km-hJ3"/>
</constraints>
<state key="normal" title="Add"/>
<connections>
<action selector="onAddCount" destination="BYZ-38-t0r" eventType="touchUpInside" id="QYB-J4-vI2"/>
</connections>
</button>
</subviews>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="CgZ-JE-gda" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" id="1Dz-Aq-10E"/>
<constraint firstItem="vVY-o2-UDW" firstAttribute="top" secondItem="Ohd-Fe-iAE" secondAttribute="bottom" constant="2" id="EqG-G1-3gr"/>
<constraint firstItem="vVY-o2-UDW" firstAttribute="bottom" secondItem="6Tk-OE-BBY" secondAttribute="bottom" id="IDv-xt-QVd"/>
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="vVY-o2-UDW" secondAttribute="trailing" constant="20" id="IQ1-E7-q9T"/>
<constraint firstItem="dKf-iz-CSn" firstAttribute="leading" secondItem="CgZ-JE-gda" secondAttribute="trailing" constant="8" symbolic="YES" id="ZKK-wt-eqr"/>
<constraint firstItem="dKf-iz-CSn" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" id="hUq-Je-pdK"/>
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="Ohd-Fe-iAE" secondAttribute="trailing" constant="20" id="wT9-j5-NMO"/>
<constraint firstItem="CgZ-JE-gda" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="20" id="ybv-NA-2by"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="NPA-Os-dNw"/>
<connections>
<outlet property="countView" destination="dKf-iz-CSn" id="9dM-Hg-UMg"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-722" y="-192"/>
</scene>
<!--View Controller-->
<scene sceneID="e8d-wy-DfZ">
<objects>
<viewController id="EXG-G5-awH" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="3av-sG-Ibb">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<viewLayoutGuide key="safeArea" id="R4s-uY-wnW"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="xYB-oQ-Y1A" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="4" y="-192"/>
</scene>
</scenes>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

@ -0,0 +1,56 @@
// Copyright 2021 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import Foundation
/// A protocol that receives updates when the datamodel is changed.
protocol DataModelObserver: AnyObject {
func onCountUpdate(newCount: Int64)
}
/// A wrapper object around a weak reference to an object that implements DataModelObserver.
///
/// This is required since you can't directly hold weak references to protocols in data structures.
private struct DataModelObserverWrapper {
weak var wrapped: DataModelObserver?
init(_ wrapped: DataModelObserver) {
self.wrapped = wrapped
}
}
/// A singleton data model that is observable.
class DataModel {
private var _count: Int64 = 0
var count: Int64 {
get {
return self._count
}
set {
self._count = newValue
for observer in observers {
if let wrapped = observer.wrapped {
wrapped.onCountUpdate(newCount: self._count)
}
}
}
}
private var observers: [DataModelObserverWrapper] = []
static let shared = DataModel()
func addObserver(observer: DataModelObserver) {
observers.append(DataModelObserverWrapper(observer))
}
func removeObserver(observer: DataModelObserver) {
observers.removeAll { (element: DataModelObserverWrapper) -> Bool in
if let wrapped = element.wrapped {
return wrapped === observer
} else {
// Handle observers who dealloc'd without removing themselves.
return true
}
}
}
}

@ -0,0 +1,29 @@
// Copyright 2021 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import UIKit
/// A UIViewController that hosts two instances of SingleFlutterViewController, splitting the screen
/// between them.
class DoubleFlutterViewController: UIViewController {
private let topFlutter: SingleFlutterViewController = SingleFlutterViewController(
withEntrypoint: "topMain")
private let bottomFlutter: SingleFlutterViewController = SingleFlutterViewController(
withEntrypoint: "bottomMain")
override func viewDidLoad() {
addChild(topFlutter)
addChild(bottomFlutter)
let safeFrame = self.view.safeAreaLayoutGuide.layoutFrame
let halfHeight = safeFrame.height / 2.0
topFlutter.view.frame = CGRect(
x: safeFrame.minX, y: safeFrame.minY, width: safeFrame.width, height: halfHeight)
bottomFlutter.view.frame = CGRect(
x: safeFrame.minX, y: topFlutter.view.frame.maxY, width: safeFrame.width, height: halfHeight)
self.view.addSubview(topFlutter.view)
self.view.addSubview(bottomFlutter.view)
topFlutter.didMove(toParent: self)
bottomFlutter.didMove(toParent: self)
}
}

@ -0,0 +1,43 @@
// Copyright 2021 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import Flutter
import UIKit
/// UIViewController associated with the NativeViewCount storyboard scene.
class HostViewController: UIViewController, DataModelObserver {
@IBOutlet weak var countView: UILabel!
deinit {
DataModel.shared.removeObserver(observer: self)
}
func onCountUpdate(newCount: Int64) {
self.countView.text = String(format: "%d", newCount)
}
override func viewDidLoad() {
super.viewDidLoad()
DataModel.shared.addObserver(observer: self)
onCountUpdate(newCount: DataModel.shared.count)
}
@IBAction func onAddCount() {
DataModel.shared.count = DataModel.shared.count + 1
}
@IBAction func onNext() {
let navController = self.navigationController!
// Based on the number of view controllers in the stack we push a
// SingleFlutterViewController or DoubleFlutterViewController to alternatve
// between the two.
if navController.viewControllers.count % 4 == 1 {
let vc = SingleFlutterViewController(withEntrypoint: nil)
navController.pushViewController(vc, animated: true)
} else {
let vc = DoubleFlutterViewController()
navController.pushViewController(vc, animated: true)
}
}
}

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

@ -0,0 +1,32 @@
// Copyright 2021 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import UIKit
/// The UIWindowSceneDelegate for the app.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(
_ scene: UIScene, willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let _ = (scene as? UIWindowScene) else { return }
}
func sceneDidDisconnect(_ scene: UIScene) {
}
func sceneDidBecomeActive(_ scene: UIScene) {
}
func sceneWillResignActive(_ scene: UIScene) {
}
func sceneWillEnterForeground(_ scene: UIScene) {
}
func sceneDidEnterBackground(_ scene: UIScene) {
}
}

@ -0,0 +1,57 @@
// Copyright 2021 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import Flutter
import Foundation
/// A FlutterViewController intended for the MyApp widget in the Flutter module.
///
/// This view controller maintains a connection to the Flutter instance and syncs it with the
/// datamodel. In practice you should override the other init methods or switch to composition
/// instead of inheritence.
class SingleFlutterViewController: FlutterViewController, DataModelObserver {
private var channel: FlutterMethodChannel?
init(withEntrypoint entryPoint: String?) {
let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
let newEngine = appDelegate.engines.makeEngine(withEntrypoint: entryPoint, libraryURI: nil)
super.init(engine: newEngine, nibName: nil, bundle: nil)
DataModel.shared.addObserver(observer: self)
}
deinit {
DataModel.shared.removeObserver(observer: self)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func onCountUpdate(newCount: Int64) {
if let channel = channel {
channel.invokeMethod("setCount", arguments: newCount)
}
}
override func viewDidLoad() {
super.viewDidLoad()
channel = FlutterMethodChannel(
name: "multiple-flutters", binaryMessenger: self.engine!.binaryMessenger)
channel!.invokeMethod("setCount", arguments: DataModel.shared.count)
let navController = self.navigationController!
channel!.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
if call.method == "incrementCount" {
DataModel.shared.count = DataModel.shared.count + 1
result(nil)
} else if call.method == "next" {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "NativeViewCount")
navController.pushViewController(vc, animated: true)
result(nil)
} else {
result(FlutterMethodNotImplemented)
}
}
}
}

@ -0,0 +1,11 @@
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
flutter_application_path = '../multiple_flutters_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'MultipleFluttersIos' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
install_all_flutter_pods(flutter_application_path)
end

@ -0,0 +1,28 @@
PODS:
- Flutter (1.0.0)
- FlutterPluginRegistrant (0.0.1):
- Flutter
- multiple_flutters_module (0.0.1):
- Flutter
DEPENDENCIES:
- Flutter (from `../multiple_flutters_module/.ios/Flutter/engine`)
- FlutterPluginRegistrant (from `../multiple_flutters_module/.ios/Flutter/FlutterPluginRegistrant`)
- multiple_flutters_module (from `../multiple_flutters_module/.ios/Flutter`)
EXTERNAL SOURCES:
Flutter:
:path: "../multiple_flutters_module/.ios/Flutter/engine"
FlutterPluginRegistrant:
:path: "../multiple_flutters_module/.ios/Flutter/FlutterPluginRegistrant"
multiple_flutters_module:
:path: "../multiple_flutters_module/.ios/Flutter"
SPEC CHECKSUMS:
Flutter: ac41d61a47ae5bf8195a5598d2d63754888ec0d5
FlutterPluginRegistrant: 2afd5ea46d3a949472c9b7da6462d8fbf7d8b16e
multiple_flutters_module: fdf461a0e4225614d475110d85db6fd6de5aeff1
PODFILE CHECKSUM: d7f39981f450db398859f05de2475ec53909a487
COCOAPODS: 1.9.3

@ -0,0 +1,17 @@
# multiple_flutters_ios
This is an add-to-app sample that uses the Flutter Engine Group API to host
multiple instances of Flutter in an iOS app.
## Getting Started
```sh
cd ../multiple_flutters_module
flutter pub get
cd -
pod install
open MultipleFluttersIos.xcworkspace
# (build and run)
```
For more information see: [multiple_flutters/README.md](../README.md)

@ -0,0 +1,48 @@
.DS_Store
.dart_tool/
.packages
.pub/
.idea/
.vagrant/
.sconsign.dblite
.svn/
*.swp
profile
DerivedData/
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
build/
.android/
.ios/
.flutter-plugins
.flutter-plugins-dependencies
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 1d3f6971600f6e3fb144a30fab2b889e34af0c22
channel: master
project_type: module

@ -0,0 +1,5 @@
# multiple_flutters_module
This is the Flutter module that is embedded in the `multiple_flutters` projects.
See also: [multiple_flutters/README.md](../README.md)

@ -0,0 +1,98 @@
// Copyright 2021 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp(Colors.blue));
@pragma('vm:entry-point')
void topMain() => runApp(MyApp(Colors.green));
@pragma('vm:entry-point')
void bottomMain() => runApp(MyApp(Colors.purple));
class MyApp extends StatelessWidget {
MyApp(this.color);
final Color color;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: this.color,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
MethodChannel _channel;
@override
void initState() {
super.initState();
_channel = MethodChannel('multiple-flutters');
_channel.setMethodCallHandler((MethodCall call) async {
if (call.method == "setCount") {
// A notification that the host platform's data model has been updated.
setState(() {
_counter = call.arguments as int;
});
} else {
throw Exception('not implemented ${call.method}');
}
});
}
void _incrementCounter() {
// Mutations to the data model are forwarded to the host platform.
_channel.invokeMethod("incrementCount", _counter);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
TextButton(
onPressed: _incrementCounter,
child: Text('Add'),
),
TextButton(
onPressed: () {
_channel.invokeMethod("next", _counter);
},
child: Text('Next'),
),
],
),
),
);
}
}

@ -0,0 +1,153 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.5.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.10"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.19"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
sdks:
dart: ">=2.12.0-0.0 <3.0.0"

@ -0,0 +1,25 @@
name: multiple_flutters_module
description: A module that is embedded in the multiple_flutters_ios and multiple_flutters_android sample code.
version: 1.0.0+1
environment:
sdk: ">=2.8.1 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
module:
androidX: true
androidPackage: com.example.multiple_flutters_module
iosBundleIdentifier: com.example.multipleFluttersModule

@ -7,6 +7,7 @@ declare -ar PROJECT_NAMES=(
"add_to_app/prebuilt_module/flutter_module" \
"add_to_app/plugin/flutter_module_using_plugin" \
"add_to_app/books/flutter_module_books" \
"add_to_app/multiple_flutters/multiple_flutters_module" \
"animations" \
# Tracking issue: https://github.com/flutter/samples/issues/652
# "flutter_maps_firestore" \
@ -57,7 +58,10 @@ do
flutter format -n --set-exit-if-changed .
# Run the actual tests.
flutter test
if [ -d "test"]
then
flutter test
fi
popd
done

@ -21,6 +21,11 @@ pushd add_to_app/fullscreen/flutter_module
flutter packages get
popd
echo "Fetching dependencies and building 'multiple_flutters/multiple_flutters_module'."
pushd add_to_app/multiple_flutters/multiple_flutters_module
flutter packages get
popd
echo "== Testing 'add_to_app/fullscreen/ios_fullscreen' on Flutter's $FLUTTER_VERSION channel =="
pushd "add_to_app/fullscreen/ios_fullscreen"
@ -71,5 +76,23 @@ COMPILER_INDEX_STORE_ENABLE=NO CONFIGURATION=Release \
popd
echo "== Testing 'add_to_app/multiple_flutters/multiple_flutters_ios' on Flutter's $FLUTTER_VERSION channel =="
pushd "add_to_app/multiple_flutters/multiple_flutters_ios"
pod install
xcodebuild -workspace "MultipleFluttersIos.xcworkspace" \
-scheme "MultipleFluttersIos" CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO \
CODE_SIGN_IDENTITY=- EXPANDED_CODE_SIGN_IDENTITY=- \
COMPILER_INDEX_STORE_ENABLE=NO CONFIGURATION=Debug | xcpretty
xcodebuild -workspace "MultipleFluttersIos.xcworkspace" \
-scheme "MultipleFluttersIos" CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO \
CODE_SIGN_IDENTITY=- EXPANDED_CODE_SIGN_IDENTITY=- \
COMPILER_INDEX_STORE_ENABLE=NO CONFIGURATION=Release \
-destination generic/platform=iOS | xcpretty
popd
echo "-- Success --"

Loading…
Cancel
Save