Skip to content

Commit

Permalink
Merge pull request #25 from BurkusCat/advanced-startup
Browse files Browse the repository at this point in the history
Advanced startup navigation
  • Loading branch information
BurkusCat authored Oct 12, 2023
2 parents 2b97e35 + b30b85a commit d3ef9a6
Show file tree
Hide file tree
Showing 14 changed files with 217 additions and 46 deletions.
40 changes: 38 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,46 @@ navigationService.Navigate(navigationUri);
```

## Choosing the start page of your app
### (navigationService, serviceProvider)

In the below example, we use both an `INavigationService` and an `IServiceProvider`. The `IServiceProvider` is used to resolve the .NET MAUI service, [`IPreferences`](https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/storage/preferences?tabs=android). If a username is stored in preferences, we use the `INavigationService` to go to the `HomePage` of the app. Otherwise, we go to the `LoginPage`.
``` csharp
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseBurkusMvvm(burkusMvvm =>
{
burkusMvvm.OnStart(async (navigationService, serviceProvider) =>
{
var preferences = serviceProvider.GetRequiredService<IPreferences>();

if (preferences.ContainsKey(PreferenceKeys.Username))
{
// we are logged in to the app
await navigationService.Push<HomePage>();
}
else
{
// logged out so we need to get the user to login
await navigationService.Push<LoginPage>();
}
});
})
...
```
### (IServiceProvider serviceProvider)

It is possible to have a service that decides which page is most appropriate to navigate to. This service could decide to:
- Navigate to the "Terms & Conditions" page if the user has not agreed to the latest terms yet
- Navigate to the "Signup / Login" page if the user is logged out
- Navigate to the "Home" page if the user has used the app before and doesn't need to do anything

In the below example, we only resolve a `IServiceProvider` which allows us to resolve `IAppStartupService`. The `IAppStartupService` will call the `INavigationService` internally to do the navigation.
```csharp
public static class MauiProgram
{
Expand All @@ -238,9 +274,9 @@ public static class MauiProgram
.UseMauiApp<App>()
.UseBurkusMvvm(burkusMvvm =>
{
burkusMvvm.OnStart(async (navigationService) =>
burkusMvvm.OnStart(async (IServiceProvider serviceProvider) =>
{
var appStartupService = ServiceResolver.Resolve<IAppStartupService>();
var appStartupService = serviceProvider.GetRequiredService<IAppStartupService>();
await appStartupService.NavigateToFirstPage();
});
})
Expand Down
16 changes: 14 additions & 2 deletions samples/DemoApp/MauiProgram.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using DemoApp.Abstractions;
using DemoApp.Models;
using DemoApp.Services;
using DemoApp.ViewModels;
using DemoApp.Views;
Expand All @@ -15,9 +16,19 @@ public static MauiApp CreateMauiApp()
.UseMauiApp<App>()
.UseBurkusMvvm(burkusMvvm =>
{
burkusMvvm.OnStart(async (navigationService) =>
burkusMvvm.OnStart(async (navigationService, serviceProvider) =>
{
await navigationService.Push<LoginPage>();
var preferences = serviceProvider.GetRequiredService<IPreferences>();
if (preferences.ContainsKey(PreferenceKeys.Username))
{
// we are logged in to the app
await navigationService.Push<HomePage>();
}
else
{
await navigationService.Push<LoginPage>();
}
});
})
.RegisterViewModels()
Expand Down Expand Up @@ -62,6 +73,7 @@ public static MauiAppBuilder RegisterViews(this MauiAppBuilder mauiAppBuilder)

public static MauiAppBuilder RegisterServices(this MauiAppBuilder mauiAppBuilder)
{
mauiAppBuilder.Services.AddSingleton(Preferences.Default);
mauiAppBuilder.Services.AddSingleton<IWeatherService, WeatherService>();

return mauiAppBuilder;
Expand Down
6 changes: 6 additions & 0 deletions samples/DemoApp/Models/NavigationParameterKeys.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace DemoApp.Models;

public static class NavigationParameterKeys
{
public static readonly string Username = "username";
}
10 changes: 10 additions & 0 deletions samples/DemoApp/Models/PreferenceKeys.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace DemoApp.Models;

public static class PreferenceKeys
{
#region Preference keys

public static readonly string Username = "username";

#endregion Preference keys
}
32 changes: 27 additions & 5 deletions samples/DemoApp/ViewModels/ChangeUsernameViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Burkus.Mvvm.Maui;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DemoApp.Models;
using DemoApp.Properties;

namespace DemoApp.ViewModels;

public partial class ChangeUsernameViewModel : BaseViewModel
{
#region Fields

private IPreferences preferences { get; }

private bool wasAnimatedNavigationUsed;

#endregion Fields
Expand All @@ -21,9 +26,11 @@ public partial class ChangeUsernameViewModel : BaseViewModel
#region Constructors

public ChangeUsernameViewModel(
INavigationService navigationService)
INavigationService navigationService,
IPreferences preferences)
: base(navigationService)
{
this.preferences = preferences;
}

#endregion Constructors
Expand All @@ -43,9 +50,15 @@ public override async Task OnNavigatingFrom(NavigationParameters parameters)
{
await base.OnNavigatingFrom(parameters);

// pass 'Username' back regardless if the user presses the button
// or uses a different method of closing the modal (e.g. Android back button)
parameters.Add("username", Username);
if (IsValidUsername())
{
// save username as a preference
preferences.Set(PreferenceKeys.Username, Username);

// pass 'Username' back regardless if the user presses the button
// or uses a different method of closing the modal (e.g. Android back button)
parameters.Add(NavigationParameterKeys.Username, Username);
}

// this is a modal, so we need to close it modally
parameters.UseModalNavigation = true;
Expand All @@ -70,4 +83,13 @@ private async Task Finish()
}

#endregion Commands

#region Private methods

private bool IsValidUsername()
{
return !string.IsNullOrWhiteSpace(Username);
}

#endregion Private methods
}
21 changes: 16 additions & 5 deletions samples/DemoApp/ViewModels/HomeViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DemoApp.Abstractions;
using DemoApp.Models;
using DemoApp.Views;

namespace DemoApp.ViewModels;
Expand All @@ -9,7 +10,9 @@ public partial class HomeViewModel : BaseViewModel
{
#region Fields

protected IWeatherService weatherService { get; }
private IPreferences preferences { get; }

private IWeatherService weatherService { get; }

#endregion Fields

Expand All @@ -27,9 +30,11 @@ public partial class HomeViewModel : BaseViewModel

public HomeViewModel(
INavigationService navigationService,
IPreferences preferences,
IWeatherService weatherService)
: base(navigationService)
{
this.preferences = preferences;
this.weatherService = weatherService;
}

Expand All @@ -41,11 +46,14 @@ public override async Task OnNavigatedTo(NavigationParameters parameters)
{
await base.OnNavigatedTo(parameters);

var usernameValue = parameters.GetValue<string>("username");

if (!string.IsNullOrWhiteSpace(usernameValue))
if (parameters.ContainsKey(NavigationParameterKeys.Username))
{
Username = usernameValue;
Username = parameters.GetValue<string>(NavigationParameterKeys.Username);
}
else
{
// load the username from preferences
Username = preferences.Get<string>(PreferenceKeys.Username, default);
}

CurrentWeatherDescription = weatherService.GetWeatherDescription();
Expand Down Expand Up @@ -95,6 +103,9 @@ private async Task GoToTabbedPageDemo()
[RelayCommand]
private async Task Logout()
{
// remove the username preference
preferences.Remove(PreferenceKeys.Username);

// use the navigate URI syntax to logout with an absolute URI
await navigationService.Navigate("/LoginPage");
}
Expand Down
16 changes: 12 additions & 4 deletions samples/DemoApp/ViewModels/LoginViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DemoApp.Models;
using DemoApp.Properties;
using DemoApp.Views;

Expand All @@ -9,7 +10,9 @@ public partial class LoginViewModel : BaseViewModel
{
#region Fields

protected IDialogService dialogService { get; }
private IDialogService dialogService { get; }

private IPreferences preferences { get; }

#endregion Fields

Expand All @@ -27,10 +30,12 @@ public partial class LoginViewModel : BaseViewModel

public LoginViewModel(
IDialogService dialogService,
INavigationService navigationService)
INavigationService navigationService,
IPreferences preferences)
: base(navigationService)
{
this.dialogService = dialogService;
this.preferences = preferences;
}

#endregion Constructors
Expand All @@ -49,9 +54,12 @@ private async Task Login()
return;
}

// save username as a preference
preferences.Set(PreferenceKeys.Username, Username);

var navigationParameters = new NavigationParameters
{
{ "username", Username },
{ NavigationParameterKeys.Username, Username },
};

// after we login, we replace the stack so the user can't go back to the Login page
Expand All @@ -73,7 +81,7 @@ private async Task Register()

private bool IsValidLoginForm()
{
if (string.IsNullOrEmpty(Username))
if (string.IsNullOrWhiteSpace(Username))
{
dialogService.DisplayAlert(
Resources.Error,
Expand Down
2 changes: 1 addition & 1 deletion src/Abstractions/IBurkusMvvmBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

internal interface IBurkusMvvmBuilder
{
Func<INavigationService, Task> onStartFunc { get; set; }
Func<INavigationService, IServiceProvider, Task> onStartFunc { get; set; }
}
2 changes: 1 addition & 1 deletion src/Builders/InternalBurkusMvvmBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
/// </summary>
internal class InternalBurkusMvvmBuilder : BurkusMvvmBuilder, IBurkusMvvmBuilder
{
public Func<INavigationService, Task> onStartFunc { get; set; }
public Func<INavigationService, IServiceProvider, Task> onStartFunc { get; set; }

Check warning on line 8 in src/Builders/InternalBurkusMvvmBuilder.cs

View workflow job for this annotation

GitHub Actions / build-sample-ci

Non-nullable property 'onStartFunc' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 8 in src/Builders/InternalBurkusMvvmBuilder.cs

View workflow job for this annotation

GitHub Actions / build-plugin-ci

Non-nullable property 'onStartFunc' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 8 in src/Builders/InternalBurkusMvvmBuilder.cs

View workflow job for this annotation

GitHub Actions / release-nuget

Non-nullable property 'onStartFunc' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
}
26 changes: 24 additions & 2 deletions src/Extensions/BurkusMvvmBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ public static class BurkusMvvmBuilderExtensions
/// Define where the app should go first when starting. You must navigate to a page when starting.
/// </summary>
/// <param name="builder">BurkusMvvmBuilder</param>
/// <param name="onStartFunc">Function to perform when starting with access to the <see cref="INavigationService"/></param>
/// <param name="onStartFunc">Function to perform when starting with access to <see cref="INavigationService"/> and <see cref="IServiceProvider"/></param>
/// <returns></returns>
public static BurkusMvvmBuilder OnStart(this BurkusMvvmBuilder builder, Func<INavigationService, Task> onStartFunc)
public static BurkusMvvmBuilder OnStart(this BurkusMvvmBuilder builder, Func<INavigationService, IServiceProvider, Task> onStartFunc)
{
var internalBuilder = builder as InternalBurkusMvvmBuilder;

Expand All @@ -19,4 +19,26 @@ public static BurkusMvvmBuilder OnStart(this BurkusMvvmBuilder builder, Func<INa

return builder;
}

/// <summary>
/// Define where the app should go first when starting. You must navigate to a page when starting.
/// </summary>
/// <param name="builder">BurkusMvvmBuilder</param>
/// <param name="onStartFunc">Function to perform when starting with access to <see cref="INavigationService"/>.</param>
/// <returns></returns>
public static BurkusMvvmBuilder OnStart(this BurkusMvvmBuilder builder, Func<INavigationService, Task> onStartFunc)
{
return OnStart(builder, (nav, sp) => onStartFunc(nav));
}

/// <summary>
/// Define where the app should go first when starting. You must navigate to a page when starting.
/// </summary>
/// <param name="builder">BurkusMvvmBuilder</param>
/// <param name="onStartFunc">Function to perform when starting with access to <see cref="IServiceProvider"/>.</param>
/// <returns></returns>
public static BurkusMvvmBuilder OnStart(this BurkusMvvmBuilder builder, Func<IServiceProvider, Task> onStartFunc)
{
return OnStart(builder, (nav, sp) => onStartFunc(sp));
}
}
3 changes: 2 additions & 1 deletion src/Models/BurkusMvvmApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ protected override Window CreateWindow(IActivationState? activationState)

var burkusMvvmBuilder = ServiceResolver.Resolve<IBurkusMvvmBuilder>();
var navigationService = ServiceResolver.Resolve<INavigationService>();
var serviceProvider = ServiceResolver.GetServiceProvider();

// perform the user's desired initialization logic
if (burkusMvvmBuilder.onStartFunc != null)
{
burkusMvvmBuilder.onStartFunc.Invoke(navigationService);
burkusMvvmBuilder.onStartFunc.Invoke(navigationService, serviceProvider);
}

return base.CreateWindow(activationState);
Expand Down
Loading

0 comments on commit d3ef9a6

Please sign in to comment.