From a7dc31f8799ff5bd3ac4727b1c4e0fdbc8928b98 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Soucy Date: Fri, 24 May 2024 10:46:30 -0400 Subject: [PATCH 1/4] feat: add android firebase implementation of forced update and killswitch --- .gitignore | 4 + .../ForcedUpdates/UpdateRequiredService.cs | 8 +- .../ApplicationTemplate.Mobile.csproj | 19 ++- .../Configuration/ApiConfiguration.cs | 14 --- ...ApplicationTemplate.Shared.Views.projitems | 1 + .../ViewServicesConfiguration.cs | 9 ++ .../RemoteConfigRepository.Android.cs | 114 ++++++++++++++++++ 7 files changed, 153 insertions(+), 16 deletions(-) create mode 100644 src/app/ApplicationTemplate.Shared.Views/RemoteConfigRepository.Android.cs diff --git a/.gitignore b/.gitignore index 6272ab935..73dcc4f58 100644 --- a/.gitignore +++ b/.gitignore @@ -325,3 +325,7 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ + +# Firebase +*google-services.json +*GoogleService-Info.plist diff --git a/src/app/ApplicationTemplate.Business/ForcedUpdates/UpdateRequiredService.cs b/src/app/ApplicationTemplate.Business/ForcedUpdates/UpdateRequiredService.cs index 5882edd60..f2c23db26 100644 --- a/src/app/ApplicationTemplate.Business/ForcedUpdates/UpdateRequiredService.cs +++ b/src/app/ApplicationTemplate.Business/ForcedUpdates/UpdateRequiredService.cs @@ -18,7 +18,13 @@ public sealed class UpdateRequiredService : IUpdateRequiredService, IDisposable public UpdateRequiredService(IMinimumVersionReposiory minimumVersionReposiory) { _subscription = minimumVersionReposiory.MinimumVersionObservable - .Subscribe(_ => UpdateRequired?.Invoke(this, EventArgs.Empty)); + .Subscribe(version => + { + if (version > new Version("1.1.0")) + { + UpdateRequired?.Invoke(this, EventArgs.Empty); + } + }); } /// diff --git a/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj b/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj index 6678ad4b6..63c14fb79 100644 --- a/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj +++ b/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj @@ -15,6 +15,9 @@ True partial + + + @@ -100,7 +103,15 @@ - + + + + + + + + google-services.json + @@ -153,6 +164,12 @@ true + + + + GoogleService-Info.plist + + diff --git a/src/app/ApplicationTemplate.Presentation/Configuration/ApiConfiguration.cs b/src/app/ApplicationTemplate.Presentation/Configuration/ApiConfiguration.cs index 1bab32ae7..0f6224574 100644 --- a/src/app/ApplicationTemplate.Presentation/Configuration/ApiConfiguration.cs +++ b/src/app/ApplicationTemplate.Presentation/Configuration/ApiConfiguration.cs @@ -42,8 +42,6 @@ public static IServiceCollection AddApi(this IServiceCollection services, IConfi .AddAuthentication() .AddPosts(configuration) .AddUserProfile() - .AddMinimumVersion() - .AddKillSwitch() .AddDadJokes(configuration); return services; @@ -55,18 +53,6 @@ private static IServiceCollection AddUserProfile(this IServiceCollection service return services.AddSingleton(); } - private static IServiceCollection AddMinimumVersion(this IServiceCollection services) - { - // This one doesn't have an actual remote API yet. It's always a mock implementation. - return services.AddSingleton(); - } - - private static IServiceCollection AddKillSwitch(this IServiceCollection services) - { - // This one doesn't have an actual remote API yet. It's always a mock implementation. - return services.AddSingleton(); - } - private static IServiceCollection AddAuthentication(this IServiceCollection services) { // This one doesn't have an actual remote API yet. It's always a mock implementation. diff --git a/src/app/ApplicationTemplate.Shared.Views/ApplicationTemplate.Shared.Views.projitems b/src/app/ApplicationTemplate.Shared.Views/ApplicationTemplate.Shared.Views.projitems index 059e75fd1..80a485874 100644 --- a/src/app/ApplicationTemplate.Shared.Views/ApplicationTemplate.Shared.Views.projitems +++ b/src/app/ApplicationTemplate.Shared.Views/ApplicationTemplate.Shared.Views.projitems @@ -118,6 +118,7 @@ + Shell.xaml diff --git a/src/app/ApplicationTemplate.Shared.Views/Configuration/ViewServicesConfiguration.cs b/src/app/ApplicationTemplate.Shared.Views/Configuration/ViewServicesConfiguration.cs index b6d91d4d6..ca0c1ec18 100644 --- a/src/app/ApplicationTemplate.Shared.Views/Configuration/ViewServicesConfiguration.cs +++ b/src/app/ApplicationTemplate.Shared.Views/Configuration/ViewServicesConfiguration.cs @@ -1,4 +1,5 @@ using System.Reactive.Concurrency; +using ApplicationTemplate.DataAccess; using Chinook.DynamicMvvm; using MessageDialogService; using Microsoft.Extensions.DependencyInjection; @@ -34,6 +35,14 @@ public static IServiceCollection AddViewServices(this IServiceCollection service .AddSingleton() .AddSingleton() .AddSingleton() +#if __ANDROID__ + .AddSingleton() + .AddSingleton(s => s.GetRequiredService()) + .AddSingleton(s => s.GetRequiredService()) +#else + .AddSingleton() + .AddSingleton() +#endif .AddMessageDialog(); } diff --git a/src/app/ApplicationTemplate.Shared.Views/RemoteConfigRepository.Android.cs b/src/app/ApplicationTemplate.Shared.Views/RemoteConfigRepository.Android.cs new file mode 100644 index 000000000..3f02c45cf --- /dev/null +++ b/src/app/ApplicationTemplate.Shared.Views/RemoteConfigRepository.Android.cs @@ -0,0 +1,114 @@ +#if __ANDROID__ +using System; +using System.Reactive.Subjects; +using ApplicationTemplate.DataAccess; +using Firebase.RemoteConfig; + +namespace ApplicationTemplate.Views; + +/// +/// RemoteConfigRepository is a repository for Firebase Remote Config. +/// +public sealed class RemoteConfigRepository : IKillSwitchRepository, IMinimumVersionReposiory, IDisposable +{ + private Subject _killSwitchSubject = new Subject(); + private Subject _versionSubject = new Subject(); + + /// + /// Initializes a new instance of the class. + /// + public RemoteConfigRepository() + { + FirebaseRemoteConfig mFirebaseRemoteConfig = FirebaseRemoteConfig.Instance; + FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder() + .SetMinimumFetchIntervalInSeconds(3600) + .Build(); + mFirebaseRemoteConfig.SetConfigSettingsAsync(configSettings); + + FetchRemoteConfig(); + ListenForRealTimeChanges(); + } + + private void FetchRemoteConfig() + { + FirebaseRemoteConfig mFirebaseRemoteConfig = FirebaseRemoteConfig.Instance; + mFirebaseRemoteConfig.FetchAndActivate() + .AddOnCompleteListener(new FetchCompleteListener(this)); + } + + private void ListenForRealTimeChanges() + { + FirebaseRemoteConfig mFirebaseRemoteConfig = FirebaseRemoteConfig.Instance; + + mFirebaseRemoteConfig.AddOnConfigUpdateListener(new ConfigUpdateListener(this)); + } + + /// + public IObservable MinimumVersionObservable => _versionSubject; + + /// + public void CheckMinimumVersion() + { + throw new NotImplementedException(); + } + + /// + public IObservable ObserveKillSwitchActivation() + { + return _killSwitchSubject; + } + + /// + public void Dispose() + { + _killSwitchSubject.Dispose(); + _versionSubject.Dispose(); + } + + private sealed class FetchCompleteListener : Java.Lang.Object, Android.Gms.Tasks.IOnCompleteListener + { + private readonly RemoteConfigRepository _repository; + + public FetchCompleteListener(RemoteConfigRepository repository) + { + _repository = repository; + } + + public void OnComplete(Android.Gms.Tasks.Task task) + { + if (task.IsSuccessful) + { + _repository._killSwitchSubject.OnNext(FirebaseRemoteConfig.Instance.GetBoolean("kill_switch")); + _repository._versionSubject.OnNext(new Version(FirebaseRemoteConfig.Instance.GetString("minimum_version"))); + } + } + } + + private sealed class ConfigUpdateListener : Java.Lang.Object, IConfigUpdateListener + { + private readonly RemoteConfigRepository _repository; + + public ConfigUpdateListener(RemoteConfigRepository repository) + { + _repository = repository; + } + + public void OnError(FirebaseRemoteConfigException p0) + { + throw new NotImplementedException(); + } + + public void OnUpdate(ConfigUpdate p0) + { + var instance = FirebaseRemoteConfig.Instance; + + instance.FetchAndActivate(); + var isKillSwitchActivated = FirebaseRemoteConfig.Instance.GetBoolean("kill_switch"); + var minimumVersion = new Version(FirebaseRemoteConfig.Instance.GetString("minimum_version")); + + _repository._killSwitchSubject.OnNext(isKillSwitchActivated); + _repository._versionSubject.OnNext(minimumVersion); + } + } +} +#endif From 78f04c14094e4c2725920b463ee1d0990c75992b Mon Sep 17 00:00:00 2001 From: Marc-Antoine Soucy Date: Fri, 24 May 2024 10:55:51 -0400 Subject: [PATCH 2/4] a --- .../ApplicationTemplate.Mobile.csproj | 1 + .../ApplicationTemplate.Shared.Views.projitems | 1 + .../RemoteConfigRepository.Ios.cs | 12 ++++++++++++ 3 files changed, 14 insertions(+) create mode 100644 src/app/ApplicationTemplate.Shared.Views/RemoteConfigRepository.Ios.cs diff --git a/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj b/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj index 63c14fb79..c71dd11fd 100644 --- a/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj +++ b/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj @@ -42,6 +42,7 @@ + diff --git a/src/app/ApplicationTemplate.Shared.Views/ApplicationTemplate.Shared.Views.projitems b/src/app/ApplicationTemplate.Shared.Views/ApplicationTemplate.Shared.Views.projitems index 80a485874..9da107edc 100644 --- a/src/app/ApplicationTemplate.Shared.Views/ApplicationTemplate.Shared.Views.projitems +++ b/src/app/ApplicationTemplate.Shared.Views/ApplicationTemplate.Shared.Views.projitems @@ -119,6 +119,7 @@ + Shell.xaml diff --git a/src/app/ApplicationTemplate.Shared.Views/RemoteConfigRepository.Ios.cs b/src/app/ApplicationTemplate.Shared.Views/RemoteConfigRepository.Ios.cs new file mode 100644 index 000000000..1020a4398 --- /dev/null +++ b/src/app/ApplicationTemplate.Shared.Views/RemoteConfigRepository.Ios.cs @@ -0,0 +1,12 @@ +#if __IOS__ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ApplicationTemplate.Views +{ + internal class RemoteConfigRepository + { + } +} +#endif From 785d54748f74e751975001b4387a2ecc8c478b3e Mon Sep 17 00:00:00 2001 From: Marc-Antoine Soucy Date: Mon, 27 May 2024 15:16:39 -0400 Subject: [PATCH 3/4] a --- .../ApplicationTemplate.Mobile.csproj | 7 +- .../ViewServicesConfiguration.cs | 2 +- .../RemoteConfigRepository.Ios.cs | 92 ++++++++++++++++++- 3 files changed, 90 insertions(+), 11 deletions(-) diff --git a/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj b/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj index c71dd11fd..0ae9218fd 100644 --- a/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj +++ b/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj @@ -42,7 +42,6 @@ - @@ -109,11 +108,6 @@ - - - google-services.json - - @@ -219,6 +213,7 @@ + diff --git a/src/app/ApplicationTemplate.Shared.Views/Configuration/ViewServicesConfiguration.cs b/src/app/ApplicationTemplate.Shared.Views/Configuration/ViewServicesConfiguration.cs index ca0c1ec18..ff128d4c9 100644 --- a/src/app/ApplicationTemplate.Shared.Views/Configuration/ViewServicesConfiguration.cs +++ b/src/app/ApplicationTemplate.Shared.Views/Configuration/ViewServicesConfiguration.cs @@ -35,7 +35,7 @@ public static IServiceCollection AddViewServices(this IServiceCollection service .AddSingleton() .AddSingleton() .AddSingleton() -#if __ANDROID__ +#if __ANDROID__ || __IOS__ .AddSingleton() .AddSingleton(s => s.GetRequiredService()) .AddSingleton(s => s.GetRequiredService()) diff --git a/src/app/ApplicationTemplate.Shared.Views/RemoteConfigRepository.Ios.cs b/src/app/ApplicationTemplate.Shared.Views/RemoteConfigRepository.Ios.cs index 1020a4398..4374777de 100644 --- a/src/app/ApplicationTemplate.Shared.Views/RemoteConfigRepository.Ios.cs +++ b/src/app/ApplicationTemplate.Shared.Views/RemoteConfigRepository.Ios.cs @@ -1,12 +1,96 @@ #if __IOS__ using System; -using System.Collections.Generic; -using System.Text; +using System.Reactive.Subjects; +using ApplicationTemplate.DataAccess; +using Firebase.RemoteConfig; +using Foundation; -namespace ApplicationTemplate.Views +namespace ApplicationTemplate.Views; + +/// +/// Implementation of the killswitch repository and the minimum version with Firebase Remote Config for iOS. +/// +public sealed class RemoteConfigRepository : IKillSwitchRepository, IMinimumVersionReposiory, IDisposable { - internal class RemoteConfigRepository + private BehaviorSubject _killSwitchSubject = new BehaviorSubject(default); + private BehaviorSubject _versionSubject = new BehaviorSubject(default); + + /// + /// Initializes a new instance of the class. + /// + public RemoteConfigRepository() + { + Firebase.Core.App.Configure(); + // Enabling developer mode, allows for frequent refreshes of the cache + RemoteConfig.SharedInstance.ConfigSettings = new RemoteConfigSettings(); + + object[] values = { false, "1.0.0" }; + object[] keys = { "kill_switch", "minimum_version" }; + var defaultValues = NSDictionary.FromObjectsAndKeys(values, keys, keys.Length); + RemoteConfig.SharedInstance.SetDefaults(defaultValues); + + // CacheExpirationSeconds is set to CacheExpiration here, indicating that any previously + // fetched and cached config would be considered expired because it would have been fetched + // more than CacheExpiration seconds ago. Thus the next fetch would go to the server unless + // throttling is in progress. The default expiration duration is 43200 (12 hours). + RemoteConfig.SharedInstance.Fetch(10, (status, error) => + { + switch (status) + { + case RemoteConfigFetchStatus.Success: + Console.WriteLine("Config Fetched!"); + + // Call this method to make fetched parameter values available to your app + RemoteConfig.SharedInstance.Activate(); + + _killSwitchSubject.OnNext(RemoteConfig.SharedInstance["kill_switch"].BoolValue); + _versionSubject.OnNext(new Version(RemoteConfig.SharedInstance["minimum_version"].StringValue)); + break; + + case RemoteConfigFetchStatus.Throttled: + case RemoteConfigFetchStatus.NoFetchYet: + case RemoteConfigFetchStatus.Failure: + Console.WriteLine("Config not fetched..."); + break; + } + }); + + RemoteConfig.SharedInstance.AddObserver(new NSString("kill_switch"), NSKeyValueObservingOptions.New, (nSObservedChange) => + { + if (nSObservedChange.NewValue is NSNumber nSNumber) + { + _killSwitchSubject.OnNext(nSNumber.BoolValue); + } + }); + RemoteConfig.SharedInstance.AddObserver(new NSString("minimum_version"), NSKeyValueObservingOptions.New, (nSObservedChange) => + { + if (nSObservedChange.NewValue is NSString nSString) + { + _versionSubject.OnNext(new Version(nSString)); + } + }); + } + + /// + public IObservable MinimumVersionObservable => _versionSubject; + + /// + public void CheckMinimumVersion() + { + throw new NotImplementedException(); + } + + /// + public IObservable ObserveKillSwitchActivation() + { + return _killSwitchSubject; + } + + /// + public void Dispose() { + _killSwitchSubject.Dispose(); + _versionSubject.Dispose(); } } #endif From 2ba75864ec0123bbb14052f760b1479ef8334f96 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Soucy Date: Mon, 27 May 2024 16:06:09 -0400 Subject: [PATCH 4/4] a --- .../ApplicationTemplate.Mobile.csproj | 9 ++++++++- ...kWithSwiftSystemLibrariesWorkaround.targets | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 src/app/ApplicationTemplate.Mobile/LinkWithSwiftSystemLibrariesWorkaround.targets diff --git a/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj b/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj index 0ae9218fd..cb9d5bdf3 100644 --- a/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj +++ b/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj @@ -155,6 +155,9 @@ $(MtouchExtraArgs) --registrar:static $(MtouchExtraArgs) --marshal-objectivec-exceptions:disable + + $(MtouchExtraArgs) --gcc_flags -L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphoneos + $(MtouchExtraArgs) --gcc_flags -L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphonesimulator/ iOS\Entitlements.plist true @@ -213,7 +216,7 @@ - + @@ -281,4 +284,8 @@ + + + + \ No newline at end of file diff --git a/src/app/ApplicationTemplate.Mobile/LinkWithSwiftSystemLibrariesWorkaround.targets b/src/app/ApplicationTemplate.Mobile/LinkWithSwiftSystemLibrariesWorkaround.targets new file mode 100644 index 000000000..9497a7aed --- /dev/null +++ b/src/app/ApplicationTemplate.Mobile/LinkWithSwiftSystemLibrariesWorkaround.targets @@ -0,0 +1,18 @@ + + + + + + <_SwiftPlatform Condition="$(RuntimeIdentifier.StartsWith('iossimulator-'))">iphonesimulator + <_SwiftPlatform Condition="$(RuntimeIdentifier.StartsWith('ios-'))">iphoneos + + + <_CustomLinkFlags Include="-L" /> + <_CustomLinkFlags Include="/usr/lib/swift" /> + <_CustomLinkFlags Include="-L" /> + <_CustomLinkFlags Include="$(_SdkDevPath)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$(_SwiftPlatform)" /> + <_CustomLinkFlags Include="-Wl,-rpath" /> + <_CustomLinkFlags Include="-Wl,/usr/lib/swift" /> + + + \ No newline at end of file