I have this piece of code and I don't even know how to initialize it at this point. I tried a few different versions but all of them come up with a warning saying
"Warning CS0649 Field 'NoteService.db' is never assigned to, and will always have its default value null"
The part where I try to create this table looks like this:
static SQLiteAsyncConnection db;
static async Task Init()
{
if (db != null)
return;
{
var databasePath = Path.Combine(FileSystem.AppDataDirectory, "MyData.db");
db = new SQLiteAsyncConnection(databasePath);
await db.CreateTableAsync<Note>();
}
}
This is the entirety of the code
MyNoteViewModel.cs:
namespace MyApp.ViewModels {
public class MyNoteViewModel : ViewModelBase
{
public ObservableRangeCollection<Note> Note { get;}
public AsyncCommand RefreshCommand { get; }
public AsyncCommand AddCommand { get; }
public AsyncCommand<Note> RemoveCommand { get; }
public new bool IsBusy { get; private set; }
public MyNoteViewModel()
{
Note = new ObservableRangeCollection<Note>();
RefreshCommand = new AsyncCommand(Refresh);
AddCommand = new AsyncCommand(Add);
RemoveCommand = new AsyncCommand<Note>(Remove);
}
async Task Add()
{
var name = await App.Current.MainPage.DisplayPromptAsync("Notes", "Enter your notes here");
await NoteService.AddNote(name);
await Refresh();
}
async Task Remove(Note note)
{
await NoteService.RemoveNote(note.Id);
await Refresh();
}
async Task Refresh()
{
IsBusy = true;
await Task.Delay(2000);
Note.Clear();
var notes = NoteService.GetNote();
Note.AddRange((IEnumerable<Note>)notes);
IsBusy = false;
return;
}
NotePage.xaml:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:model="clr-namespace:MyApp.Models"
xmlns:mvvm="clr-namespace:MvvmHelpers;assembly=MvvmHelpers"
xmlns:viewmodels="clr-namespace:MyApp.ViewModels"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
x:Class="MyApp.MyNotePage"
x:Name="MyNotePage"
x:DataType="viewmodels:MyNoteViewModel"
BackgroundColor="White">
<ContentPage.BindingContext>
<viewmodels:MyNoteViewModel/>
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<xct:ItemSelectedEventArgsConverter x:Key="ItemSelectedEventArgsConverter"/>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Command="{Binding AddCommand}"/>
</ContentPage.ToolbarItems>
<ListView
BackgroundColor="Transparent"
CachingStrategy="RecycleElement"
HasUnevenRows="True"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding IsBusy, Mode=OneWay}"
ItemsSource="{Binding Note}"
RefreshCommand="{Binding RefreshCommand}"
RefreshControlColor="DarkViolet"
SelectionMode="None"
SeparatorVisibility="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:Note">
<ViewCell>
<ViewCell.ContextActions>
<MenuItem
Command="{Binding Source={x:Reference MyNotePage}, Path=BindingContext.RemoveCommand}"
CommandParameter="{Binding .}"
IsDestructive="True"
Text="Delete"/>
</ViewCell.ContextActions>
<Grid Padding="10">
<Frame CornerRadius="20" HasShadow="True">
<StackLayout Orientation="Horizontal">
<StackLayout VerticalOptions="Center">
<Label
FontSize="Large"
Text="{Binding Name}"
VerticalOptions="Center"/>
<Label
FontSize="Small"
Text="{Binding Id}"
VerticalOptions="Center"/>
</StackLayout>
</StackLayout>
</Frame>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
NotePage.xaml.cs:
namespace MyApp
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MyNotePage
{
public MyNotePage()
{
InitializeComponent();
}
}
}
NoteService.cs:
namespace MyApp.Services
{
public static class NoteService
{
static SQLiteAsyncConnection db;
static async Task Init()
{
if (db != null)
return;
{
var databasePath = Path.Combine(FileSystem.AppDataDirectory, "MyData.db");
db = new SQLiteAsyncConnection(databasePath);
await db.CreateTableAsync<Note>();
}
}
public static async Task AddNote(string name)
{
await Init();
var note = new Note()
{
Name = name,
};
var id = await db.InsertAsync(note);
}
public static async Task RemoveNote(int id)
{
await Init();
await db.DeleteAsync<Note>(id);
}
public static async Task<IEnumerable<Note>> GetNote()
{
await Init();
var note = await db.Table<Note>().ToListAsync();
return note;
}
}
}
GetNote is async, but you are not using await when calling it. That means that notes is a Task. Then when you attempt to cast it, the cast fails and returns null, which then causes a NullRef exception
var notes = NoteService.GetNote();
Note.AddRange((IEnumerable<Note>)notes);
instead do this
var notes = await NoteService.GetNote();
Note.AddRange(notes);
var db = new SQLiteAsyncConnection(databasePath);
You are creating a new field scoped to the method, which is allowed by the compiler, but not what you want. Remove the var keyword to reference your class-level field.
Related
I am working on an App, in which I am able to attach attachments like images and audio to a profile and display those attachments in CreateAndUpdateRiskProfileView. I can add attachments with the AddAttachmentModal. When I choose an attachment there it will be automatically displayed in the CreateAndUpdatetRiskProfileView. Now I also want to be able to delete attachments permanently from the database. This is possible when I longpress on an attachment in the AddAttachmentModal and select delete. Now this works and the attachment is not being displayed in the AddAttachmentModal and if I go to another profile which used this attachment, the attachment is also gone. However, in the CreateAndUpdateRiskProfileView of the Profile where I just deleted an attachment this attachment is still there and will remain there until I close the App and build again.
I want the deleted attachment to not show anymore imidiatly, but I don't know how I can make this happen.
I would appreciate the help!
This is the CreateAndUpdateRiskProfileView :
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:SiRiAs.Controls"
xmlns:flowlayout="clr-namespace:SiRiAs.Controls.FlowLayout"
xmlns:behaviours="clr-namespace:SiRiAs.Behaviors"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:convertes="clr-namespace:SiRiAs.Lib.Converters;assembly=SiRiAs.Lib"
xmlns:selectors="clr-namespace:SiRiAs.Lib.TemplateSelectors;assembly=SiRiAs.Lib"
xmlns:viewmodel="clr-namespace:SiRiAs.Lib.ViewModels;assembly=SiRiAs.Lib"
xmlns:models="clr-namespace:SiRiAs.Lib.Models;assembly=SiRiAs.Lib"
x:Class="SiRiAs.Views.CreateAndUpdateRiskProfileView"
x:DataType="viewmodel:CreateAndUpdateRiskProfileViewModel"
Shell.NavBarIsVisible="False">
<ContentPage.Resources>
<ResourceDictionary>
<convertes:LocationToStringConverter x:Key="LocationToStringConverter"
DefaultConvertReturnValue="0°0'0''N 0°0'0''W"/>
<DataTemplate x:Key="ImageAttachmentView"
x:DataType="models:Attachment">
<controls:LongPressImageButton Style="{StaticResource PreviewImageButtonStyle}"
Aspect="AspectFill"
HeightRequest="70"
WidthRequest="70"
Source="{Binding Link}"
LongPressCommand="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:CreateAndUpdateRiskProfileViewModel}}, Path=ShowDeleteOptionCommand}"
LongPressCommandParameter="{Binding .}"
/>
</DataTemplate>
<DataTemplate x:Key="AudioAttachmentView"
x:DataType="models:Attachment">
<controls:LongPressButton Style="{StaticResource PreviewButtonStyle}"
Text="{Binding Name}"
HeightRequest="70"
WidthRequest="70"
LongPressCommand="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:CreateAndUpdateRiskProfileViewModel}}, Path=ShowDeleteOptionCommand}"
LongPressCommandParameter="{Binding .}"/>
</DataTemplate>
This is the CreateAndUpdateProfileView.xaml.cs
using SiRiAs.Lib.ViewModels;
namespace SiRiAs.Views;
public partial class CreateAndUpdateRiskProfileView : ContentPage {
private readonly CreateAndUpdateRiskProfileViewModel _viewModel;
public CreateAndUpdateRiskProfileView(CreateAndUpdateRiskProfileViewModel createAndUpdateRiskProfileViewModel) {
InitializeComponent();
_viewModel = createAndUpdateRiskProfileViewModel;
BindingContext = createAndUpdateRiskProfileViewModel;
}
protected override bool OnBackButtonPressed() {
if(_viewModel.ReturnOrPopupCommand.CanExecute(null)) {
_viewModel.ReturnOrPopupCommand.Execute(null);
}
return true;
}
}
This is the CreateAndUpdateRiskProfileViewModel:
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using SiRiAs.Lib.Helpers;
using SiRiAs.Lib.Models;
using SiRiAs.Lib.Repositories;
using SiRiAs.Lib.Services;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace SiRiAs.Lib.ViewModels {
[QueryProperty(nameof(Intent), nameof(Intent))]
[QueryProperty(nameof(Model), nameof(Model))]
[QueryProperty(nameof(ActivityId), nameof(ActivityId))]
public partial class CreateAndUpdateRiskProfileViewModel : ObservableObject {
private readonly IUnitOfWork _unitOfWork;
private readonly IPopupProvider _popupProvider;
private bool isModelDirty;
public Guid ActivityId { get; set; }
[ObservableProperty]
private Intent intent = Intent.Create;
[ObservableProperty]
private RiskProfile model = new();
[ObservableProperty]
private ObservableCollection<Attachment> attachments = new();
//...
public CreateAndUpdateRiskProfileViewModel(IUnitOfWork unitOfWork, IPopupProvider popupProvider) {
_unitOfWork = unitOfWork;
_popupProvider = popupProvider;
PopulateRiskCategories();
}
/// <summary>
/// Populate fields if model is loaded from database
/// </summary>
/// <param name="value">model</param>
partial void OnModelChanged(RiskProfile value) {
if (value is not null) {
CreationTimeStamp = Model.CreationDate;
SelectedRiskCategory = AvailableRiskCategories.FirstOrDefault(riskCategory => riskCategory.Id == Model.RiskCategory?.Id);
SelectedRiskFeature = AvailableRiskFeatures.FirstOrDefault(riskFeature => riskFeature.Id == Model.RiskFeature?.Id);
DangerLevel = Model.DangerLevel;
Description = Model.Description;
Measures = Model.Measures;
Location = Model.Location;
Attachments = new ObservableCollection<Attachment>(model.Attachments);
isModelDirty = false;
}
}
/// <summary>
/// Add model to database
/// </summary>
[RelayCommand]
public async void CreateOrUpdate() {
CommitChanges();
if (Intent == Intent.Create) {
Model.ActivityId = ActivityId;
Model.CreationDate = Model.LastChangeDate = CreationTimeStamp;
await _unitOfWork.RiskProfiles.Add(Model);
await _unitOfWork.Save();
Model.Attachments = Attachments.ToList();
} else if (Intent == Intent.Update) {
Model.LastChangeDate = DateTime.Now;
Model.Attachments = Attachments.ToList();
_unitOfWork.RiskProfiles.Update(Model);
}
await _unitOfWork.Save();
NavigateBack();
}
[RelayCommand]
public async void AddAttachment() {
var selectedAttachment = await _popupProvider.ShowAddAttachmentPopup();
if (selectedAttachment is not null && !AttachmentAlreadyAttached(selectedAttachment)) {
Attachments.Add(selectedAttachment);
isModelDirty = true;
}
}
[RelayCommand]
public async void ShowDeleteOption(Attachment attachment) {
var result = await _popupProvider.ShowDeleteAttachmentModal();
if(result.Equals(MoreOptionsPopupResult.Delete)) {
var deleteResult = await _popupProvider.ShowDeletePopup();
if(deleteResult.Equals(DeleteDialogResult.Delete)) {
Attachments.Remove(attachment);
isModelDirty = true;
}
}
}
//...
This the the AddAttachmentModal which gets via AddAttachment():
modals:BaseModalPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:modals="clr-namespace:SiRiAs.Views.Modals"
xmlns:controls="clr-namespace:SiRiAs.Controls"
xmlns:viewmodel="clr-namespace:SiRiAs.Lib.ViewModels;assembly=SiRiAs.Lib"
xmlns:behaviors="clr-namespace:SiRiAs.Behaviors"
xmlns:selectors="clr-namespace:SiRiAs.Lib.TemplateSelectors;assembly=SiRiAs.Lib"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:models="clr-namespace:SiRiAs.Lib.Models;assembly=SiRiAs.Lib"
x:Class="SiRiAs.Views.Modals.AddAttachmentModal"
x:DataType="viewmodel:AddAttachmentModalViewModel"
xmlns:convertes="clr-namespace:SiRiAs.Lib.Converters;assembly=SiRiAs.Lib"
Title="AddAttachmentPopup">
<ContentPage.Resources>
<DataTemplate x:Key="ImageAttachmentView" x:DataType="models:Attachment">
<controls:LongPressImageButton Style="{StaticResource PreviewImageButtonStyle}"
Aspect="AspectFill"
Source="{Binding Link}"
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:AddAttachmentModalViewModel}}, Path=SelectedCommand}"
CommandParameter="{Binding .}"
LongPressCommand="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:AddAttachmentModalViewModel}}, Path=ShowDeleteOptionCommand}"
LongPressCommandParameter="{Binding .}"
/>
</DataTemplate>
<DataTemplate x:Key="AudioAttachmentView" x:DataType="models:Attachment">
<controls:LongPressButton Style="{StaticResource PreviewButtonStyle}"
Text="{Binding Name}"
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:AddAttachmentModalViewModel}}, Path=SelectedCommand}"
CommandParameter="{Binding .}"
LongPressCommand="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:AddAttachmentModalViewModel}}, Path=ShowDeleteOptionCommand}"
LongPressCommandParameter="{Binding .}"
/>
</DataTemplate>
//...
The AddAttachmentModalViewModel:
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using SiRiAs.Lib.Models;
using SiRiAs.Lib.Repositories;
using System.Collections.ObjectModel;
using System.Diagnostics;
using SiRiAs.Lib.Services;
using SiRiAs.Lib.Helpers;
namespace SiRiAs.Lib.ViewModels {
public partial class AddAttachmentModalViewModel : ObservableObject {
[ObservableProperty]
private ObservableCollection<Attachment> attachments;
private readonly IUnitOfWork _unitOfWork;
private readonly IPopupProvider _popupProvider;
//...
public AddAttachmentModalViewModel(IUnitOfWork unitOfWork, IPopupProvider popupProvider, IAudioRecorder audioRecorder) {
_unitOfWork = unitOfWork;
_popupProvider = popupProvider;
_audioRecorder = audioRecorder;
OnSelectedAttachmentTypeChanged(SelectedAttachmentType);
}
[RelayCommand]
public async void ShowDeleteOption(Attachment attachment) {
var result = await _popupProvider.ShowDeleteAttachmentPopup();
if(result.Equals(DeleteDialogResult.Delete)) {
var deleteResult = await _popupProvider.ShowDeletePopup();
if(deleteResult.Equals(DeleteDialogResult.Delete)) {
_unitOfWork.Attachments.Remove(attachment);
await _unitOfWork.Save();
Attachments.Remove(attachment);
foreach(RiskProfile riskProfile in await _unitOfWork.RiskProfiles.GetAll()){
if(riskProfile.Attachments.Contains(attachment)) {
riskProfile.Attachments.Remove(attachment);
}
}
}
//...
}
}
I have this RefreshCommand which I use to refresh a database of "notes", but when I refresh the page, instead of refreshing for 2 seconds and then just giving me the notes, it keeps on refreshing, which isn't what I would like it to do, plus it lags the app.
Here is the code
MyNotePage.xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:model="clr-namespace:MyApp.Models"
xmlns:mvvm="clr-namespace:MvvmHelpers;assembly=MvvmHelpers"
xmlns:viewmodels="clr-namespace:MyApp.ViewModels"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
x:Class="MyApp.MyNotePage"
x:DataType="viewmodels:MyNoteViewModel"
BackgroundColor="White">
<ContentPage.BindingContext>
<viewmodels:MyNoteViewModel/>
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<xct:ItemSelectedEventArgsConverter x:Key="ItemSelectedEventArgsConverter"/>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Command="{Binding AddCommand}"/>
</ContentPage.ToolbarItems>
<ListView
BackgroundColor="Transparent"
CachingStrategy="RecycleElement"
HasUnevenRows="True"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding IsBusy, Mode=OneWay}"
ItemsSource="{Binding Note}"
RefreshCommand="{Binding RefreshCommand}"
RefreshControlColor="DarkViolet"
SelectionMode="None"
SeparatorVisibility="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:Note">
<ViewCell>
<ViewCell.ContextActions>
<MenuItem
Command="{Binding Source={Binding MyNotePage}, Path=BindingContext.RemoveCommand}"
CommandParameter="{Binding .}"
IsDestructive="True"
Text="Delete"/>
</ViewCell.ContextActions>
<Grid Padding="10">
<Frame CornerRadius="20" HasShadow="True">
<StackLayout Orientation="Horizontal">
<StackLayout VerticalOptions="Center">
<Label
FontSize="Large"
Text="{Binding Name}"
VerticalOptions="Center"/>
<Label
FontSize="Small"
Text="{Binding Id}"
VerticalOptions="Center"/>
</StackLayout>
</StackLayout>
</Frame>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
MyNotePage.xaml.cs
namespace MyApp
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MyNotePage
{
public MyNotePage()
{
InitializeComponent();
}
}
}
MyNoteViewModel.cs
namespace MyApp.ViewModels
{
public class MyNoteViewModel : ViewModelBase
{
public ObservableRangeCollection<Note> Note { get; }
public AsyncCommand RefreshCommand { get; }
public AsyncCommand AddCommand { get; }
public AsyncCommand<Note> RemoveCommand { get; }
public new bool IsBusy { get; private set; }
public MyNoteViewModel()
{
Note = new ObservableRangeCollection<Note>();
RefreshCommand = new AsyncCommand(Refresh);
AddCommand = new AsyncCommand(Add);
RemoveCommand = new AsyncCommand<Note>(Remove);
}
async Task Add()
{
var name = await App.Current.MainPage.DisplayPromptAsync("Notes", "Enter your notes here");
await NoteService.AddNote(name);
await Refresh();
}
async Task Remove(Note note)
{
await NoteService.RemoveNote(note.Id);
await Refresh();
}
async Task Refresh()
{
IsBusy = true;
await Task.Delay(2000);
Note.Clear();
var notes = await NoteService.GetNote();
Note.AddRange(notes);
IsBusy = false;
}
}
}
And NoteService.cs
namespace MyApp.Services
{
public static class NoteService
{
static SQLiteAsyncConnection db;
static async Task Init()
{
if (db != null)
return;
{
var databasePath = Path.Combine(FileSystem.AppDataDirectory, "MyData.db");
db = new SQLiteAsyncConnection(databasePath);
await db.CreateTableAsync<Note>();
}
}
public static async Task AddNote(string name)
{
await Init();
var note = new Note()
{
Name = name,
};
await db.InsertAsync(note);
}
public static async Task RemoveNote(int id)
{
await Init();
await db.DeleteAsync<Note>(id);
}
public static async Task<IEnumerable<Note>> GetNote()
{
await Init();
var note = await db.Table<Note>().ToListAsync();
return note;
}
}
}
Maybe it is something to do with IsBusy? I don't know if I set that up properly. The bool does change from false to true but it looks like it doesn't do the opposite to stop the refresh. Thanks for the help.
I pointed this out to you in your previous question. IsBusy does not raise a PropertyChanged event so the UI is never notified
I'm trying to make CollectionView in Shell but it's not updating.
I have one view model connected to Page and AppShell but when I update Collection view only page is updationg.
`public class AppShellViewModel : INotifyPropertyChanged
{
public Command Load { get; }
public ObservableCollection<ListData> _lists { get; set; }
public ObservableCollection<ListData> Lists
{
get { return _lists; }
set
{
_lists = value;
OnPropertyChanged();
}
}
public AppShellViewModel()
{
Lists = new ObservableCollection<ListData>()
{
new ListData(){id=0,name="test",UserId=0},
new ListData(){id=1,name="test1",UserId=1},
new ListData(){id=2,name="test2",UserId=2},
new ListData(){id=3,name="test3",UserId=3},
new ListData(){id=4,name="test4",UserId=4}
};
Load = new Command(async () => await GetUserLists());
}
async Task GetUserLists()
{
for (int i = 5; i < 15; i++)
{
Lists.Add(new ListData {id=i, name=$"test{ i }", UserId=i });
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}`
Then i have App Shell Collection View
`<Shell.FlyoutContent>
<StackLayout BackgroundColor="#34495e">
<Label Text="YOUR LISTS" FontSize="50" />
<CollectionView ItemsSource="{Binding Lists}" >
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10" x:DataType="model:ListData">
<Label Text="{Binding name}"
LineBreakMode="NoWrap"
FontSize="13" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</Shell.FlyoutContent>`
And There is Page CollectionView
`<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ToDoApp.Views.AboutPage"
xmlns:model="clr-namespace:ToDoApp.Models">
<StackLayout>
<Button Text="Load" Command="{Binding Load}"/>
<Label Text="{Binding error}"/>
<CollectionView ItemsSource="{Binding Lists}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10" x:DataType="model:ListData">
<Label Text="{Binding name}"
LineBreakMode="NoWrap"
FontSize="13" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage>`
before update it looks like this
Page before update
Shell before update
And after update the only what changed is content page and shell is the same as before
Page after update
Shell after update
Related to Jason's comment.
WON'T CHANGE TOGETHER
NOT the same instance - BindingContexts similar to these:
// In AppShell.xaml.cs.
public AppShell()
{
InitializeComponent();
BindingContext = new AppShellViewModel();
}
// In AboutPage.xaml.cs.
public AboutPage()
{
InitializeComponent();
BindingContext = new AppShellViewModel();
}
GOOD (SHARED BETWEEN TWO PLACES)
BindingContexts are SAME instance:
// In AppShellViewModel.cs.
public class AppShellViewModel ...
{
private static AppShellViewModel _it;
public static AppShellViewModel It
{
get {
if (_it == null)
_it = new AppShellViewModel();
return _it;
}
}
}
// In AppShell.xaml.cs.
public AppShell()
{
InitializeComponent();
BindingContext = AppShellViewModel.It;
}
// In AboutPage.xaml.cs.
public AboutPage()
{
InitializeComponent();
BindingContext = AppShellViewModel.It;
}
Using Visual Studio 2015 proff,
My LoginViewModel Class (Portable Class Library)
public class LoginViewModel : INotifyPropertyChanged, INotifyCollectionChanged
{
LoginPage page;
private ObservableCollection<Employees> _employeeList;
private string _loginName;
public ObservableCollection<Employees> EmployeeList
{
get { return _employeeList; }
set
{
_employeeList = value;
OnPropertyChanged();
OnCollectionChanged(NotifyCollectionChangedAction.Reset);
}
}
public string LoginName
{
get { return _loginName; }
set
{
_loginName = value;
if (_loginName != null)
{
OnPropertyChanged();
}
}
}
public LoginViewModel(LoginPage parent)
{
page = parent;
}
public async void GetEmployees()
{
var loginService = new LoginService();
EmployeeList = await loginService.GetEmployeesAsync();
}
public event PropertyChangedEventHandler PropertyChanged;
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected virtual void OnCollectionChanged( NotifyCollectionChangedAction action)
{
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action));
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
My LoginPage.xaml (Portable Class Library)
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ScannerApp.Views.LoginPage"
xmlns:ViewModels="clr-namespace:ScannerApp.ViewModels;assembly=ScannerApp">
<StackLayout Orientation="Vertical">
<Label Text="Please Login"
VerticalOptions="Start"
HorizontalTextAlignment="Center"
IsVisible="true"
FontSize="Large"
FontAttributes="Bold" />
<ListView x:Name="mylist" ItemsSource="{Binding EmployeeList}"
HasUnevenRows="True" SelectedItem="{Binding LoginName}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" Padding="12,6">
<Label Text="{Binding Name}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
My LoginPage.xaml.cs Class (Portable Class Library)
public partial class LoginPage : ContentPage
{
public LoginPage()
{
InitializeComponent();
BindingContext = new LoginViewModel(this);
}
protected override void OnAppearing()
{
base.OnAppearing();
LoginViewModel model = new LoginViewModel(this);
model.GetEmployees();
BindingContext = model;
}
public ListView MyList
{
get
{
return mylist;
}
}
}
Question
I get a list of employees, the list on the front end renders this. The user then Selects a name from the list, At this point I would like to detect this and then navigate to my different page.
Currently my property is not hit, I'm wondering if this has anything to do with my Binding on the code behind "OnAppearing"? but I'm not sure.
While what you have may work there are a few tweaks I would suggest.
No need to set your BindingContext in your constructor and in OnAppearing(). Just make your LoginViewModel a class level private property in your code-behind and only assign it to your BindingContext in your constructor. Then call GetEmployees() in OnAppearing().
Also, you should make GetEmployees() return a Task, in order to await as far up the chain as possible.
ViewModel:
....
public async Task GetEmployees()
{
var loginService = new LoginService();
EmployeeList = await loginService.GetEmployeesAsync();
}
....
Code-behind:
public partial class LoginPage : ContentPage
{
private LoginViewModel _model;
public LoginPage()
{
InitializeComponent();
BindingContext = _model = new LoginViewModel(this);
}
protected override async void OnAppearing() //Notice I changed this to async and can now await the GetEmployees() call
{
base.OnAppearing();
await _model.GetEmployees();
}
public ListView MyList
{
get
{
return mylist;
}
}
private async void OnItemSelected(object sender, SelectedItemChangedEventArgs e) {
if (e.SelectedItem == null) return;
await Navigation.PushAsync(new MenuPage());
}
}
XAML:
<!-- Adding OnItemSelected in XAML below -->
<ListView x:Name="mylist"
ItemsSource="{Binding EmployeeList}"
HasUnevenRows="True"
SelectedItem="{Binding LoginName}"
ItemSelected="OnItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" Padding="12,6">
<Label Text="{Binding Name}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
mylist.ItemSelected += async (sender, e) => {
if (e.SelectedItem == null) return;
await Navigation.PushAsync(new MenuPage());
};
This works, had to set the page to a nav wrap in the App.cs, then apply this event handler to the OnAppearing method.
When I run the app, I get two tabs (Im using Tabbed Page) but they are blank.
I have a somewhat complex ViewModel:
public partial class NowPlayingView
{
const string NowPlayingUrl = "http://api.myserver.com";
public static List<MoviesItem> MoviesLst { get; set; }
public NowPlayingView()
{
InitializeComponent();
BindingContext = new MoviesViewModel();
}
public class MoviesViewModel
{
public MoviesViewModel()
{
Action<Dictionary<string, string>> initAction = initialize;
initAction(new Dictionary<string, string>()
{
{"$format", "json"},
{"AccessKey", "f54tg5gf54g-fgs3452-324asdf4"},
{"CineplexLanguage", "en-us"}
});
}
public async void initialize(Dictionary<string,string> parameters)
{
var data = await (new ApiUtilities().CallGetData<MoviesNowPlaying>(NowPlayingUrl, "/api.svc/MoviesNowPlaying", parameters));
MoviesLst = data.d.results.Select(x => new MoviesItem() {Header = x.Title, Text = x.MediumPosterImageURL}).ToList();
}
}
public class MoviesItem
{
public string Header { get; set; }
public string Text { get; set; }
}
}
My XAML file look like this:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamPlex.MainCategories.NowPlayingView"
Title="Now Playing">
<ListView x:Name="MoviesListView" RowHeight="80" BackgroundColor="Transparent" ItemsSource="{Binding MoviesLst}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Vertical" Spacing="0" Padding="10">
<Label Font="Bold,20" Text="{Binding Header}" TextColor="Indigo"/>
<Label Font="16" Text="{Binding Text}" TextColor="Indigo"/>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
I checked the contents of MoviesLst and it contains plenty of data, any ideas what could be wrong?
I do not do a lot of MVC, but I believe the View should have this form:
public ActionResult Index() {
return View();
}
I would think your View should be structured something like...
public ActionResult Index() {
var data = await (new ApiUtilities().CallGetData<MoviesNowPlaying>(NowPlayingUrl, "/api.svc/MoviesNowPlaying", parameters));
var list = data.d.results.Select(x => new MoviesItem() {Header = x.Title, Text = x.MediumPosterImageURL}).ToList();
return View(list);
}
Again, though, I have only worked through the basic tutorials on MVC. I could be misunderstanding your model.
I don't see where you are posting your View anywhere. What I see appears to be the Model.