In the second part of this series about State Machine, we covered how to do internal transitions by showing an example that added Rewind/Forward capabilities to the MediaPlayer. In this part, we will cover how to pass parameters to Triggers.
Understanding how to pass parameters
Stateless library allows you to pass parameters to Triggers, this is really handy when you need to work with external parameters. For example, in our video player, if we want to change the seconds used for forwarding/rewinding, we need a way to pass those parameters to the triggers to respond accordingly.
To achieve this we can use parameters the following syntaxis:
var myTriggerParam = stateMachine.SetTriggerParameters<string>(Trigger.MyTrigger); stateMachine.Configure(State.MyState) .OnEntryFrom(myTriggerParam, param => //Handle the param here);
Let’s code it
First, we are going to create two TriggersParameters (For Forward and Rewind). Then will associate them to the triggers VideoTrigger.Forward and VideoTrigger.Rewind.
public class MainViewModel : INotifyPropertyChanged | |
{ | |
public MainViewModel() | |
{ | |
var forwardParamTrigger = _videoPlayerStateMachine.SetTriggerParameters<double>(VideoTrigger.Forward); | |
var rewindParamTrigger = _videoPlayerStateMachine.SetTriggerParameters<double>(VideoTrigger.Rewind); | |
... | |
} | |
} |
Second, we will replace the InternalTransition that we were using for Forward/Rewind with OnEntryFrom instead. Also, add a PermitReentryIf for those triggers since it will allow reentering to the same state.
public class MainViewModel : INotifyPropertyChanged | |
{ | |
public MainViewModel() | |
{ | |
_videoPlayerStateMachine.Configure(VideoState.Playing) | |
.OnActivate(OnStateEntry) | |
.OnEntry(OnStateEntry) | |
.PermitReentryIf(VideoTrigger.Forward) | |
.PermitReentryIf(VideoTrigger.Rewind) | |
.Permit(VideoTrigger.Pause, VideoState.Paused) | |
.Permit(VideoTrigger.Stop, VideoState.Stopped) | |
.OnEntryFrom(rewindParamTrigger, (seconds) => { | |
MainThread.BeginInvokeOnMainThread(() => | |
{ | |
Position = Position.Subtract(TimeSpan.FromSeconds(seconds)); | |
}); | |
}) | |
.OnEntryFrom(forwardParamTrigger, (seconds) => { | |
MainThread.BeginInvokeOnMainThread(() => | |
{ | |
Position = Position.Add(TimeSpan.FromSeconds(seconds)); | |
}); | |
}); | |
} | |
} |
We will create two commands for Forward and Rewind, which will get the parameter set by the user and then fire the trigger with the parameter.
public class MainViewModel : INotifyPropertyChanged | |
{ | |
... | |
public ICommand ForwardCommand { get; } | |
public ICommand RewindCommand { get; } | |
public MainViewModel(){ | |
... | |
ForwardCommand = new Command<double>((seconds) => { | |
_videoPlayerStateMachine.Fire(forwardParamTrigger, seconds); | |
}); | |
RewindCommand = new Command<double>((seconds) => { | |
_videoPlayerStateMachine.Fire(rewindParamTrigger, seconds); | |
}); | |
} | |
} |
Finally, we add the buttons for Rewind and Forward to the UI. Also, a stepper to change the seconds dynamically.
<?xml version="1.0" encoding="utf-8" ?> | |
<ContentPage | |
x:Class="VideoPlayerStateMachine.MainPage" | |
xmlns="http://xamarin.com/schemas/2014/forms" | |
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | |
xmlns:controls="clr-namespace:VideoPlayerStateMachine.Controls" | |
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core" | |
xmlns:local="clr-namespace:VideoPlayerStateMachine" | |
xmlns:viewModels="clr-namespace:VideoPlayerStateMachine.ViewModels" | |
BackgroundColor="Black" | |
ios:Page.UseSafeArea="true"> | |
<ContentPage.BindingContext> | |
<viewModels:MainViewModel /> | |
</ContentPage.BindingContext> | |
<Grid ColumnDefinitions="Auto, *" | |
RowDefinitions="Auto, Auto, Auto, Auto, Auto, Auto"> | |
<Label | |
FontSize="Title" | |
HorizontalOptions="Center" | |
Text="{Binding Status}" | |
TextColor="White" | |
VerticalOptions="Start" | |
Grid.Row="0" | |
Grid.Column="0" | |
Grid.ColumnSpan="2"/> | |
<Label | |
FontSize="Title" | |
HorizontalOptions="Center" | |
Text="{Binding Position, StringFormat='{}{0:hh\\:mm\\:ss}'}" | |
TextColor="White" | |
VerticalOptions="Start" | |
Grid.Row="1" | |
Grid.Column="0" | |
Grid.ColumnSpan="2"/> | |
<controls:VideoPlayer | |
x:Name="VideoPlayer" | |
AutoPlay="{Binding CanAutoPlay}" | |
Command="{Binding VideoActionCommand}" | |
Position="{Binding Position, Mode=TwoWay}" | |
VerticalOptions="Start" | |
HeightRequest="400" | |
Source="https://sec.ch9.ms/ch9/5d93/a1eab4bf-3288-4faf-81c4-294402a85d93/XamarinShow_mid.mp4" | |
Grid.Row="2" | |
Grid.Column="0" | |
Grid.ColumnSpan="2"/> | |
<StackLayout | |
HorizontalOptions="CenterAndExpand" | |
VerticalOptions="Start" | |
Orientation="Horizontal" | |
Grid.Row="3" | |
Grid.Column="0" | |
Grid.ColumnSpan="2"> | |
<Button | |
Command="{Binding TriggerCommand}" | |
CommandParameter="{x:Static local:VideoTrigger.Play}" | |
FontSize="Medium" | |
IsVisible="{Binding CanPlay}" | |
Text="Play" | |
TextColor="White" /> | |
<Button | |
Command="{Binding TriggerCommand}" | |
CommandParameter="{x:Static local:VideoTrigger.Pause}" | |
FontSize="Medium" | |
IsVisible="{Binding CanPause}" | |
Text="Pause" | |
TextColor="White" /> | |
<Button | |
Command="{Binding TriggerCommand}" | |
CommandParameter="{x:Static local:VideoTrigger.Stop}" | |
FontSize="Medium" | |
IsVisible="{Binding CanStop}" | |
Text="Stop" | |
TextColor="White" /> | |
<Button | |
Command="{Binding RewindCommand}" | |
CommandParameter="{Binding Source={x:Reference RewindStepper}, Path=Value}" | |
Margin="40,0,0,0" | |
WidthRequest="30" | |
HeightRequest="30" | |
VerticalOptions="Center" | |
FontSize="Small" | |
CornerRadius="15" | |
IsVisible="{Binding CanStop}" | |
Text="{Binding Source={x:Reference RewindStepper}, Path=Value, StringFormat='-{0}'}" | |
BackgroundColor="White" | |
TextColor="Black" /> | |
<Button | |
Command="{Binding ForwardCommand}" | |
CommandParameter="{Binding Source={x:Reference ForwardStepper}, Path=Value}" | |
WidthRequest="30" | |
HeightRequest="30" | |
VerticalOptions="Center" | |
FontSize="Small" | |
CornerRadius="15" | |
IsVisible="{Binding CanStop}" | |
Text="{Binding Source={x:Reference ForwardStepper}, Path=Value, StringFormat='+{0}'}" | |
BackgroundColor="White" | |
TextColor="Black" /> | |
</StackLayout> | |
<Label Text="Rewind Seconds (-5,+5)" | |
TextColor="White" | |
IsVisible="{Binding CanStop}" | |
Grid.Row="4" | |
Grid.Column="0"/> | |
<Stepper Maximum="30" | |
Minimum="5" | |
Increment="5" | |
BackgroundColor="White" | |
IsVisible="{Binding CanStop}" | |
x:Name="RewindStepper" | |
Grid.Row="4" | |
Grid.Column="1"/> | |
<Label Text="Forward Seconds (-5,+5)" | |
IsVisible="{Binding CanStop}" | |
TextColor="White" | |
Grid.Row="5" | |
Grid.Column="0"/> | |
<Stepper Maximum="30" | |
Minimum="5" | |
Increment="5" | |
IsVisible="{Binding CanStop}" | |
BackgroundColor="White" | |
x:Name="ForwardStepper" | |
Grid.Row="5" | |
Grid.Column="1"/> | |
</Grid> | |
</ContentPage> |
Result

That’s all for now!
Check the full source code here.
Happy Stateless!