Skip to content

Commit bdbd6c5

Browse files
committed
Updated sample docs
1 parent 4ae70cb commit bdbd6c5

File tree

1 file changed

+50
-13
lines changed

1 file changed

+50
-13
lines changed

docs/mvvm/PuttingThingsTogether.md

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ public sealed class PostWidgetViewModel : ObservableRecipient
136136
protected override void OnActivated()
137137
{
138138
// We use a method group here, but a lambda expression is also valid
139-
Messenger.Register<PropertyChangedMessage<object>>(this, Receive);
139+
Messenger.Register<PostWidgetViewModel, PropertyChangedMessage<object>>(this, (r, m) => r.Receive(m));
140140
}
141141

142142
/// <inheritdoc/>
@@ -156,9 +156,9 @@ We now have a draft of our viewmodels ready, and we can start looking into the s
156156
## Building the settings service
157157

158158
> [!NOTE]
159-
> The sample is built using the service locator pattern, but this is not the only possible pattern to use to manage service. The MVVM Toolkit also fully supports the dependency injection pattern, and you can choose the one you prefer depending on the architecture of your application, the available development time or personal preference.
159+
> The sample is built using the dependency injection pattern, which is the recommended approach to deal with services in viewmodels. It is also possible to use other patterns, such as the service locator pattern, but the MVVM Toolkit does not offer built-in APIs to enable that.
160160
161-
Since we want some of our properties to be saved and persisted, we need a way for viewmodels to be able to interact with the application settings. We shouldn't use platform-specific APIs directly in our viewmodels though, as that would prevent us from having all our viewmodels in a portable, .NET Standard project. We can solve this issue by using services, and the `Ioc` class. The idea is to write interfaces that represent all the API surface that we need, and then to implement platform-specific types implementing this interface on all our application targets. The viewmodels will only interact with the interfaces, so they will not have any strong reference to any platform-specific type at all.
161+
Since we want some of our properties to be saved and persisted, we need a way for viewmodels to be able to interact with the application settings. We shouldn't use platform-specific APIs directly in our viewmodels though, as that would prevent us from having all our viewmodels in a portable, .NET Standard project. We can solve this issue by using services, and the APIs in the `Microsoft.Extensions.DependencyInjection` library to setup our `IServiceProvider` instance for the application. The idea is to write interfaces that represent all the API surface that we need, and then to implement platform-specific types implementing this interface on all our application targets. The viewmodels will only interact with the interfaces, so they will not have any strong reference to any platform-specific type at all.
162162

163163
Here's a simple interface for a settings service:
164164

@@ -189,14 +189,16 @@ We can assume that platform-specific types implementing this interface will take
189189
/// <summary>
190190
/// Gets the <see cref="ISettingsService"/> instance to use.
191191
/// </summary>
192-
private readonly ISettingsService SettingsService = Ioc.Default.GetRequiredService<ISettingsService>();
192+
private readonly ISettingsService SettingsService;
193193

194194
/// <summary>
195195
/// Creates a new <see cref="SubredditWidgetViewModel"/> instance.
196196
/// </summary>
197-
public SubredditWidgetViewModel()
197+
public SubredditWidgetViewModel(ISettingsService settingsService)
198198
{
199-
selectedSubreddit = SettingsService.GetValue<string>(nameof(SelectedSubreddit)) ?? Subreddits[0];
199+
SettingsService = settingsService;
200+
201+
selectedSubreddit = settingsService.GetValue<string>(nameof(SelectedSubreddit)) ?? Subreddits[0];
200202
}
201203

202204
private string selectedSubreddit;
@@ -216,7 +218,7 @@ public string SelectedSubreddit
216218
}
217219
```
218220

219-
Here we're using the service locator pattern, which is one of the supported patterns by the `Ioc` class. We've declared an `ISettingsService SettingsService` field that just stores our settings service (we're retrieving it from the static `Ioc.Default` instance), and then we're initializing the `SelectedSubreddit` property in the constructor, by either using the previous value or just the first available subreddit. Then we also modified the `SelectedSubreddit` setter, so that it will also use the settings service to save the new value to disk.
221+
Here we're using dependency injection and constructor injection, as mentioned above. We've declared an `ISettingsService SettingsService` field that just stores our settings service (which we're receiving as parameter in the viewmodel constructor), and then we're initializing the `SelectedSubreddit` property in the constructor, by either using the previous value or just the first available subreddit. Then we also modified the `SelectedSubreddit` setter, so that it will also use the settings service to save the new value to disk.
220222

221223
Great! Now we just need to write a platform specific version of this service, this time directly inside one of our app projects. Here's what that service might look like on UWP:
222224

@@ -248,16 +250,29 @@ public sealed class SettingsService : ISettingsService
248250
}
249251
```
250252

251-
The final piece of the puzzle is to inject this platform-specific service into our service provider instance, which in this case is the `Ioc.Default` instance. We can do this at startup, like so:
253+
The final piece of the puzzle is to inject this platform-specific service into our service provider instance. We can do this at startup, like so:
252254

253255
```csharp
254-
Ioc.Default.ConfigureServices(services =>
256+
/// <summary>
257+
/// Gets the <see cref="IServiceProvider"/> instance to resolve application services.
258+
/// </summary>
259+
public IServiceProvider Services { get; }
260+
261+
/// <summary>
262+
/// Configures the services for the application.
263+
/// </summary>
264+
private static IServiceProvider ConfigureServices()
255265
{
266+
var services = new ServiceCollection();
267+
256268
services.AddSingleton<ISettingsService, SettingsService>();
257-
});
269+
services.AddTransient<PostWidgetViewModel>();
270+
271+
return services.BuildServiceProvider();
272+
}
258273
```
259274

260-
This will register a singleton instance of our `SettingsService` as a type implementing `ISettingsService`. This means that every time one of our viewmodels uses `Ioc.Default.GetService<ISettingsService>()` while the app in use is the UWP one, it will receive a `SettingsService` instance, which will use the UWP APIs behind the scene to manipulate settings. Perfect!
275+
This will register a singleton instance of our `SettingsService` as a type implementing `ISettingsService`. We are also registering the `PostWidgetViewModel` as a transient service, meaning every time we retrieve an instance, it will be a new one (you can imagine this being useful if wanted to have multiple, independent post widgets). This means that every time we resolve an `ISettingsService` instance while the app in use is the UWP one, it will receive a `SettingsService` instance, which will use the UWP APIs behind the scene to manipulate settings. Perfect!
261276

262277
## Building the Reddit service
263278

@@ -329,11 +344,19 @@ We have added a new `IRedditService` field to store our service, just like we di
329344
The last missing piece now is just to inject the actual service into our service provider. The big difference in this case is that by using `refit` we don't actually need to implement the service at all! The library will automatically create a type implementing the service for us, behind the scenes. So we only need to get an `IRedditService` instance and inject it directly, like so:
330345

331346
```csharp
332-
Ioc.Default.ConfigureServices(services =>
347+
/// <summary>
348+
/// Configures the services for the application.
349+
/// </summary>
350+
private static IServiceProvider ConfigureServices()
333351
{
352+
var services = new ServiceCollection();
353+
334354
services.AddSingleton<ISettingsService, SettingsService>();
335355
services.AddSingleton(RestService.For<IRedditService>("https://www.reddit.com/"));
336-
});
356+
services.AddTransient<PostWidgetViewModel>();
357+
358+
return services.BuildServiceProvider();
359+
}
337360
```
338361

339362
And that's all we need to do! We now have all our backend ready to use, including two custom services that we created specifically for this app! 🎉
@@ -342,6 +365,20 @@ And that's all we need to do! We now have all our backend ready to use, includin
342365

343366
Now that all the backend is completed, we can write the UI for our widgets. Note how using the MVVM pattern let us focus exclusively on the business logic at first, without having to write any UI-related code until now. Here we'll remove all the UI code that's not interacting with our viewmodels, for simplicity, and we'll go through each different control one by one. The full source code can be found in the sample app.
344367

368+
Before going through the various controls, here's how we can resolve viewmodels for all the different views in our application (eg. the `PostWidgetView`):
369+
370+
```csharp
371+
public PostWidgetView()
372+
{
373+
this.InitializeComponent();
374+
this.DataContext = App.Current.Services.GetService<PostWidgetViewModel>();
375+
}
376+
377+
public PostWidgetViewModel ViewModel => (PostWidgetViewModel)DataContext;
378+
```
379+
380+
We're using our `IServiceProvider` instance to resolve the `PostWidgetViewModel` object we need, which is then assigned to the data context property. We're also creating a strongly-typed `ViewModel` property that simply casts the data context to the correct viewmodel type - this is needed to enable `x:Bind` in the XAML code.
381+
345382
Let's start with the subreddit widget, which features a `ComboBox` to select a subreddit, a `Button` to refresh the feed, a `ListView` to display posts and a `ProgressBar` to indicate when the feed is loading. We'll assume that the `ViewModel` property represents an instance of the viewmodel we've described before - this can be declared either in XAML or directly in code behind.
346383

347384
### Subreddit selector:

0 commit comments

Comments
 (0)