Artykuł

freeimages.com freeimages.com
sie 27 2015
0

Universal Apps - mapowanie danych z API na potrzeby XAML

Tworząc aplikacje mobilne, nie jest łatwo zbudować model danych, który idealnie odpowiadałby potrzebom aktualnego widoku. Problem staje się jeszcze bardziej złożony, w sytuacji gdy korzystamy z API, które zwraca ogólny model, dla rożnych końcówek. W takiej sytuacji, programista aplikacji mobilnej musi sam zadbać o odpowiednie wyświetlenie i sformatowanie otrzymanych danych..

W świecie Universal Apps problem ten można rozwiązać na różne sposoby. Osobiście preferuje tutaj wykorzystanie architektury MVVM, która wymaga od programisty stworzenia dedykowanego ViewModelu zawierającego odpowiednio przygotowane dane do wyświetlenia. Nie wszystkie właściwości da się jednak sensownie odpowiednio przetworzyć - zwłaszcza jeśli określony element danych wyświetlany jest inaczej na komputerze i telefonie. W takiej sytuacji z pomocą przyjdą konwertery. W dzisiejszym tekście zaprezentuje mix obu rozwiązań na przykładzie prostej aplikacji na telefon.

Założenia

Z założenia, moja prosta aplikacja na telefon, będzie wyświetlać dane osobowe pobrane z API (oczywiście na potrzeby przykładu, dane będę wklepane na stałe w kodzie;-). Model danych zwrócony z sieci, wygląda następująco:

public class PersonData
{
	public string FirstName { get; set; }
	public string LastName { get; set; }
	public int Age { get; set; }
}

Z założenia chciałbym imię i nazwisko skleić w jeden string (tak wiem - mogę w TextBlocku skorzystać z Inlines, o których sam pisałem, ale potrzebuje jakiś przykład), a także uwarunkować kolor tła formatki. Dla osób powyżej 50 roku życia, byłby to ciemny niebieski, natomiast dla pozostałych - jasny (nie pytajcie mnie dlaczego akurat taki podział - tak po prostu naszło;-).

Pierwszy problem rozwiążę za pomocą nowej właściwości w lokalnym modelu danych, natomiast do drugiego zaprzęgnę konwerter:-)

Model danych + ViewModel

Klasa modelu z której będziemy korzystać, będzie zbliżona strukturalnie do klasy danych. Zasadnicza różnica między tymi konstrukcjami będzie jedna - model będzie zawierał wyliczaną właściwość FullName. A skoro różnica będzie tak mała, to większość pracy będzie w stanie za nas załatwić stary dobry znajomy - AutoMapper. Poniżej kod modelu:

public class PersonDataModel
{
	public string FirstName { get; set; }
	public string LastName { get; set; }

	public string FullName
	{
		get
		{
			return this.FirstName + " " + this.LastName;
		}
	}

	public int Age { get; set; }
}

Nasz model danych zostanie opakowany w ViewModel, który będzie również odpowiedzialny za fikcyjnie pobieranie danych z API. Poniżej jego kod:

public class PersonDataViewModel
{
	public PersonDataViewModel()
	{
		Mapper.CreateMap<PersonData, PersonDataModel>();
		this.PersonDataModel = this.GetData();
	}

	public PersonDataModel  PersonDataModel
	{
		get;
		private set;
	}
		
	public PersonDataModel GetData()
	{
		return Mapper.Map<PersonData, PersonDataModel>(this.GetDataFromApi());
	}

	private PersonData GetDataFromApi()
	{
		return new PersonData
		{
			FirstName = "Jan",
			LastName = "Kowalski",
			Age = 29
		};
	}
}

W konstruktorze klasy dodajemy mapę dla AutoMappera, z której później korzystamy w trakcie pobierania danych.

Konwerter

Kolejnym elementem układanki jest konwerter. Z założenia będzie on przyjmować INTa (wiek), który później zostanie przekształcony do obiektu SolidColorBrush. O konwerterach pisałem już co nieco przy okazji tekstu C# - Interfejsy które warto znać. Nie będziemy w tym przypadku implementować metody ConvertBack, ze względu na fakt, że bindowanie jest tutaj jednostronne:

public class AgeToBrushConverter : IValueConverter
{
	public object Convert(object value, Type targetType, object parameter, string language)
	{
		if(value == null || !(value is int))
		{
			return new SolidColorBrush(Colors.Black);
		}
		int age = Convert.ToIn32(value.ToString());
		return new SolidColorBrush(age > 50 ?
			Colors.DarkBlue : Colors.LightBlue);
	}

	public object ConvertBack(object value, Type targetType, object parameter, string language)
	{
		throw new NotImplementedException();
	}
}

Strona

Projekt zawierać będzie tylko jedną stronę testową - MainPage. Została ona stworzona na podstawie standardowego szablonu Basic page. Modyfikację rozpoczniemy od code-behind. Na końcu konstruktora dodajemy wpis, który ustawi DataContext strony na nowy obiekt wcześniej utworzonego ViewModelu:

this.DataContext = new PersonDataViewModel();

Ostatnim elementem układanki jest kod XAML strony. Poniżej w skróconej postaci:

<Page 
    <!-- standardowy kod -->
	>
    <Page.Resources>
        <local:AgeToBrushConverter x:Name="AgeToBrushConverter" />
    </Page.Resources>
    
	<StackPanel x:Name="ContentRoot" Margin="19,9.5,19,0" 
				Background="{Binding PersonDataModel.Age, 
							 Converter={StaticResource AgeToBrushConverter}}">
		<TextBlock Style="{StaticResource BodyTextBlockStyle}" Padding="19">
			<Run>Imię i nazwisko:</Run>
			<Run Text="{Binding PersonDataModel.FullName}" />
		</TextBlock>
		<TextBlock Style="{StaticResource BodyTextBlockStyle}" Padding="19">
			<Run>Wiek:</Run>
			<Run Text="{Binding PersonDataModel.Age}" />
		</TextBlock>
	</StackPanel>
</Page>

Zastosowaliśmy tutaj obie, wcześniej wymienione koncepcje. Konwerter został dodany do lokalnych zasobów strony i później użyłem go w kodzie, podobnie zresztą jak poszczególnych właściwości modelu danych. Aby ominąć zapis w postaci: PersonDataModel.Age, możliwe jest ustawieniu DataContextu StackPanelu na właściwość PersonDataModel.

Zdaje sobie sprawę, że przykład nie jest może do końca zbyt ambitny, ale w pewnym sensie ukazuje realia obu podejść do tego tematu.

Data ostatniej modyfikacji: 08.09.2015, 22:32.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus