Explorar o código

Combine: Add a sample app (#8113)

A SwiftUI app that shows how to use the Combine-enabled APIs in context.

The  app contains a basic drill-down menu navigation and a simple screen that shows how to use Anonymous Auth and authStateDidChangePublisher.

As we add more publishers, their use should be demonstrated in this app as well.

The app currently needs a prod Firebase project, so one of the next steps will be to enable it for local Emulators.

#no-changelog
Peter Friese %!s(int64=4) %!d(string=hai) anos
pai
achega
f9929d0a9a
Modificáronse 49 ficheiros con 1295 adicións e 0 borrados
  1. 428 0
      Example/CombineSample/CombineSample.xcodeproj/project.pbxproj
  2. 78 0
      Example/CombineSample/CombineSample.xcodeproj/xcshareddata/xcschemes/CombineSample.xcscheme
  3. 10 0
      Example/CombineSample/CombineSample.xcworkspace/contents.xcworkspacedata
  4. 8 0
      Example/CombineSample/CombineSample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  5. 33 0
      Example/CombineSample/CombineSample/App/CombineSampleApp.swift
  6. 20 0
      Example/CombineSample/CombineSample/Assets.xcassets/AccentColor.colorset/Contents.json
  7. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/100.png
  8. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/1024.png
  9. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/114.png
  10. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/120.png
  11. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/128.png
  12. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/144.png
  13. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/152.png
  14. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/16.png
  15. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/167.png
  16. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/172.png
  17. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/180.png
  18. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/196.png
  19. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/20.png
  20. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/216.png
  21. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/256.png
  22. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/29.png
  23. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/32.png
  24. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/40.png
  25. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/48.png
  26. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/50.png
  27. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/512.png
  28. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/55.png
  29. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/57.png
  30. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/58.png
  31. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/60.png
  32. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/64.png
  33. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/72.png
  34. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/76.png
  35. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/80.png
  36. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/87.png
  37. BIN=BIN
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/88.png
  38. 302 0
      Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/Contents.json
  39. 6 0
      Example/CombineSample/CombineSample/Assets.xcassets/Contents.json
  40. 50 0
      Example/CombineSample/CombineSample/Info.plist
  41. 6 0
      Example/CombineSample/CombineSample/Preview Content/Preview Assets.xcassets/Contents.json
  42. 47 0
      Example/CombineSample/CombineSample/ViewModels/Authentication/AnonymousSignInViewModel.swift
  43. 35 0
      Example/CombineSample/CombineSample/ViewModels/Authentication/UserInfoViewModel.swift
  44. 46 0
      Example/CombineSample/CombineSample/Views/Authentication/AnonymousSignInView.swift
  45. 38 0
      Example/CombineSample/CombineSample/Views/Authentication/AuthenticationMenuView.swift
  46. 40 0
      Example/CombineSample/CombineSample/Views/Authentication/UserInfoView.swift
  47. 43 0
      Example/CombineSample/CombineSample/Views/LabelTextView.swift
  48. 41 0
      Example/CombineSample/CombineSample/Views/MenuView.swift
  49. 64 0
      Example/CombineSample/README.md

+ 428 - 0
Example/CombineSample/CombineSample.xcodeproj/project.pbxproj

@@ -0,0 +1,428 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 52;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		885E1B212655457200E13A96 /* AnonymousSignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885E1B202655457200E13A96 /* AnonymousSignInView.swift */; };
+		885F5FB12653BE0D00848BCF /* CombineSampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885F5FB02653BE0D00848BCF /* CombineSampleApp.swift */; };
+		885F5FB52653BE0D00848BCF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 885F5FB42653BE0D00848BCF /* Assets.xcassets */; };
+		885F5FB82653BE0D00848BCF /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 885F5FB72653BE0D00848BCF /* Preview Assets.xcassets */; };
+		88B12B5B265547FF008CFF38 /* AuthenticationMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88B12B5A265547FF008CFF38 /* AuthenticationMenuView.swift */; };
+		88B12B5D26554829008CFF38 /* MenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88B12B5C26554829008CFF38 /* MenuView.swift */; };
+		88DCE7432655419C003BCB65 /* UserInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DCE7422655419C003BCB65 /* UserInfoViewModel.swift */; };
+		88DCE746265541FB003BCB65 /* UserInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DCE745265541FB003BCB65 /* UserInfoView.swift */; };
+		88DCE7482655429F003BCB65 /* AnonymousSignInViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DCE7472655429F003BCB65 /* AnonymousSignInViewModel.swift */; };
+		88DF7E412653CE6E009794F3 /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 88DF7E402653CE6E009794F3 /* FirebaseAuth */; };
+		88DF7E472653D092009794F3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 88DF7E462653D092009794F3 /* GoogleService-Info.plist */; };
+		88DF7E4B2653D673009794F3 /* FirebaseCombineSwift-Beta in Frameworks */ = {isa = PBXBuildFile; productRef = 88DF7E4A2653D673009794F3 /* FirebaseCombineSwift-Beta */; };
+		88DF7E512654057F009794F3 /* LabelTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DF7E502654057F009794F3 /* LabelTextView.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+		885E1B202655457200E13A96 /* AnonymousSignInView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnonymousSignInView.swift; sourceTree = "<group>"; };
+		885F5FAD2653BE0D00848BCF /* CombineSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CombineSample.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		885F5FB02653BE0D00848BCF /* CombineSampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineSampleApp.swift; sourceTree = "<group>"; };
+		885F5FB42653BE0D00848BCF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+		885F5FB72653BE0D00848BCF /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
+		885F5FB92653BE0D00848BCF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		88B12B5A265547FF008CFF38 /* AuthenticationMenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationMenuView.swift; sourceTree = "<group>"; };
+		88B12B5C26554829008CFF38 /* MenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = "<group>"; };
+		88DCE7422655419C003BCB65 /* UserInfoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoViewModel.swift; sourceTree = "<group>"; };
+		88DCE745265541FB003BCB65 /* UserInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoView.swift; sourceTree = "<group>"; };
+		88DCE7472655429F003BCB65 /* AnonymousSignInViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnonymousSignInViewModel.swift; sourceTree = "<group>"; };
+		88DF7E462653D092009794F3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
+		88DF7E502654057F009794F3 /* LabelTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelTextView.swift; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		885F5FAA2653BE0D00848BCF /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				88DF7E412653CE6E009794F3 /* FirebaseAuth in Frameworks */,
+				88DF7E4B2653D673009794F3 /* FirebaseCombineSwift-Beta in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		885F5FA42653BE0C00848BCF = {
+			isa = PBXGroup;
+			children = (
+				885F5FAF2653BE0D00848BCF /* CombineSample */,
+				885F5FAE2653BE0D00848BCF /* Products */,
+				88DF7E3F2653CE6E009794F3 /* Frameworks */,
+			);
+			sourceTree = "<group>";
+		};
+		885F5FAE2653BE0D00848BCF /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				885F5FAD2653BE0D00848BCF /* CombineSample.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		885F5FAF2653BE0D00848BCF /* CombineSample */ = {
+			isa = PBXGroup;
+			children = (
+				88DF7E452653CF3B009794F3 /* App */,
+				88DF7E4F26540561009794F3 /* Views */,
+				88DCE74026554174003BCB65 /* ViewModels */,
+				885F5FB42653BE0D00848BCF /* Assets.xcassets */,
+				88DF7E462653D092009794F3 /* GoogleService-Info.plist */,
+				885F5FB92653BE0D00848BCF /* Info.plist */,
+				885F5FB62653BE0D00848BCF /* Preview Content */,
+			);
+			path = CombineSample;
+			sourceTree = "<group>";
+		};
+		885F5FB62653BE0D00848BCF /* Preview Content */ = {
+			isa = PBXGroup;
+			children = (
+				885F5FB72653BE0D00848BCF /* Preview Assets.xcassets */,
+			);
+			path = "Preview Content";
+			sourceTree = "<group>";
+		};
+		88DCE74026554174003BCB65 /* ViewModels */ = {
+			isa = PBXGroup;
+			children = (
+				88DCE74126554189003BCB65 /* Authentication */,
+			);
+			path = ViewModels;
+			sourceTree = "<group>";
+		};
+		88DCE74126554189003BCB65 /* Authentication */ = {
+			isa = PBXGroup;
+			children = (
+				88DCE7422655419C003BCB65 /* UserInfoViewModel.swift */,
+				88DCE7472655429F003BCB65 /* AnonymousSignInViewModel.swift */,
+			);
+			path = Authentication;
+			sourceTree = "<group>";
+		};
+		88DCE744265541E7003BCB65 /* Authentication */ = {
+			isa = PBXGroup;
+			children = (
+				88DCE745265541FB003BCB65 /* UserInfoView.swift */,
+				88B12B5A265547FF008CFF38 /* AuthenticationMenuView.swift */,
+				885E1B202655457200E13A96 /* AnonymousSignInView.swift */,
+			);
+			path = Authentication;
+			sourceTree = "<group>";
+		};
+		88DF7E3F2653CE6E009794F3 /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+		88DF7E452653CF3B009794F3 /* App */ = {
+			isa = PBXGroup;
+			children = (
+				885F5FB02653BE0D00848BCF /* CombineSampleApp.swift */,
+			);
+			path = App;
+			sourceTree = "<group>";
+		};
+		88DF7E4F26540561009794F3 /* Views */ = {
+			isa = PBXGroup;
+			children = (
+				88B12B5C26554829008CFF38 /* MenuView.swift */,
+				88DF7E502654057F009794F3 /* LabelTextView.swift */,
+				88DCE744265541E7003BCB65 /* Authentication */,
+			);
+			path = Views;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		885F5FAC2653BE0D00848BCF /* CombineSample */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 885F5FBC2653BE0D00848BCF /* Build configuration list for PBXNativeTarget "CombineSample" */;
+			buildPhases = (
+				885F5FA92653BE0D00848BCF /* Sources */,
+				885F5FAA2653BE0D00848BCF /* Frameworks */,
+				885F5FAB2653BE0D00848BCF /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = CombineSample;
+			packageProductDependencies = (
+				88DF7E402653CE6E009794F3 /* FirebaseAuth */,
+				88DF7E4A2653D673009794F3 /* FirebaseCombineSwift-Beta */,
+			);
+			productName = CombineSample;
+			productReference = 885F5FAD2653BE0D00848BCF /* CombineSample.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		885F5FA52653BE0C00848BCF /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastSwiftUpdateCheck = 1250;
+				LastUpgradeCheck = 1250;
+				TargetAttributes = {
+					885F5FAC2653BE0D00848BCF = {
+						CreatedOnToolsVersion = 12.5;
+					};
+				};
+			};
+			buildConfigurationList = 885F5FA82653BE0C00848BCF /* Build configuration list for PBXProject "CombineSample" */;
+			compatibilityVersion = "Xcode 9.3";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = 885F5FA42653BE0C00848BCF;
+			productRefGroup = 885F5FAE2653BE0D00848BCF /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				885F5FAC2653BE0D00848BCF /* CombineSample */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		885F5FAB2653BE0D00848BCF /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				885F5FB82653BE0D00848BCF /* Preview Assets.xcassets in Resources */,
+				88DF7E472653D092009794F3 /* GoogleService-Info.plist in Resources */,
+				885F5FB52653BE0D00848BCF /* Assets.xcassets in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		885F5FA92653BE0D00848BCF /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				885E1B212655457200E13A96 /* AnonymousSignInView.swift in Sources */,
+				88B12B5D26554829008CFF38 /* MenuView.swift in Sources */,
+				88DCE746265541FB003BCB65 /* UserInfoView.swift in Sources */,
+				885F5FB12653BE0D00848BCF /* CombineSampleApp.swift in Sources */,
+				88DF7E512654057F009794F3 /* LabelTextView.swift in Sources */,
+				88DCE7482655429F003BCB65 /* AnonymousSignInViewModel.swift in Sources */,
+				88DCE7432655419C003BCB65 /* UserInfoViewModel.swift in Sources */,
+				88B12B5B265547FF008CFF38 /* AuthenticationMenuView.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+		885F5FBA2653BE0D00848BCF /* 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.5;
+				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;
+		};
+		885F5FBB2653BE0D00848BCF /* 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.5;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				MTL_FAST_MATH = YES;
+				SDKROOT = iphoneos;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Release;
+		};
+		885F5FBD2653BE0D00848BCF /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+				CODE_SIGN_STYLE = Automatic;
+				DEVELOPMENT_ASSET_PATHS = "\"CombineSample/Preview Content\"";
+				DEVELOPMENT_TEAM = YGAZHQXHH4;
+				ENABLE_PREVIEWS = YES;
+				INFOPLIST_FILE = CombineSample/Info.plist;
+				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.CombineSample;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_VERSION = 5.0;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		885F5FBE2653BE0D00848BCF /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+				CODE_SIGN_STYLE = Automatic;
+				DEVELOPMENT_ASSET_PATHS = "\"CombineSample/Preview Content\"";
+				DEVELOPMENT_TEAM = YGAZHQXHH4;
+				ENABLE_PREVIEWS = YES;
+				INFOPLIST_FILE = CombineSample/Info.plist;
+				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = com.google.firebase.CombineSample;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_VERSION = 5.0;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		885F5FA82653BE0C00848BCF /* Build configuration list for PBXProject "CombineSample" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				885F5FBA2653BE0D00848BCF /* Debug */,
+				885F5FBB2653BE0D00848BCF /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		885F5FBC2653BE0D00848BCF /* Build configuration list for PBXNativeTarget "CombineSample" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				885F5FBD2653BE0D00848BCF /* Debug */,
+				885F5FBE2653BE0D00848BCF /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+
+/* Begin XCSwiftPackageProductDependency section */
+		88DF7E402653CE6E009794F3 /* FirebaseAuth */ = {
+			isa = XCSwiftPackageProductDependency;
+			productName = FirebaseAuth;
+		};
+		88DF7E4A2653D673009794F3 /* FirebaseCombineSwift-Beta */ = {
+			isa = XCSwiftPackageProductDependency;
+			productName = "FirebaseCombineSwift-Beta";
+		};
+/* End XCSwiftPackageProductDependency section */
+	};
+	rootObject = 885F5FA52653BE0C00848BCF /* Project object */;
+}

+ 78 - 0
Example/CombineSample/CombineSample.xcodeproj/xcshareddata/xcschemes/CombineSample.xcscheme

@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1250"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "885F5FAC2653BE0D00848BCF"
+               BuildableName = "CombineSample.app"
+               BlueprintName = "CombineSample"
+               ReferencedContainer = "container:CombineSample.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "885F5FAC2653BE0D00848BCF"
+            BuildableName = "CombineSample.app"
+            BlueprintName = "CombineSample"
+            ReferencedContainer = "container:CombineSample.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "885F5FAC2653BE0D00848BCF"
+            BuildableName = "CombineSample.app"
+            BlueprintName = "CombineSample"
+            ReferencedContainer = "container:CombineSample.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 10 - 0
Example/CombineSample/CombineSample.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "container:CombineSample.xcodeproj">
+   </FileRef>
+   <FileRef
+      location = "group:../../../firebase-ios-sdk">
+   </FileRef>
+</Workspace>

+ 8 - 0
Example/CombineSample/CombineSample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@@ -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>

+ 33 - 0
Example/CombineSample/CombineSample/App/CombineSampleApp.swift

@@ -0,0 +1,33 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import SwiftUI
+import Firebase
+
+@main
+struct CombineSampleApp: App {
+  init() {
+    FirebaseApp.configure()
+  }
+
+  var body: some Scene {
+    WindowGroup {
+      NavigationView {
+        MenuView()
+      }
+      // see https://stackoverflow.com/questions/63740788/swiftui-displaymodebuttonitem-is-internally-managed
+      .navigationViewStyle(StackNavigationViewStyle())
+    }
+  }
+}

+ 20 - 0
Example/CombineSample/CombineSample/Assets.xcassets/AccentColor.colorset/Contents.json

@@ -0,0 +1,20 @@
+{
+  "colors" : [
+    {
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "0.157",
+          "green" : "0.792",
+          "red" : "1.000"
+        }
+      },
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/100.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/1024.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/114.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/120.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/128.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/144.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/152.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/16.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/167.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/172.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/180.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/196.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/20.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/216.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/256.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/29.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/32.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/40.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/48.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/50.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/512.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/55.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/57.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/58.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/60.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/64.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/72.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/76.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/80.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/87.png


BIN=BIN
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/88.png


+ 302 - 0
Example/CombineSample/CombineSample/Assets.xcassets/AppIcon.appiconset/Contents.json

@@ -0,0 +1,302 @@
+{
+  "images" : [
+    {
+      "filename" : "40.png",
+      "idiom" : "iphone",
+      "scale" : "2x",
+      "size" : "20x20"
+    },
+    {
+      "filename" : "60.png",
+      "idiom" : "iphone",
+      "scale" : "3x",
+      "size" : "20x20"
+    },
+    {
+      "filename" : "29.png",
+      "idiom" : "iphone",
+      "scale" : "1x",
+      "size" : "29x29"
+    },
+    {
+      "filename" : "58.png",
+      "idiom" : "iphone",
+      "scale" : "2x",
+      "size" : "29x29"
+    },
+    {
+      "filename" : "87.png",
+      "idiom" : "iphone",
+      "scale" : "3x",
+      "size" : "29x29"
+    },
+    {
+      "filename" : "80.png",
+      "idiom" : "iphone",
+      "scale" : "2x",
+      "size" : "40x40"
+    },
+    {
+      "filename" : "120.png",
+      "idiom" : "iphone",
+      "scale" : "3x",
+      "size" : "40x40"
+    },
+    {
+      "filename" : "57.png",
+      "idiom" : "iphone",
+      "scale" : "1x",
+      "size" : "57x57"
+    },
+    {
+      "filename" : "114.png",
+      "idiom" : "iphone",
+      "scale" : "2x",
+      "size" : "57x57"
+    },
+    {
+      "filename" : "120.png",
+      "idiom" : "iphone",
+      "scale" : "2x",
+      "size" : "60x60"
+    },
+    {
+      "filename" : "180.png",
+      "idiom" : "iphone",
+      "scale" : "3x",
+      "size" : "60x60"
+    },
+    {
+      "filename" : "20.png",
+      "idiom" : "ipad",
+      "scale" : "1x",
+      "size" : "20x20"
+    },
+    {
+      "filename" : "40.png",
+      "idiom" : "ipad",
+      "scale" : "2x",
+      "size" : "20x20"
+    },
+    {
+      "filename" : "29.png",
+      "idiom" : "ipad",
+      "scale" : "1x",
+      "size" : "29x29"
+    },
+    {
+      "filename" : "58.png",
+      "idiom" : "ipad",
+      "scale" : "2x",
+      "size" : "29x29"
+    },
+    {
+      "filename" : "40.png",
+      "idiom" : "ipad",
+      "scale" : "1x",
+      "size" : "40x40"
+    },
+    {
+      "filename" : "80.png",
+      "idiom" : "ipad",
+      "scale" : "2x",
+      "size" : "40x40"
+    },
+    {
+      "filename" : "50.png",
+      "idiom" : "ipad",
+      "scale" : "1x",
+      "size" : "50x50"
+    },
+    {
+      "filename" : "100.png",
+      "idiom" : "ipad",
+      "scale" : "2x",
+      "size" : "50x50"
+    },
+    {
+      "filename" : "72.png",
+      "idiom" : "ipad",
+      "scale" : "1x",
+      "size" : "72x72"
+    },
+    {
+      "filename" : "144.png",
+      "idiom" : "ipad",
+      "scale" : "2x",
+      "size" : "72x72"
+    },
+    {
+      "filename" : "76.png",
+      "idiom" : "ipad",
+      "scale" : "1x",
+      "size" : "76x76"
+    },
+    {
+      "filename" : "152.png",
+      "idiom" : "ipad",
+      "scale" : "2x",
+      "size" : "76x76"
+    },
+    {
+      "filename" : "167.png",
+      "idiom" : "ipad",
+      "scale" : "2x",
+      "size" : "83.5x83.5"
+    },
+    {
+      "filename" : "1024.png",
+      "idiom" : "ios-marketing",
+      "scale" : "1x",
+      "size" : "1024x1024"
+    },
+    {
+      "filename" : "48.png",
+      "idiom" : "watch",
+      "role" : "notificationCenter",
+      "scale" : "2x",
+      "size" : "24x24",
+      "subtype" : "38mm"
+    },
+    {
+      "filename" : "55.png",
+      "idiom" : "watch",
+      "role" : "notificationCenter",
+      "scale" : "2x",
+      "size" : "27.5x27.5",
+      "subtype" : "42mm"
+    },
+    {
+      "filename" : "58.png",
+      "idiom" : "watch",
+      "role" : "companionSettings",
+      "scale" : "2x",
+      "size" : "29x29"
+    },
+    {
+      "filename" : "87.png",
+      "idiom" : "watch",
+      "role" : "companionSettings",
+      "scale" : "3x",
+      "size" : "29x29"
+    },
+    {
+      "filename" : "80.png",
+      "idiom" : "watch",
+      "role" : "appLauncher",
+      "scale" : "2x",
+      "size" : "40x40",
+      "subtype" : "38mm"
+    },
+    {
+      "filename" : "88.png",
+      "idiom" : "watch",
+      "role" : "appLauncher",
+      "scale" : "2x",
+      "size" : "44x44",
+      "subtype" : "40mm"
+    },
+    {
+      "filename" : "100.png",
+      "idiom" : "watch",
+      "role" : "appLauncher",
+      "scale" : "2x",
+      "size" : "50x50",
+      "subtype" : "44mm"
+    },
+    {
+      "filename" : "172.png",
+      "idiom" : "watch",
+      "role" : "quickLook",
+      "scale" : "2x",
+      "size" : "86x86",
+      "subtype" : "38mm"
+    },
+    {
+      "filename" : "196.png",
+      "idiom" : "watch",
+      "role" : "quickLook",
+      "scale" : "2x",
+      "size" : "98x98",
+      "subtype" : "42mm"
+    },
+    {
+      "filename" : "216.png",
+      "idiom" : "watch",
+      "role" : "quickLook",
+      "scale" : "2x",
+      "size" : "108x108",
+      "subtype" : "44mm"
+    },
+    {
+      "filename" : "1024.png",
+      "idiom" : "watch-marketing",
+      "scale" : "1x",
+      "size" : "1024x1024"
+    },
+    {
+      "filename" : "16.png",
+      "idiom" : "mac",
+      "scale" : "1x",
+      "size" : "16x16"
+    },
+    {
+      "filename" : "32.png",
+      "idiom" : "mac",
+      "scale" : "2x",
+      "size" : "16x16"
+    },
+    {
+      "filename" : "32.png",
+      "idiom" : "mac",
+      "scale" : "1x",
+      "size" : "32x32"
+    },
+    {
+      "filename" : "64.png",
+      "idiom" : "mac",
+      "scale" : "2x",
+      "size" : "32x32"
+    },
+    {
+      "filename" : "128.png",
+      "idiom" : "mac",
+      "scale" : "1x",
+      "size" : "128x128"
+    },
+    {
+      "filename" : "256.png",
+      "idiom" : "mac",
+      "scale" : "2x",
+      "size" : "128x128"
+    },
+    {
+      "filename" : "256.png",
+      "idiom" : "mac",
+      "scale" : "1x",
+      "size" : "256x256"
+    },
+    {
+      "filename" : "512.png",
+      "idiom" : "mac",
+      "scale" : "2x",
+      "size" : "256x256"
+    },
+    {
+      "filename" : "512.png",
+      "idiom" : "mac",
+      "scale" : "1x",
+      "size" : "512x512"
+    },
+    {
+      "filename" : "1024.png",
+      "idiom" : "mac",
+      "scale" : "2x",
+      "size" : "512x512"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 6 - 0
Example/CombineSample/CombineSample/Assets.xcassets/Contents.json

@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 50 - 0
Example/CombineSample/CombineSample/Info.plist

@@ -0,0 +1,50 @@
+<?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>
+		<true/>
+	</dict>
+	<key>UIApplicationSupportsIndirectInputEvents</key>
+	<true/>
+	<key>UILaunchScreen</key>
+	<dict/>
+	<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>

+ 6 - 0
Example/CombineSample/CombineSample/Preview Content/Preview Assets.xcassets/Contents.json

@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 47 - 0
Example/CombineSample/CombineSample/ViewModels/Authentication/AnonymousSignInViewModel.swift

@@ -0,0 +1,47 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Foundation
+import Firebase
+import FirebaseCombineSwift
+import Combine
+
+class AnonymousSignInViewModel: UserInfoViewModel {
+  private var cancellables = Set<AnyCancellable>()
+
+  @Published var errorMessage: String = ""
+
+  func signIn() {
+    Auth.auth().signInAnonymously()
+      .map { $0.user }
+      .catch { error -> Just<User?> in
+        if (error as NSError).code == AuthErrorCode.adminRestrictedOperation.rawValue {
+          print("Make sure to enable Anonymous Auth for your project")
+        } else {
+          print(error)
+        }
+        return Just(nil)
+      }
+      .compactMap { $0 }
+      .sink { user in
+        print("User \(user.uid) signed in")
+      }
+      .store(in: &cancellables)
+  }
+
+  func signOut() {
+    try? Auth.auth().signOut()
+    user = nil
+  }
+}

+ 35 - 0
Example/CombineSample/CombineSample/ViewModels/Authentication/UserInfoViewModel.swift

@@ -0,0 +1,35 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Firebase
+import FirebaseCombineSwift
+import Combine
+
+class UserInfoViewModel: ObservableObject {
+  @Published var user: User?
+
+  @Published var isSignedIn = false
+
+  private var cancellables = Set<AnyCancellable>()
+
+  init() {
+    Auth.auth().authStateDidChangePublisher()
+      .map { $0 }
+      .assign(to: &$user)
+
+    $user
+      .map { $0 != nil }
+      .assign(to: &$isSignedIn)
+  }
+}

+ 46 - 0
Example/CombineSample/CombineSample/Views/Authentication/AnonymousSignInView.swift

@@ -0,0 +1,46 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import SwiftUI
+import Firebase
+import FirebaseCombineSwift
+import Combine
+
+struct AnonymousSignInView: View {
+  @StateObject var viewModel = AnonymousSignInViewModel()
+
+  var body: some View {
+    Form {
+      UserInfoView(viewModel: viewModel)
+      Section(header: Text("Sign in anonymously")) {
+        Button(action: viewModel.signIn) {
+          Text("Sign in")
+        }
+        .disabled(viewModel.isSignedIn)
+
+        Button(action: viewModel.signOut) {
+          Text("Sign out")
+        }
+        .disabled(!viewModel.isSignedIn)
+      }
+    }
+    .navigationTitle("Anonymous Auth")
+  }
+}
+
+struct AnonymousSignInView_Previews: PreviewProvider {
+  static var previews: some View {
+    AnonymousSignInView()
+  }
+}

+ 38 - 0
Example/CombineSample/CombineSample/Views/Authentication/AuthenticationMenuView.swift

@@ -0,0 +1,38 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import SwiftUI
+
+struct AuthenticationMenuView: View {
+  @StateObject var viewModel = UserInfoViewModel()
+
+  var body: some View {
+    List {
+      UserInfoView(viewModel: viewModel)
+      Section(header: Text("Demos")) {
+        NavigationLink(destination: AnonymousSignInView()) {
+          Label("Anonymous Sign In", systemImage: "person.fill.questionmark")
+        }
+      }
+    }
+    .listStyle(InsetGroupedListStyle())
+    .navigationTitle("Firebase Auth")
+  }
+}
+
+struct AuthenticationMenuView_Previews: PreviewProvider {
+  static var previews: some View {
+    AuthenticationMenuView()
+  }
+}

+ 40 - 0
Example/CombineSample/CombineSample/Views/Authentication/UserInfoView.swift

@@ -0,0 +1,40 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import SwiftUI
+
+struct UserInfoView: View {
+  @ObservedObject var viewModel: UserInfoViewModel
+
+  var body: some View {
+    Section(header: Text("User Info")) {
+      LabelTextView(
+        "User state",
+        value: viewModel.isSignedIn ? "User is signed in" : "User is signed out"
+      )
+      LabelTextView("User ID", value: viewModel.user?.uid ?? "")
+      LabelTextView("Display name", value: viewModel.user?.displayName ?? "")
+      LabelTextView("Email", value: viewModel.user?.email ?? "")
+    }
+  }
+}
+
+struct UserInfoView_Previews: PreviewProvider {
+  static let viewModel = UserInfoViewModel()
+  static var previews: some View {
+    Form {
+      UserInfoView(viewModel: viewModel)
+    }
+  }
+}

+ 43 - 0
Example/CombineSample/CombineSample/Views/LabelTextView.swift

@@ -0,0 +1,43 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import SwiftUI
+
+struct LabelTextView: View {
+  var label: String
+  var value: String
+
+  init(_ label: String, value: String) {
+    self.label = label
+    self.value = value
+  }
+
+  var body: some View {
+    VStack(alignment: .leading) {
+      Text(label)
+        .font(.caption)
+        .foregroundColor(.accentColor)
+      Text(value)
+    }
+  }
+}
+
+struct LabelTextView_Previews: PreviewProvider {
+  static var previews: some View {
+    Form {
+      LabelTextView("Hello", value: "World")
+    }
+    .previewLayout(.device)
+  }
+}

+ 41 - 0
Example/CombineSample/CombineSample/Views/MenuView.swift

@@ -0,0 +1,41 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import SwiftUI
+
+struct MenuView: View {
+  var body: some View {
+    List {
+      Section(header: Text("Demos")) {
+        NavigationLink(destination: AuthenticationMenuView()) {
+          Label("Firebase Authentication", systemImage: "key")
+        }
+        NavigationLink(destination: Text("Not implemented yet")) {
+          Label("Cloud Functions", systemImage: "gearshape.2")
+        }
+        NavigationLink(destination: Text("Not implemented yet")) {
+          Label("Cloud Firestore", systemImage: "externaldrive.badge.icloud")
+        }
+      }
+    }
+    .listStyle(InsetGroupedListStyle())
+    .navigationTitle("Firebase & Combine")
+  }
+}
+
+struct MenuView_Previews: PreviewProvider {
+  static var previews: some View {
+    MenuView()
+  }
+}

+ 64 - 0
Example/CombineSample/README.md

@@ -0,0 +1,64 @@
+# Firebase & Combine Sample
+
+This sample demonstrates how to use Firebase's Combine APIs.
+
+## How to use
+
+### Set up a Firebase project
+
+1. Create a new Firebase project via the [Firebase console](https://console.firebase.google.com/)
+2. Enable the required Firebase services in the Firebase project you created in step 1
+   * Firebase Authentication
+      * Enable Anonymous Auth
+3. Register this demo app as an iOS project
+4. Download `GoogleServices-Info.plist` and drag it into your project (it's easiest if you place it just next to `Info.plist`)
+
+
+### Enable Combine
+
+Currently, Combine support for Firebase is still under development, which is why we haven't enabled the respective Swift Package Manager products yet. You need to do so yourself:
+
+In `Package.swift`, find the following lines:
+
+```swift
+    // TODO: Re-enable after API review passes.
+    // .library(
+    //   name: "FirebaseCombineSwift-Beta",
+    //   targets: ["FirebaseCombineSwift"]
+    // ),
+    // .library(
+    //   name: "FirebaseAuthCombineSwift-Beta",
+    //   targets: ["FirebaseAuthCombineSwift"]
+    // ),
+    // .library(
+    //   name: "FirebaseFunctionsCombineSwift-Beta",
+    //   targets: ["FirebaseFunctionsCombineSwift"]
+    // ),
+    // .library(
+    //   name: "FirebaseStorageCombineSwift-Beta",
+    //   targets: ["FirebaseStorageCombineSwift"]
+    // ),
+```
+
+ and uncomment them:
+ ```swift
+    // TODO: Re-enable after API review passes.
+    .library(
+      name: "FirebaseCombineSwift-Beta",
+      targets: ["FirebaseCombineSwift"]
+    ),
+    .library(
+      name: "FirebaseAuthCombineSwift-Beta",
+      targets: ["FirebaseAuthCombineSwift"]
+    ),
+    .library(
+      name: "FirebaseFunctionsCombineSwift-Beta",
+      targets: ["FirebaseFunctionsCombineSwift"]
+    ),
+    .library(
+      name: "FirebaseStorageCombineSwift-Beta",
+      targets: ["FirebaseStorageCombineSwift"]
+    ),
+ ```
+
+ The app should now compile.