Xamarin Forms Map not adding pins on new pin created - xamarin

I have a Xamarin Forms map that shows a collection of Pins. When I add a new location it doesn't show the newly created pin until I click 'Refresh'.
I originally had the AddLocation subscribe in the constructor of the ViewModel and it worked, but added the pin 3 times to the database and map. Its since moving the AddLocation subscribe and unsubscribe to their own methods in the ViewModel and calling then OnAppearing() and OnDisappearing() that the new pin doesnt show.
The map is on a page called ItemPage.xaml
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Clicked="ToolbarItem_Clicked" />
<ToolbarItem Text="Refresh" Clicked="ToolbarItem_Clicked_1" />
</ContentPage.ToolbarItems>
<ContentPage.Content>
<!--The map-->
<ContentView Content="{Binding Map}" />
</ContentPage.Content>
and the Code Behind -
[DesignTimeVisible(false)]
public partial class ItemPage : ContentPage
{
ItemViewModel viewModel;
public ItemPage()
{
InitializeComponent();
BindingContext = viewModel = new ItemViewModel();
}
private void Button_Clicked(object sender, EventArgs e)
{
}
protected override void OnAppearing()
{
base.OnAppearing();
}
async void ToolbarItem_Clicked(object sender, EventArgs e)
{
await Navigation.PushModalAsync(new NavigationPage(new NewLocationPage()));
}
private void ToolbarItem_Clicked_1(object sender, EventArgs e)
{
viewModel.LoadLocationsCommand.Execute(null);
}
}
The View Model -
public class ItemViewModel : BaseViewModel
{
public ObservableCollection<MapPinModel> Locations { get; set; }
public ObservableCollection<ItemModel> Items { get; set; }
public Command LoadLocationsCommand { get; set; }
public Command LoadItemsCommand { get; set; }
public Map Map { get; private set; }
public ItemViewModel()
{
Title = "Items";
Locations = new ObservableCollection<MapPinModel>();
Items = new ObservableCollection<ItemModel>();
LoadLocationsCommand = new Command(async () => await ExecuteLoadLocationsCommand());
LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());
Map = new Map(MapSpan.FromCenterAndRadius(
new Xamarin.Forms.Maps.Position(53.70251232638285, -1.8018436431884768),
Distance.FromMiles(0.5)))
{
IsShowingUser = true,
VerticalOptions = LayoutOptions.FillAndExpand
};
this.LoadLocationsCommand.Execute(null);
}
public void UnsubscribeMessages()
{
MessagingCenter.Unsubscribe<NewLocationPage, ItemLocationModel>(this, "AddLocation");
}
public void SubscribeMessages()
{
MessagingCenter.Subscribe<NewLocationPage, ItemLocationModel>(this, "AddLocation", async (obj, item) =>
{
await ItemDataStore.AddItemLocationAsync(item);
});
}
async Task ExecuteLoadItemsCommand()
{
if (IsBusy)
return;
try
{
Items.Clear();
var items = await ItemDataStore.GetItemsAsync(true);
foreach (var item in items)
{
Items.Add(item);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
async Task ExecuteLoadLocationsCommand()
{
Map.Pins.Clear();
var locations = await ItemDataStore.GetLocationsAsync(true);
foreach (Feature feature in locations.Features)
{
if (!feature.Geometry.Type.Equals(GeoJSONObjectType.Point))
continue;
Point point = feature.Geometry as Point;
GeoJSON.Net.Geometry.Position s = point.Coordinates as GeoJSON.Net.Geometry.Position;
MapPinModel pin = new MapPinModel
{
PopupContent = feature.Properties["popupContent"].ToString(),
ScientificName = feature.Properties["scientificname"].ToString(),
ItemDescription = feature.Properties["description"].ToString(),
AddedBy = feature.Properties["username"].ToString(),
Latitude = s.Latitude.ToString(),
Longitude = s.Longitude.ToString()
};
Xamarin.Forms.Maps.Position position = new Xamarin.Forms.Maps.Position(Convert.ToDouble(pin.Latitude), Convert.ToDouble(pin.Longitude));
Pin newPin = new Pin
{
Label = pin.PopupContent,
Type = PinType.Place,
Position = position
};
Map.Pins.Add(newPin);
}
}
}
Finally the New Location page -
[DesignTimeVisible(false)]
public partial class NewLocationPage : ContentPage
{
ItemViewModel viewModel;
public ItemLocationModel Location { get; set; }
public NewLocationPage()
{
InitializeComponent();
Location = new ItemLocationModel();
BindingContext = viewModel = new ItemViewModel();
BindingContext = this;
viewModel.SubscribeMessages();
}
async void Save_Clicked(object sender, EventArgs e)
{
var location = await Geolocation.GetLastKnownLocationAsync();
Location.Latitude = location.Latitude.ToString();
Location.Longitude = location.Longitude.ToString();
Location.DatePosted = DateTime.Now;
Location.ItemId = ((ItemModel)itemsPicker.SelectedItem).Id;
Location.SecretLocation = secretLocation.IsToggled;
MessagingCenter.Send(this, "AddLocation", Location);
await Navigation.PopModalAsync();
Location = null;
}
async void Cancel_Clicked(object sender, EventArgs e)
{
await Navigation.PopModalAsync();
}
protected override void OnAppearing()
{
viewModel.SubscribeMessages();
base.OnAppearing();
if (viewModel.Items.Count == 0)
viewModel.LoadItemsCommand.Execute(null);
itemsPicker.ItemsSource = viewModel.Items;
}
protected override void OnDisappearing()
{
viewModel.LoadLocationsCommand.Execute(null);
viewModel.UnsubscribeMessages();
base.OnDisappearing();
}
}

Related

Xamarin Forms updating map pins when new pin added

I have a Map in Xamarin Forms that will show a collection of Pins stored in a SQLite database. Its quite simple, the map shows with the pins, there is an Add button at the top which presents with a textbox to give a new pin a Name. The Lat/Lng is populated automatically and when Save is pressed, the new pin should get added to the map.
When the map loads, the pins are not shown on the map. I have created a button to load the pins and they will show when that button is pressed.
A breakpoint in the ViewModel ExecuteLoadPinsCommand() shows that Pins does have pins inside it.
When I create a new pin, again the new pin doesn't show.
Here is the View. Note the toolbar button with the binding Pins.Count, thats the button I click to show the pins.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"
x:Class="WE.Views.MapPage"
xmlns:vm="clr-namespace:WE.ViewModels"
Title="{Binding Title}">
<ContentPage.BindingContext>
<vm:MapPageViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Command="{Binding AddPinCommand}" />
<ToolbarItem Text="{Binding Pins.Count}" Clicked="ToolbarItem_Clicked" />
</ContentPage.ToolbarItems>
<ContentPage.Content>
<maps:Map x:Name="myMap" ItemsSource="{Binding Pins}" IsShowingUser="True">
<maps:Map.ItemTemplate>
<DataTemplate>
<maps:Pin Label="{Binding Name}" Position="{Binding Position}"/>
</DataTemplate>
</maps:Map.ItemTemplate>
</maps:Map>
</ContentPage.Content>
Here is the ViewModel.
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MapPage : ContentPage
{
MapPageViewModel _viewModel;
public MapPage()
{
InitializeComponent();
BindingContext = _viewModel = new MapPageViewModel();
_viewModel.LoadPinsCommand.Execute(this);
}
protected override async void OnAppearing()
{
base.OnAppearing();
_viewModel.OnAppearing();
await GetCurrentLocation();
}
CancellationTokenSource cts;
async Task GetCurrentLocation()
{
var request = new GeolocationRequest(GeolocationAccuracy.Medium, TimeSpan.FromSeconds(10));
cts = new CancellationTokenSource();
var location = await Geolocation.GetLocationAsync(request, cts.Token);
if (location != null)
{
Console.WriteLine($"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude: {location.Altitude}");
myMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(location.Latitude, location.Longitude), Distance.FromMeters(100)));
}
}
private async void ToolbarItem_Clicked(object sender, EventArgs e)
{
UpdatePins();
}
void UpdatePins()
{
foreach (var pin in _viewModel.Pins)
{
Pin newPin = new Pin
{
Label = pin.Name,
Position = new Position(Convert.ToDouble(pin.Latitude), Convert.ToDouble(pin.Longitude)),
Type = PinType.Place
};
myMap.Pins.Add(newPin);
}
}
}
and finally, here is the View Model
public class MapPageViewModel : BaseViewModel
{
private readonly IMapService _mapService;
public int PinCount { get; set; }
public ObservableCollection<MapPinModel> Pins { get; set; }
public Command LoadPinsCommand { get; }
public Command AddPinCommand { get; }
public MapPageViewModel()
{
_mapService = new MapService();
Title = "Map";
Pins = new ObservableCollection<MapPinModel>();
PinCount = 0;
LoadPinsCommand = new Command(async () => await ExecuteLoadPinsCommand());
AddPinCommand = new Command(OnAddPin);
this.LoadPinsCommand.Execute(null);
}
async Task ExecuteLoadPinsCommand()
{
IsBusy = true;
try
{
Pins.Clear();
Pins = await App.Database.GetPins();
Debug.WriteLine("Pins in DB: " + Pins.Count.ToString());
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
public async void OnAppearing()
{
IsBusy = true;
}
private async void OnAddPin(object obj)
{
await Shell.Current.GoToAsync(nameof(AddPinPage));
}
}
Add New Pin
ViewModel
private readonly IMapService _mapService;
private string userid;
private string latitude;
private string longitude;
private DateTime dateadded = DateTime.Now;
private DateTime datefound = DateTime.Now;
private string name;
public AddPinViewModel()
{
SaveCommand = new Command(OnSave, ValidateSave);
CancelCommand = new Command(OnCancel);
this.PropertyChanged +=
(_, __) => SaveCommand.ChangeCanExecute();
_mapService = new MapService();
}
private bool ValidateSave()
{
return !String.IsNullOrWhiteSpace(name);
}
public string UserId
{
get => userid;
set => SetProperty(ref userid, value);
}
public string Latitude
{
get => latitude;
set => SetProperty(ref latitude, value);
}
public string Longitude
{
get => longitude;
set => SetProperty(ref longitude, value);
}
public DateTime DateAdded
{
get => dateadded;
set => SetProperty(ref dateadded, value);
}
public DateTime DateFound
{
get => datefound;
set => SetProperty(ref datefound, value);
}
public string Name
{
get => name;
set => SetProperty(ref name, value);
}
public Command SaveCommand { get; }
public Command CancelCommand { get; }
private async void OnCancel()
{
// This will pop the current page off the navigation stack
await Shell.Current.GoToAsync("..");
}
private async void OnSave()
{
var location = await GetCurrentLocation();
var userid = await Xamarin.Essentials.SecureStorage.GetAsync("userId");
MapPinModel pin = new MapPinModel()
{
Name = Name,
DateAdded = DateTime.Now,
DateFound = DateTime.Now,
Latitude = location.Latitude,
Longitude = location.Longitude
};
await App.Database.AddPin(pin);
await Shell.Current.GoToAsync("..");
}
CancellationTokenSource cts;
async Task<LatLngModel> GetCurrentLocation()
{
LatLngModel model = new LatLngModel();
var request = new GeolocationRequest(GeolocationAccuracy.Medium, TimeSpan.FromSeconds(10));
cts = new CancellationTokenSource();
var location = await Geolocation.GetLocationAsync(request, cts.Token);
if (location != null)
{
model.Latitude = location.Latitude.ToString();
model.Longitude = location.Longitude.ToString();
}
return model;
}

How to PushAsync from MasterPage?

I have MasterPage that currently using Event-Driven approach as follow:
MasterPage.xaml.cs
private void TapHome_Tapped(object sender, EventArgs e)
{
Detail = new NavigationPage(new HomePage());
IsPresented = false;
}
App.xaml.cs:
public App()
{
InitializeComponent();
if (!string.IsNullOrEmpty(Preferences.Get(Constant.Setting_AccessToken, "")))
{
MainPage = new MasterPage();
}
else if (string.IsNullOrEmpty(Preferences.Get(Constant.Setting_UserEmail, "")) &&
string.IsNullOrEmpty(Preferences.Get(Constant.Setting_Password, "")))
{
MainPage = new NavigationPage(new LoginPage());
}
}
Since we tried to migrate to the MVVM approach. We attempted to change the code:
MasterPage.xaml.cs
public MasterPage()
{
InitializeComponent();
BindingContext = _viewModel = new MasterViewModel()
{
Navigation = Navigation
};
//IsPresented = false;
}
MasterViewModel.cs:
public ICommand HomeCommand { get; private set; }
public ICommand ActivitiesCommand { get; private set; }
public ICommand ChangePasswordCommand { get; private set; }
public ICommand LogoutCommand { get; private set; }
public MasterViewModel()
{
HomeCommand = new Command(async () => await GoToHome());
ActivitiesCommand = new Command(async () => await GoToActivities());
ChangePasswordCommand = new Command(async () => await GoToChangePassword());
LogoutCommand = new Command(async () => await Logout());
}
public async Task GoToHome()
{
// Attempt 1:
// Navigation.PushAsync(new NavigationPage(new HomePage()));
// Attempt 2:
// Application.Current.MainPage = new NavigationPage(new HomePage());
}
Tried the first attempt and we have error on Android - System.InvalidOperationException: PushAsync is not supported globally on Android, please use a NavigationPage.
Tried the second attempt and worked OK but no navigation at HomePage or other pages after clicking it.
Any ideas?
Never mind. We worked out this:
public async Task GoToHome()
{
((MasterDetailPage)Application.Current.MainPage).Detail = new NavigationPage(new HomePage());
((MasterDetailPage)Application.Current.MainPage).IsPresented = false;
}

How can I share the value of a field between back-end C# and a renderer?

My C# looks like this:
public App()
{
InitializeComponent();
MainPage = new Japanese.MainPage();
}
public partial class MainPage : TabbedPage
{
public MainPage()
{
InitializeComponent();
var phrasesPage = new NavigationPage(new PhrasesPage())
{
Title = "Play",
Icon = "ionicons-2-0-1-ios-play-outline-25.png"
};
public partial class PhrasesPage : ContentPage
{
public PhrasesFrame phrasesFrame;
public PhrasesPage()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
App.phrasesPage = this;
}
protected override void OnAppearing()
{
base.OnAppearing();
App.dataChange = true;
phrasesFrame = new PhrasesFrame(this);
phrasesStackLayout.Children.Add(phrasesFrame);
}
public partial class PhrasesFrame : Frame
{
private async Task ShowCard()
{
if (pauseCard == false)
..
and I have an iOS renderer for a tab page
public class TabbedPageRenderer : TabbedRenderer
{
private MainPage _page;
private void OnTabBarReselected(object sender, UITabBarSelectionEventArgs e)
{
...
pauseCard = false;
...
My problem is there is no connection between the two and I would like to know how I can make it so that pauseCard could be set in one place and read in another.
Here is a simple custom Entry example using a bindable bool property that gets changed from the renderer every time the text changes in the entry.
Entry subclass w/ a bindable property called OnOff (bool)
public class CustomPropertyEntry : Entry
{
public static readonly BindableProperty OnOffProperty = BindableProperty.Create(
propertyName: "OnOff",
returnType: typeof(bool),
declaringType: typeof(CustomPropertyEntry),
defaultValue: false);
public bool OnOff
{
get { return (bool)GetValue(OnOffProperty); }
set { SetValue(OnOffProperty, value); }
}
}
iOS Renderer
Note: I keep a reference to the instance of the CustomPropertyEntry passed into OnElementChanged so later I can set its custom property when needed.
public class CustomPropertyEntryRenderer : ViewRenderer<CustomPropertyEntry, UITextField>
{
UITextField textField;
CustomPropertyEntry entry;
protected override void OnElementChanged(ElementChangedEventArgs<CustomPropertyEntry> e)
{
base.OnElementChanged(e);
if (Control == null)
{
textField = new UITextField();
SetNativeControl(textField);
}
if (e.OldElement != null)
{
textField.RemoveTarget(EditChangedHandler, UIControlEvent.EditingChanged);
entry = null;
}
if (e.NewElement != null)
{
textField.AddTarget(EditChangedHandler, UIControlEvent.EditingChanged);
entry = e.NewElement;
}
}
void EditChangedHandler(object sender, EventArgs e)
{
entry.OnOff = !entry.OnOff;
}
}
XAML Example:
<local:CustomPropertyEntry x:Name="customEntry" Text="" />
<Switch BindingContext="{x:Reference customEntry}" IsToggled="{Binding OnOff}" />

Xamarin Forms: Keyboard didn't hide

The keyboard hides when I create the page, but when I change the focus to another entry I call the same function but the soft keyboard didn't hide?
ConfigService.cs
public void HideKeyboard()
{
var inputMethodManager = Xamarin.Forms.Forms.Context.GetSystemService(Context.InputMethodService) as InputMethodManager;
if (inputMethodManager != null && Xamarin.Forms.Forms.Context is Activity)
{
var activity = Xamarin.Forms.Forms.Context as Activity;
var focusedView = activity.CurrentFocus;
var token = focusedView == null ? null : focusedView.WindowToken;
inputMethodManager.HideSoftInputFromWindow(token, HideSoftInputFlags.None);
}
}
VerplaatsingPage.xaml.cs
namespace SI_Foodware.View
{
public partial class VerplaatsingPage : ContentPage
{
VerplaatsingPageViewModel vm;
public VerplaatsingPage (SubMenu subMenu)
{
InitializeComponent ();
vm = new VerplaatsingPageViewModel(this, subMenu);
BindingContext = vm;
}
public void OnClickDestination(object o, EventArgs e)
{
}
public void OnClickSelect(object o, EventArgs e)
{
}
public void OnClickCancel(object o, EventArgs e)
{
}
protected override void OnAppearing()
{
base.OnAppearing();
ent_drager.Focus();
IConfigService configService = DependencyService.Get<IConfigService>();
configService.HideKeyboard();
}
}
}
VerplaatsingPageViewModel.cs
public void Keyboard()
{
IConfigService configService = DependencyService.Get<IConfigService>();
configService.HideKeyboard();
}
public void OnScan(string scanProperty)
{
var list = db.GetAllItems<ContainerLine>();
Artikel = list[0].ItemNo;
Description = list[0].Description;
LotNo = list[0].LotNo;
NumberOfContainer = list[0].Quantity;
KiloDrager = list[0].KgQuantity;
UnitOfMeasure = list[0].UnitofMeasureCode;
LocationFrom = list[0].BinCode;
cp.FindByName<Entry>("ent_destination").Focus();
Keyboard(); <-- Doesn't Work
}
I solved my problem by using
inputMethodManager.ToggleSoftInputFromWindow(token, ShowSoftInputFlags.None, HideSoftInputFlags.None);
instead of
inputMethodManager.HideSoftInputFromWindow(token, HideSoftInputFlags.None);

Xamarin forms navigation BarBackgroundColor issue

When I click on Login for the first time than the barbackground of mainpage becomes the right color, but when I logout and than login again the color of the barbackground doesn't change??
LoginPage (View)
public partial class LoginPage : ContentPage
{
LoginPageViewModel vm;
public LoginPage ()
{
vm = new LoginPageViewModel ();
BindingContext = vm;
InitializeComponent ();
}
public void OnClickLogin(object o, EventArgs e)
{
vm.Login ();
}
public void OnClickPasswoordVergeten(object o, EventArgs e)
{
vm.PasswoordVergeten ();
}
public void OnClickContactUs(object o, EventArgs e)
{
vm.ContactUs ();
}
}
LoginPageViewModel (ViewModel)
public class LoginPageViewModel: INotifyPropertyChanged
{
public LoginPageViewModel ()
{
}
public void Login()
{
App.Current.MainPage = new Dharma.MainPage();
}
}
MainPage (View)
public partial class MainPage : MasterDetailPage
{
public MainPage ()
{
InitializeComponent ();
masterPage.ListView.ItemSelected += OnItemSelected;
var page = new NavigationPage (new ListPage());
page.BarBackgroundColor = Color.FromRgb(26,179,148);
Detail = page;
masterPage.ListView.SelectedItem = null;
IsPresented = false;
}
void OnItemSelected (object sender, SelectedItemChangedEventArgs e)
{
var item = e.SelectedItem as MasterPageItem;
if (item != null) {
var page = new NavigationPage ((Page)Activator.CreateInstance (item.TargetType));
page.BarBackgroundColor = Color.FromRgb(26,179,148);
Detail = page;
masterPage.ListView.SelectedItem = null;
IsPresented = false;
}
}
}
Set the color to your master page.
public void Login()
{
var mainPage = new Dharma.MainPage();
mainPage.BarBackgroundColor = Color.FromRgb(26,179,148);
App.Current.MainPage = mainPage ;
}

Resources