In this hands-on lab, we will be building a Xamarin application that will display a list of interest locations and its location on a map. This app will target iOS, Android and UWP.
Open Visual Studio 2015
Go to File > New > Project
Navigate to Visual C# > Cross-platform and create Blank App (Native Portable) a project called EarthExplorer
This solution contains 4 projects
- EarthExplorer (Portable) - Portable Class Library that will have all shared code (model, views, and view models).
- EarthExplorer.Droid - Xamarin.Android application
- EarthExplorer.iOS - Xamarin.iOS application
- EarthExplorer.WinPhone (Windows Phone 8.1) - Windows 10 UWP application (can only be run from VS 2015 on Windows 10)
At the time of this demo, the UWP project is not created by default. We will be replacing the WinPhone project with a UWP version
Remove the EarthExplorer.WinPhone (Windows Phone 8.1) from the solution by right clicking on the project, and select Remove
This can be done by Right-clicking on the Solution and clicking on Restore NuGet packages...
We will now add a new UWP project into the solution
Right click on the Solution > Add > New project
Navigate to Visual C# > Windows > Universal and create Blank App (Universal Windows) a project called EarthExplorer.UWP
Accept the default selections for the minimum and target versions and click OK
In the EarthExplorer.UWP (Universal Windows) project, right click on *References and select Add Reference
Under Projects > Solution, select Earth Explorer project and click OK
In the EarthExplorer (Portable) project, a default class MyClass has been created
Open the class, and replace the empty class with the following code
public class PointOfInterest
{
public string Name { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
public static async Task<List<PointOfInterest>> GetGlobalListAsync()
{
var list = new List<PointOfInterest>();
list.Add(new PointOfInterest() { Name = "Paris", Latitude = 48.86206, Longitude = 2.343179 });
list.Add(new PointOfInterest() { Name = "Seattle", Latitude = 47.59978, Longitude = -122.3346 });
if (GetCurrentPOIAsync != null)
{
list.Add(await GetCurrentPOIAsync());
}
return list;
}
public static Func<Task<PointOfInterest>> GetCurrentPOIAsync { get; set; }
}
Add in the following using statements
using System.Collections.Generic;
using System.Threading.Tasks;
In the solution explorer, rename MyClass.cs to PointOfInterest.cs as a form of good practice
We're now done with our business logic!
We will now look at how to build UWP version of our app
Right click on the EarthExplorer.UWP (Universal Windows) project, and select Set as StartUp project
In the solution explorer, open MainPage.xaml
In the XAML view, add the following grid definition code within the Grid tags
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<ListView Grid.Row="0" x:Name="POIList" Margin="10,20" IsItemClickEnabled="True">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" FontSize="25"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<my:MapControl Grid.Row="1" x:Name="Map" ZoomLevel="12"/>
Add the following XML namespace in the Page tag
xmlns:my="using:Windows.UI.Xaml.Controls.Maps"
Navigate to the code behind file MainPage.xaml.cs
OnNavigatedTo is invoked when the Page is loaded and becomes the current source of a parent Frame.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
//implement other stuff
}
Now, replace
//implement other stuff
with
PointOfInterest.GetCurrentPOIAsync = async () =>
{
var currentPOI = new PointOfInterest();
var geolocator = new Geolocator();
var position = await geolocator.GetGeopositionAsync();
currentPOI.Name = "Just where I am";
currentPOI.Latitude = position.Coordinate.Point.Position.Latitude;
currentPOI.Longitude = position.Coordinate.Point.Position.Longitude;
return currentPOI;
};
POIList.ItemClick += POIList_ItemClick;
var dataSource = await PointOfInterest.GetGlobalListAsync();
POIList.ItemsSource = dataSource;
MoveTo(dataSource[0]);
Map.Style = Windows.UI.Xaml.Controls.Maps.MapStyle.AerialWithRoads;
Since there's awaitable code here, change the method signature of OnNavigatedTo from
protected override void OnNavigatedTo(NavigationEventArgs e)
to
protected async override void OnNavigatedTo(NavigationEventArgs e)
Add the following using statements
using Windows.Devices.Geolocation;
Add the following methods to implement the event handlers
private void POIList_ItemClick(object sender, ItemClickEventArgs e)
{
var poi = e.ClickedItem as PointOfInterest;
MoveTo(poi);
}
private async void MoveTo(PointOfInterest poi)
{
var position = new BasicGeoposition();
position.Latitude = poi.Latitude;
position.Longitude = poi.Longitude;
await Map.TrySetViewAsync(new Geopoint(position));
}
We're almost done!
Right click on the EarthExplorer.UWP (Universal Windows) project, and select Properties
In the Application tab, click on Package Manifest
Navigate to the Capabilities tab, and check Location
Save your changes to the project by going to File > Save All
Right click on the main Solution and select Properties
Navigate to Configuration Properties > Configuration
Check Build and Deploy against EarthExplorer.UWP and click OK. You may uncheck Build and Deploy for iOS and Android if you want to cut down time on the build
To run your app in its desktop form, select Local Machine
Or you could select the other emulators
We're now going to implement the functionality for the Android app
Right click on EarthExplorer.Droid and select Set as StartUp project
In the solution explorer, go to Resources > layout and open Main.axml
Select the HELLO WORLD, CLICK ME button in the designer, and press delete
In the Toolbox, look for the ListView control, and drag it onto the Android designer
Click on File > Save all to save changes
Open MainActivity.cs
In the OnCreate method, remove the code related to the Button as we have removed it from the UI earlier
Initialise, and wire up the list view such that your OnCreate method looks like this
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
ListView listView = FindViewById<ListView>(Resource.Id.listView1);
listView.ItemClick += ListView_ItemClick;
Datasource = await PointOfInterest.GetGlobalListAsync();
listView.Adapter = new POIAdapter(this, Datasource);
}
Above the OnCreate method, within the class, add the Datasource field:
List<PointOfInterest> Datasource;
Add the following using statement
using System.Collections.Generic;
Since there's awaitable code here, change the method signature of OnCreate from
protected override void OnCreate (Bundle bundle)
to
protected async override void OnCreate (Bundle bundle)
Now, let's implement the ItemClick handler of the ListView
private void ListView_ItemClick(object sender, AdapterView.ItemClickEventArgs e)
{
var poi = Datasource[(int)e.Id];
var geoUri = Android.Net.Uri.Parse($"geo:{poi.Latitude},{poi.Longitude}");
var mapIntent = new Intent(Intent.ActionView, geoUri);
StartActivity(mapIntent);
}
In Android, an Adapter object acts as a bridge between an AdapterView and the underlying data for that view. The Adapter provides access to the data items. The Adapter is also responsible for making a View for each item in the data set
Let's add a new class by right clicking on the EarthExplorer.Droid project, go to Add and select class
Create a new class POIAdapter.cs
Replace
class POIAdapter
{
}
with
public class POIAdapter : BaseAdapter<PointOfInterest>
{
PointOfInterest[] items;
Activity context;
public POIAdapter(Activity context, List<PointOfInterest> items) : base() {
this.context = context;
this.items = items.ToArray();
}
public override long GetItemId(int position)
{
return position;
}
public override PointOfInterest this[int position]
{
get { return items[position]; }
}
public override int Count
{
get { return items.Length; }
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View view = convertView; // re-use an existing view, if one is available
if (view == null) // otherwise create a new one
view = context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleListItem1, null);
view.FindViewById<TextView>(Android.Resource.Id.Text1).Text = items[position].Name;
return view;
}
}
Save the changes made to POIAdapter.cs by going to File > Save all
Right click on the main Solution and select Properties
Navigate to Configuration Properties > Configuration
Ensure that Build and Deploy is checked against EarthExplorer.Droid
You can run the app in the android emulator by click on the green arrow with the following settings