generated from nventive/Template
-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathCoreStartup.cs
More file actions
316 lines (267 loc) · 10.4 KB
/
CoreStartup.cs
File metadata and controls
316 lines (267 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
using System;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
using ApplicationTemplate.Business;
using ApplicationTemplate.DataAccess;
using ApplicationTemplate.Presentation;
using Chinook.BackButtonManager;
using Chinook.DataLoader;
using Chinook.DynamicMvvm;
using Chinook.DynamicMvvm.Deactivation;
using Chinook.SectionsNavigation;
using Chinook.StackNavigation;
using FluentValidation;
using MessageDialogService;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ReviewService;
using Uno.Disposables;
using Uno.Extensions;
namespace ApplicationTemplate;
public sealed class CoreStartup : CoreStartupBase
{
protected override void PreInitializeServices()
{
}
protected override IHostBuilder InitializeServices(IHostBuilder hostBuilder, string settingsFolderPath, IEnvironmentManager environmentManager)
{
// TODO: Configure your core services from here.
// Core service implementations can be used on any platform.
// Platform specific implementations override the core implementations and are configured from the Startup class.
return hostBuilder
.AddConfiguration(settingsFolderPath, environmentManager)
.ConfigureServices((context, s) => s
.AddDiagnostics(context.Configuration)
.AddMock(context.Configuration)
.AddApi(context.Configuration)
.AddMvvm()
.AddNativePlaformServices()
.AddPersistence()
.AddNavigationCore()
.AddErrorHandling()
.AddSerialization()
.AddLocalization()
.AddReviewServices()
.AddAppServices()
.AddAnalytics()
);
}
protected override void OnInitialized(IServiceProvider services)
{
// At this point all services are registered and can be used.
ViewModelBase.DefaultServiceProvider = services;
InitializeLoggerFactories(services);
HandleUnhandledExceptions(services);
ValidatorOptions.Global.LanguageManager = new FluentValidationLanguageManager();
}
protected override async Task StartServices(IServiceProvider services, bool isFirstStart)
{
if (isFirstStart)
{
// TODO: Start your core services and customize the initial navigation logic here.
StartAutomaticAnalyticsCollection(services);
await services.GetRequiredService<IReviewService>().TrackApplicationLaunched(CancellationToken.None);
NotifyUserOnSessionExpired(services);
services.GetRequiredService<DiagnosticsCountersService>().Start();
await ExecuteInitialNavigation(CancellationToken.None, services);
SuscribeToRequiredUpdate(services);
SuscribeToKillSwitch(services);
}
}
/// <summary>
/// Executes the initial navigation.
/// </summary>
/// <remarks>
/// This can be invoked from diagnostics to reset the app navigation.
/// </remarks>
/// <param name="ct">The cancellation token.</param>
/// <param name="services">The service provider.</param>
public static async Task ExecuteInitialNavigation(CancellationToken ct, IServiceProvider services)
{
var applicationSettingsRepository = services.GetRequiredService<IApplicationSettingsRepository>();
var sectionsNavigator = services.GetRequiredService<ISectionsNavigator>();
var authenticationService = services.GetRequiredService<IAuthenticationService>();
await sectionsNavigator.SetActiveSection(ct, "Login");
var currentSettings = await applicationSettingsRepository.GetAndObserveCurrent().FirstAsync(ct);
if (currentSettings.IsOnboardingCompleted)
{
var isAuthenticated = await authenticationService.GetAndObserveIsAuthenticated().FirstAsync(ct);
if (isAuthenticated)
{
await sectionsNavigator.SetActiveSection(ct, "Home", () => new DadJokesPageViewModel());
}
else
{
await sectionsNavigator.Navigate(ct, () => new LoginPageViewModel(isFirstLogin: false));
}
}
else
{
await sectionsNavigator.Navigate(ct, () => new OnboardingPageViewModel());
}
services.GetRequiredService<IExtendedSplashscreenController>().Dismiss();
}
private void StartAutomaticAnalyticsCollection(IServiceProvider services)
{
var analyticsSink = services.GetRequiredService<IAnalyticsSink>();
var sectionsNavigator = services.GetRequiredService<ISectionsNavigator>();
sectionsNavigator
.ObserveCurrentState()
.Subscribe(analyticsSink.TrackNavigation)
.DisposeWith(Disposables);
}
private void NotifyUserOnSessionExpired(IServiceProvider services)
{
var authenticationService = services.GetRequiredService<IAuthenticationService>();
var messageDialogService = services.GetRequiredService<IMessageDialogService>();
authenticationService
.ObserveSessionExpired()
.SkipWhileSelectMany(async (ct, s) =>
{
await messageDialogService.ShowMessage(ct, mb => mb
.TitleResource("SessionExpired_DialogTitle")
.ContentResource("SessionExpired_DialogBody")
.OkCommand()
);
var navigationController = services.GetRequiredService<ISectionsNavigator>();
foreach (var modal in navigationController.State.Modals)
{
await navigationController.CloseModal(CancellationToken.None);
}
foreach (var stack in navigationController.State.Sections)
{
await ClearNavigationStack(CancellationToken.None, stack.Value);
}
await services.GetRequiredService<ISectionsNavigator>().SetActiveSection(ct, "Login", () => new LoginPageViewModel(isFirstLogin: false), returnToRoot: true);
})
.Subscribe(_ => { }, e => Logger.LogError(e, "Failed to notify user of session expiration."))
.DisposeWith(Disposables);
}
private static async Task ClearNavigationStack(CancellationToken ct, ISectionStackNavigator stack)
{
await stack.Clear(ct);
}
private static void InitializeLoggerFactories(IServiceProvider services)
{
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
StackNavigationConfiguration.LoggerFactory = loggerFactory;
SectionsNavigationConfiguration.LoggerFactory = loggerFactory;
BackButtonManagerConfiguration.LoggerFactory = loggerFactory;
DynamicMvvmConfiguration.LoggerFactory = loggerFactory;
DataLoaderConfiguration.LoggerFactory = loggerFactory;
}
private static void HandleUnhandledExceptions(IServiceProvider services)
{
void OnError(Exception e, bool isTerminating = false) => ErrorConfiguration.OnUnhandledException(e, isTerminating, services);
TaskScheduler.UnobservedTaskException += (s, e) =>
{
OnError(e.Exception);
e.SetObserved();
};
AppDomain.CurrentDomain.UnhandledException += (s, e) =>
{
var exception = e.ExceptionObject as Exception;
if (exception == null)
{
return;
}
OnError(exception, e.IsTerminating);
};
}
private void SuscribeToRequiredUpdate(IServiceProvider services)
{
var updateRequiredService = services.GetRequiredService<IUpdateRequiredService>();
updateRequiredService.UpdateRequired += ForceUpdate;
void ForceUpdate(object sender, EventArgs e)
{
var navigationController = services.GetRequiredService<ISectionsNavigator>();
_ = Task.Run(async () =>
{
await navigationController.NavigateAndClear(CancellationToken.None, () => new ForcedUpdatePageViewModel());
});
updateRequiredService.UpdateRequired -= ForceUpdate;
}
}
private void SuscribeToKillSwitch(IServiceProvider serviceProvider)
{
var killSwitchService = serviceProvider.GetRequiredService<IKillSwitchService>();
var navigationController = serviceProvider.GetRequiredService<ISectionsNavigator>();
killSwitchService.ObserveKillSwitchActivation()
.SelectManyDisposePrevious(async (activated, ct) =>
{
if (Logger.IsEnabled(LogLevel.Trace))
{
Logger.LogTrace("Kill switch activation changed to {Activated}.", activated);
}
if (activated)
{
await OnKillSwitchActivated(ct);
}
else
{
await OnKillSwitchDeactivated(ct);
}
})
.Subscribe()
.DisposeWith(Disposables);
async Task OnKillSwitchActivated(CancellationToken ct)
{
// Clear all navigation stacks and show the kill switch page.
// We clear the navigation stack to avoid weird animations once the kill switch is deactivated.
foreach (var modal in navigationController.State.Modals)
{
await navigationController.CloseModal(ct);
}
await navigationController.NavigateAndClear(ct, () => new KillSwitchPageViewModel());
Logger.LogInformation("Navigated to kill switch page succesfully.");
}
async Task OnKillSwitchDeactivated(CancellationToken ct)
{
if (navigationController.GetActiveViewModel() is KillSwitchPageViewModel)
{
foreach (var stack in navigationController.State.Sections)
{
await ClearNavigationStack(ct, stack.Value);
}
await ExecuteInitialNavigation(ct, serviceProvider);
Logger.LogInformation("The kill switch was deactivated.");
}
}
}
/// <summary>
/// Starts deactivating and reactivating ViewModels based on navigation.
/// </summary>
/// <remarks>
/// To benefit from the deactivation optimizations, you need to do the following:<br/>
/// 1. Call this method from <see cref="StartServices(IServiceProvider, bool)"/>.<br/>
/// 2. Change <see cref="ViewModel"/> so that it inherits from <see cref="DeactivatableViewModelBase"/> rather than just <see cref="ViewModelBase"/>.<br/>
/// 3. Consider using extensions such as GetFromDeactivatableObservable instead of the classic GetFromObservable for your dynamic properties from observables.<br/>
/// ViewModel deactivation shows benefits when your pages have a lot of live updates.<br/>
/// Deactivation means that a page ViewModel unsubscribes from its observable or event dependencies while it's not active (i.e. "visible" to the user).<br/>
/// Reactivation occurs when a page ViewModel becomes active again and causes re-subscriptions.
/// </remarks>
/// <param name="services">The <see cref="IServiceProvider"/>.</param>
private void StartViewModelDeactivation(IServiceProvider services)
{
var sectionsNavigator = services.GetRequiredService<ISectionsNavigator>();
IDeactivatable previousVM = null;
sectionsNavigator.StateChanged += OnSectionsNavigatorStateChanged;
void OnSectionsNavigatorStateChanged(object sender, SectionsNavigatorEventArgs args)
{
var currentVM = sectionsNavigator.GetActiveStackNavigator()?.GetActiveViewModel() as IDeactivatable;
if (previousVM != currentVM)
{
previousVM?.Deactivate();
currentVM?.Reactivate();
}
previousVM = currentVM;
}
}
protected override ILogger GetOrCreateLogger(IServiceProvider serviceProvider)
{
return serviceProvider.GetRequiredService<ILogger<CoreStartup>>();
}
}