Documentation : Relations - Windows Phone 7

This example shows how to work with models relations using Mobeelizer.

Application consists of two models. First one Order has two fields - name and status, secone one is an order item which contains title field and field definig relation between models. Main screen of application contains list of model entities and two buttons. User can add new order and it's items. He can also synchronize with mobeelizer cloud. There are two users in the system and there's a possibility to switch user on application life time.

Design Your App

To complete this example we need to create new application called Relations in Mobeelizer App Designer.

Next we will configure model. We have to go to 'Models' section and create:

  • OrderEntity (CRUD operations avaliable for everyone) with the following fields:

    Name
    Required property
    Type
    Default value property
    Additional properties
    Credentials
    nameyestext--default
    statusyesinteger1min: 1, max: 5default

     

  • OrderItemEntity (CRUD operations avaliable for everyone) with the following fields:

    Name
    Required property
    Type
    Default value property
    Additional properties
    Credentials
    titleyestext--default
    orderGuidyesbelongs to OrderEntity--default


For this example we leave conflict resolving setting in overwrite state. 

Next section to configure is 'Groups & Roles', by default there is one group called 'users' and one device category 'mobile' and this two together create role 'users-mobile'. This default configuration is perfect for our example and you don't have to change it. 
When everything is done in Create mode, deploy our application to test environment, and create two users with passwords:

  • a - usera
  • b - userb

Last thing to do in App Designer is download configured template with default settings.

Use the Mobeelizer SDK

Open downloaded project in Microsoft Visual Studio (Windows Phone SDK 7.1 must be installed on your computer). There is a few files generated in our project, applicatiom.xml, app.config (read more) and 'Model/File.cs' - our model class.

Create app skeleton

In our example we will use MVVM pattern. To make it, create two folders in solution explorer - 'View' and 'ViewModel'. Next move 'MainPage.xaml' file to 'View' folder and change path to navigation page in 'WMAppManifet.xml' file - DefaultTask node - to 'View/MainPage.xaml'.

You also need to add this files into project:

  • userAIcon.fw.png - User a icon, will be displayed in entities list item. Add it into project root folder and set build action property to Content.
  • userBIcon.fw.png - User b icon, will be displayed in entities list item. Add it into project root folder and set build action property to Content.
  • OwnerNameToIconConverter.cs - Converter which converts owner name into path to user icon. Add it into ViewModel folder. 
  • movies.xml - Generated list of movies, put it in the project and set build action value to 'content'.

Prepare main screen

Now we have already created skeleton of our project, next things to do is creating View and ViewModel. As you read before our main screen will contains list of orders and its items, to make it we will create ListBox control which will be binded to Entities list. Each order will contains name label, information about entity owner, list of its items and button to add new item. Everything what I just described will be defined in ListBox.ItemTemplate. On the bottom of the screen there will be two buttons to add new order and sychronize with Mobeelizer cloud. Let's take a look at code below.

