Press "Enter" to skip to content

Using State Machine in Xamarin Forms (Part 3)

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!