Search code examples
c#mvvmunity-containerprism

Using MVVM, how can I establish a line of communication between a low level service and view model?


I am implementing a media player app with Bluetooth connectivity using Prism and Unity.

The application flow I am working with goes as follows:

  1. User issues a command on a remote device (phone/tablet)
  2. Desktop application receives the Play command via a Bluetooth service
  3. A higher level service processes the metadata and tells the VideoPlaybackViewModel to begin playing

What I have so far

The Bluetooth service has not been implemented yet because I want to finish the other elements first. When it comes time to do that, I will follow this example (https://github.com/saramgsilva/BluetoothSampleUsing32feet.Net).

Following this question (MVVM pattern violation: MediaElement.Play()), I have implemented VideoPlaybackView and VideoPlaybackViewModel.

VideoPlaybackView:

    <UserControl x:Class="Views.VideoPlaybackView"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:ia="http://schemas.microsoft.com/expression/2010/interactivity"
                 xmlns:prism="http://prismlibrary.com/"
                 prism:ViewModelLocator.AutoWireViewModel="True"
                 x:Name="MediaService">
        <ia:Interaction.Triggers>
            <ia:EventTrigger EventName="Loaded">
                <ia:InvokeCommandAction Command="{Binding LoadedCommand}" CommandParameter="{Binding ElementName=MediaService}" />
            </ia:EventTrigger>
        </ia:Interaction.Triggers>
        <Grid>
            <MediaElement 
                x:Name="VideoPlayer"
                Source="{Binding VideoSource}" />
        </Grid>
    </UserControl>

VideoPlaybackViewModel:

    public class VideoPlaybackViewModel : BindableBase {
        private Uri _videoSource;

        public IMediaService MediaService { get; private set; }

        public Uri VideoSource {
            get => _videoSource;
            set => SetProperty(ref _videoSource, value);
        }

        private DelegateCommand<IMediaService> _loadedCommand;

        public DelegateCommand<IMediaService> LoadedCommand {
            get {
                if (_loadedCommand == null) {
                    _loadedCommand =
                        new DelegateCommand<IMediaService>((mediaService) => { MediaService = mediaService; });
                }
                return _loadedCommand;
            }
        }
    }

These are initialized when the VideoPlaybackModule is loaded:

    public class VideoPlaybackModule : IModule {
        private IUnityContainer _container;
        private IRegionManager _regionManager;

        public VideoPlaybackModule(IUnityContainer container, IRegionManager regionManager) {
            _container = container;
            _regionManager = regionManager;
        }

        public void Initialize() {
            _regionManager.RegisterViewWithRegion("MainRegion", typeof(VideoPlaybackView));
        }
    }

I am using modules because I want to learn them.

The goal

What I would like is to have a controller of some sort that can receive the events from the bluetooth service, parse the metadata, update the MediaElement.Source, and somehow send a command to the VideoPlayerViewModel to actually play the video.

Attempts

I have seen the idea of implementing a controller, but I am not sure how I am supposed to initialize it. I come up with the following questions: - How do I hook up the controller to respond to the Bluetooth commands from the Bluetooth service? - Should the controller keep a reference to VideoPlaybackViewModel in order to execute commands?

I think a service could be applicable here as well. If, for example, I created a VideoPlaybackService, how would this service be used? Similar to the controller idea, it would need to handle the processing of metadata before sending the command to VideoPlayerViewModel.

How can I use Prism and Unity to implement this pattern?


Solution

  • There are many way to do this, however it seems the Mediator Pattern might be the droids you are looking for. This will help reduce the Coupling your concerned about

    Mediator pattern

    With the mediator pattern, communication between objects is encapsulated within a mediator object. Objects no longer communicate directly with each other, but instead communicate through the mediator. This reduces the dependencies between communicating objects, thereby reducing coupling.

    Coupling

    In software engineering, coupling is the degree of interdependence between software modules; a measure of how closely connected two routines or modules are; the strength of the relationships between modules.

    In something like MVVM Light you could use the MVVM Light Messenger.

    Its kind of like a Pub/Sub affair, when you Register/Subscribe to a message, and your Decoupled class can Publish/Send said message.

    However since you mentioned you are using Prism, you can use the EventAggregator. Once again this is a Pub/Sub arrangement.

    enter image description here

    An example of sending an event from your Service

    this.eventAggregator  
        .GetEvent<PubSubEvent<BlueToothData>>()  
        .Publish(new BlueToothData { SomeData = someData });
    

    An example of receiving and event in your ViewModel

    var subscriptionToken = this.eventAggregator                             
                                .GetEvent<PubSubEvent<BlueToothData>>()                          
                                .Subscribe((details) =>  
                                       {       
                                           // what ever you want to do here;        
                                       });  
    

    Note : I don't use Prism, however there are is a wealth of resources available for the EventAggregator

    Additional Resources

    Prism Event Aggregator in WPF With MVVM

    Using the Event Aggregator Pattern to Communicate