View/MainPage.xaml
<phone:PhoneApplicationPage 
    ...
    xmlns:localControls="....View"
    xmlns:mobeelizerConverters="clr-namespace:MobeelizerConverters"
	...
    >
    
    <phone:PhoneApplicationPage.Resources>
        <mobeelizerConverters:OwnerNameToIconConverter x:Key="OwnerConverter"/>
    </phone:PhoneApplicationPage.Resources>
    
     <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="MOBEELIZER TUTORIAL" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="Relations" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>
        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Grid.RowDefinitions>
                <RowDefinition Height="100" />
                <RowDefinition Height="*" />
                <RowDefinition Height="100"/>
            </Grid.RowDefinitions>
            <Grid Grid.Row="0">
                <!-- Place for switch user control -->
            </Grid>
            <ListBox Grid.Row="1" ItemsSource="{Binding Entities}" >
                <!--Order template-->
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid Margin="1" Width="430">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="*"/>
                                <RowDefinition Height="*"/>
                                <RowDefinition Height="70"/>
                            </Grid.RowDefinitions>
                            <!--Order name and information about entity owner-->
                            <StackPanel>
                                <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition/>
                                    <ColumnDefinition Width="50"/>
                                </Grid.ColumnDefinitions>
                                    <TextBlock Text="{Binding Name, StringFormat='Order {0}'}"  Style="{StaticResource PhoneTextLargeStyle}"/>
                                    <Image  Source="{Binding Owner, Converter={StaticResource OwnerConverter}}" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Right" />
                                 </Grid>
                                <TextBlock Margin="20,0,0,0" Text="{Binding Status, StringFormat='Status: {0}'}"/>
                            </StackPanel>
                            <!--List of order items-->
                            <ListBox Grid.Row="1" Margin="30,0,0,0" ItemsSource="{Binding Items}" >
                                <ListBox.ItemTemplate>
                                    <DataTemplate>
                                        <Grid Width="400" Margin="1">
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition/>
                                                <ColumnDefinition Width="50"/>
                                            </Grid.ColumnDefinitions>
                                            <StackPanel VerticalAlignment="Center" Orientation="Horizontal">
                                                <TextBlock Text="- "/>
                                                <TextBlock Text="{Binding Title}" />
                                            </StackPanel>
                                            <Image Source="{Binding Owner, Converter={StaticResource OwnerConverter}}" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Right" />
                                        </Grid>
                                    </DataTemplate>
                                </ListBox.ItemTemplate>
                            </ListBox>
                            <!--Button to add new item-->
                            <Button  Margin="30,0,0,0" Content="Add item" Grid.Row="2" Command="{Binding AddItemCommand}" CommandParameter="{Binding}"/>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
            <StackPanel Grid.Row="2" Orientation="Horizontal">
                <Button Content="Add" Width="230" Command="{Binding AddEntityCommand}" CommandParameter="{Binding Entities}"/>
                <Button Content="Sync" Width="230" Command="{Binding SyncCommand}" CommandParameter="{Binding Entities}"/>
            </StackPanel>
        </Grid>
    </Grid>
</phone:PhoneApplicationPage>

Before we start implementing ViewModel for MainPage.xaml we need to take a closer look at just created code. We can see that 'Order' entity should also contain Items list and command to add new item, but now there are not properties like these. There is not a problem to add them becouse Mobeelizer model here is a normal class. We will also implement INotifyPropertyChanged interface in OrderEntity class to notify View about Items list changes. 

Model/OrderEntity.cs
[Table]
public class OrderEntity : MobeelizerWp7Model, INotifyPropertyChanged
{
    public OrderEntity()
    {
        // TODO: Init add item command property.
    }
	...
	
	public ICommand AddItemCommand { get; set; }
	
    public List<OrderItemEntity> Items 
    { 
        get
        {
            List<OrderItemEntity> list = new List<OrderItemEntity>();
            if (Mobeelizer.IsLoggedIn)
            {
                using (IMobeelizerTransaction transaction = Mobeelizer.GetDatabase().BeginTransaction())
                {
				    // Finding current order items in database.
                    var query = from OrderItemEntity i in transaction.GetModelSet<OrderItemEntity>() where i.OrderGuid == Guid select i;
                    foreach (OrderItemEntity item in query)
                    {
                        list.Add(item);
                    }
                }
            }
            return list;
        }
    }
   
    public event PropertyChangedEventHandler  PropertyChanged;
    public void RaiseItemsChanged()
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs("Items"));
        }
    }
}

We can create MainPageViewModel class now and put it in ViewModel folder. 

ViewModel/MainPageViewModel.cs
public class MainPageViewModel
{
    public MainPageViewModel()
    {
        Entities = new ObservableCollection<OrderEntity>();
    }
    public ICommand AddEntityCommand { get; set; }

    public ICommand SyncCommand { get; set; }

    public ObservableCollection<OrderEntity> Entities { get; set; }
}

To connect our View with just created ViewModel, open MainPage.xaml.cs file and implement MainPage class constructor like this:

View/MainPage.xaml.cs
...
    public MainPage()
    {
        InitializeComponent();
        this.DataContext = new MainPageViewModel();
    }
...

Create switch user control

Before we start implementing add entity command and sync command we have to complete switching users things. We will create separated control for that. (If you already created previous example you can just copy it). In View folder create new Windows Phone User Control and name it SwitchUserControl. This control will contains grid with two columns, first column will present information which user is currently loged in and second will contains button to switch user.

View/SwitchUserControl.xaml
...
	<Grid x:Name="LayoutRoot">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="150"/>
        </Grid.ColumnDefinitions>
        <TextBlock Text="{Binding CurrentUser, StringFormat='Current user is: {0}'}" Style="{StaticResource PhoneTextLargeStyle}" VerticalAlignment="Center"/>
        <Button Grid.Column="1" Content="Switch" Command="{Binding SwitchUser}"/>
    </Grid> 
...

Code below is ViewModel definition for just created control. There are two properties, first one contains information about currently logged in user and second one is a command to switch users. Class implements INotifyPropertyChanged interface to support notification about value changes of properties.

ViewModel/SwitchUserControlViewModel.cs
   	public class SwitchUserControlViewModel : INotifyPropertyChanged
    {
	    public static String CurrentlyLoggendInUser;
        public SwitchUserControlViewModel()
        {
            // TODO 
        }
        public String CurrentUser
        {
            get
            {
                return CurrentlyLoggendInUser;
            }
        }

        public ICommand SwitchUser { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(String propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

And again remember to connect ViewModel and View. You need to also create our new control in MainPage grid row. 

SwitchUserControl.xaml.cs
...
        public SwitchUserControl()
        {
            this.DataContext = new SwitchUserControlViewModel();
            InitializeComponent();
        }
...
View/MainPage.xaml
...
<Grid Grid.Row="0">
	<localControls:SwitchUserControl />
</Grid>
... 

While View and ViewModel are almost done it is time to write some buisness logic. We will start with SwitchUserCommand, this command will be responsible for switching between users and also have to notify other classes that user was switched. Create new class in Model folder and implement it like in code below. You can see that there are two events UserSwitched and CanExecuteChanged, first one notify about switching user, second will notify View that execution lock may change. There are also four methods: 'CanExecute' which returns execution lock value, 'Execute' which contains logging in code, 'RaiseCanExecuteChanged' to rise 'CanExecuteChanged' event and 'Mobeelizer_SyncStatusChanged' which is triggered when synchronization status change.

Model/SwitchUserCommand.cs
public class SwitchUserCommand : ICommand
{
    private bool canSwitchUser = true;
    private const String USER_A_PASSWORD = "usera";
    private const String USER_B_PASSWORD = "userb";
    private const String USER_A_LOGIN = "a";
    private const String USER_B_LOGIN = "b";

    public event EventHandler CanExecuteChanged;
    public static event EventHandler UserSwitched;

	public SwitchUserCommand()
    {
        Mobeelizer.SyncStatusChanged += new MobeelizerSyncStatusChangedEventHandler(Mobeelizer_SyncStatusChanged);
    }

    public bool CanExecute(object parameter)
    {
        return canSwitchUser;
    }

    public void Execute(object parameter)
    {
       canSwitchUser = false;
       RaiseCanExecuteChanged();
       switch (SwitchUserControlViewModel.CurrentlyLoggendInUser)
       {
          case USER_A_LOGIN:
               Mobeelizer.Login(USER_B_LOGIN, USER_B_PASSWORD, error =>
               {
                    if (error == null)
                    {
                        SwitchUserControlViewModel.CurrentlyLoggendInUser = USER_B_LOGIN;
                    }
                    else
                    {
                        SwitchUserControlViewModel.CurrentlyLoggendInUser = String.Empty;
                    }
                    EventHandler handler = UserSwitched;
                    if (handler != null)
                    {
                        handler(this, EventArgs.Empty);
                    }
                    Deployment.Current.Dispatcher.BeginInvoke(new Action(() =>
                    {
                        canSwitchUser = true;
                        RaiseCanExecuteChanged();
                    }));
                });
                break;
            default:
            case USER_B_LOGIN:
                    Mobeelizer.Login(USER_A_LOGIN, USER_A_PASSWORD, error =>
                    {
                        if (error == null)
                        {
                            SwitchUserControlViewModel.CurrentlyLoggendInUser = USER_A_LOGIN;
                        }
                        else
                        {
                            SwitchUserControlViewModel.CurrentlyLoggendInUser = String.Empty;
                        }
                        EventHandler handler = UserSwitched;
                        if (handler != null)
                        {
                            handler(this, EventArgs.Empty);
                        }
                        Deployment.Current.Dispatcher.BeginInvoke(new Action(() =>
                        {
                            canSwitchUser = true;
                            RaiseCanExecuteChanged();
                        }));
                    });
                break;
        }
     }

    public void RaiseCanExecuteChanged()
    {
       EventHandler handler = CanExecuteChanged;
       if (handler != null)
       {
            handler(this, EventArgs.Empty);
       }
    }

    void Mobeelizer_SyncStatusChanged(MobeelizerSyncStatus status)
    {
        if (status == MobeelizerSyncStatus.FINISHED_WITH_SUCCESS || status == MobeelizerSyncStatus.FINISHED_WITH_FAILURE
           || status == MobeelizerSyncStatus.NONE)
        {
           canSwitchUser = true;
        }
        else
        {
            canSwitchUser = false;
        }
        Deployment.Current.Dispatcher.BeginInvoke(new Action(() =>
        {
            RaiseCanExecuteChanged();
        }));
    }
}

This code is pretty simple, when user click on switch user button, Execute method will be invoked, while method is executing, execution lock is set, when loging in is finished 'UserSwitched' event will be raised. User can't change while synchronization process is in progress, this functionality is implemented in 'Mobeelizer_SyncStatusChanged' method - when synchronization is in progress, lock flag is up. 

Last thing to do in switching user code is implementing SwichUserControlViewModel constructor. 

ViewModel/SwitchUserControlViewModel.cs
...
 		public SwitchUserControlViewModel()
        {
            SwitchUserCommand command = new SwitchUserCommand();
            SwitchUserCommand.UserSwitched += (object sender, EventArgs e) =>
                {
                    Deployment.Current.Dispatcher.BeginInvoke(new Action(() =>
                    {
                        RaisePropertyChanged("CurrentUser");
                    }));
                };
            this.SwitchUser = command;
            this.SwitchUser.Execute(null);
        }
...

Get current user entities

Nice, we can login to mobeelizer and switch between users. It is time to get current user entities. Go back to MainPageViewModel class, in constructor add new method to UserSwitched event and fill orders list when user will be switched.

ViewModel/MainPageViewModel.cs
...
public MainPageViewModel()
{
    Entities = new ObservableCollection<OrderEntity>();
    SwitchUserCommand.UserSwitched += new System.EventHandler(UserSwitched);
}

void UserSwitched(object sender, System.EventArgs e)
{
    Deployment.Current.Dispatcher.BeginInvoke(new Action(() =>
    {
        RefreshCurrenUserEntitesList();
    }));
}

private void RefreshCurrenUserEntitesList()
{
    Entities.Clear();
    if (Mobeelizer.IsLoggedIn)
    {
        using (IMobeelizerTransaction transaction = Mobeelizer.GetDatabase().BeginTransaction())
        {
            foreach (OrderEntity entity in transaction.GetModelSet<OrderEntity>())
            {
                Entities.Add(entity);
            }
        }
    }
}
...

Add entity into database

Our entities list is now visible, it is time to implement AddEntityCommand. Create new class in Model folder, called AddOrderCommand and take a look on code below.

Model/AddOrderCommand.cs
public class AddOrderCommand : ICommand
{
    public bool CanExecute(object parameter)
    {
        return Mobeelizer.IsLoggedIn;
    }
    public event EventHandler CanExecuteChanged;
    public void Execute(object parameter)
    {
        ObservableCollection<OrderEntity> entities = (ObservableCollection<OrderEntity>)parameter;
        using (IMobeelizerTransaction transaction = Mobeelizer.GetDatabase().BeginTransaction())
        {
            OrderEntity entity = new OrderEntity();
            var query = from e in transaction.GetModelSet<OrderEntity>() select e;
            entity.Name = String.Format("{0}/0000{1}", SwitchUserControlViewModel.CurrentlyLoggendInUser, query.ToList().Count);
            entity.Status = new Random().Next(1, 5);
            transaction.GetModelSet<OrderEntity>().InsertOnSubmit(entity);
            transaction.SubmitChanges();
            Deployment.Current.Dispatcher.BeginInvoke(new Action(() =>
            {
                entities.Add(entity);
            }));
        }
    }
    public void RaiseCanExecuteChanged()
    {
        EventHandler handler = CanExecuteChanged;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }
}

It is easy, right? In Execute method we are creating new OrderEntity object and generating name for it, then we are adding it into the list and database. There is one thing more, CanExecute method returns true only if user is loged in to Mobeelizer, this is because there is no access to database for not authorized users. 

Synchronize with mobeelizer

It is time for synchronization now, it will be as easy as adding new entity to list. Create new class SyncCommand in Model folder and implement it like this:

Model/SyncCommand.cs
public class SyncCommand : ICommand
{
    private bool canSyncFlag = true;
    public bool CanExecute(object parameter)
    {
        return Mobeelizer.IsLoggedIn && canSyncFlag;
    }
    public event EventHandler CanExecuteChanged;
    public void Execute(object parameter)
    {
        ObservableCollection<OrderEntity> entities = (ObservableCollection<OrderEntity>)parameter;
        canSyncFlag = false;
        RaiseCanExecuteChanged();
        Mobeelizer.Sync(error =>
        {
            Deployment.Current.Dispatcher.BeginInvoke(new Action(() =>
            {
                if (error == null)
                {
                    entities.Clear();
                    if (Mobeelizer.IsLoggedIn)
                    {
                        using (IMobeelizerTransaction transaction = Mobeelizer.GetDatabase().BeginTransaction())
                        {
                            foreach (OrderEntity entity in transaction.GetModelSet<OrderEntity>())
                            {
                                entities.Add(entity);
                            }
                        }
                    }
                }
                canSyncFlag = true;
                RaiseCanExecuteChanged();
            }));
        });
    }
    public void RaiseCanExecuteChanged()
    {
        EventHandler handler = CanExecuteChanged;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }
}

You can synchronize your data only if user is logged in and if other sync process is not in progress. This condition is defined in CanExecute method. Let's take a look at Execute method. It is actually just calling Mobeelizer sync method. When synchronization finished without any errors given entities list is refreshed, and that is all.

Now create our new classes in MainPageViewModel constructor, and raise CanExecuteChange events when user change.

ViewModel/MainPageViewModel.cs
...
public MainPageViewModel()
{
    Entities = new ObservableCollection<OrderEntity>();
    AddEntityCommand = new AddOrderCommand();
    SyncCommand = new SyncCommand();
    SwitchUserCommand.UserSwitched += new System.EventHandler(UserSwitched);
}
void UserSwitched(object sender, System.EventArgs e)
{
    Deployment.Current.Dispatcher.BeginInvoke(new Action(() =>
    {
        (AddEntityCommand as AddOrderCommand).RaiseCanExecuteChanged();
        (SyncCommand as SyncCommand).RaiseCanExecuteChanged();
        RefreshCurrenUserEntitesList();
    }));
}
...

Add new item and create relation

On the begining of this tutorial we have created AddItemCommand property in OrderEntity class but we have not implemented it yet. Let's do it now. We will create new entity using random title value from predefined list. Download this xml file, add it into solution and set build action to content.


Add new class AddOrderItemCommand into Model folder and define it like this.

Model/AddOrderItemCommand.cs
public class AddOrderItemCommand : ICommand
{
    private static List<String> movieTitles;
    private Random rand = new Random();
    public AddOrderItemCommand()
    {
        if (movieTitles == null)
        {
            XDocument movies = XDocument.Load("movies.xml");
            movieTitles = new List<string>();
            foreach (XElement title in movies.Root.Element("movieTitles").Elements("item"))
            {
                movieTitles.Add(title.Value);
            }
        }
    }
    public bool CanExecute(object parameter)
    {
        return true;
    }
    public event EventHandler CanExecuteChanged;
    public void Execute(object parameter)
    {
        OrderEntity order = parameter as OrderEntity;
        using (IMobeelizerTransaction transaction = Mobeelizer.GetDatabase().BeginTransaction())
        {
            OrderItemEntity item = new OrderItemEntity();
            item.Title = movieTitles[rand.Next(0, movieTitles.Count - 1)];
            item.OrderGuid = order.Guid; // Defining relation between entities.
            transaction.GetModelSet<OrderItemEntity>().InsertOnSubmit(item);
            transaction.SubmitChanges();
        }
        order.RaiseItemsChanged(); // Notify that items list of this entity changed.
    }
}

In constructor we are loading movie titles from xml file and storing them in list. When Execute method is triggered we are getting random value from list, creating new entity and saving it in database. One the end of the method we are notifying about order items changes.

Last thing to do is creating just implemented command in OrderEntity class constructor.

Model/OrderEntity.cs
...
public OrderEntity()
{
    AddItemCommand = new AddOrderItemCommand();
}
...

Congratulations, relation example is completed. You can test it now, you can also add your own code to it, for example you can implement removing order items or orders.

Conclusion

To conclude, this is the simple example of using relations in Mobeelizer. We have two models connected with 'belongs to' field. We can create orders, add items to them and synchronize with Mobeelizer cloud.

Attachments: