Reputation: 3001
My background is WPF (with MVVMLight Toolkit) and WinForms, but I am new to Xamarin Forms and Prism. I am trying to implement a two-step login for an app that I am porting from Swift. In step one, the user types a customer ID (each of our customers has many employees.) In step two, they are shown a list of users for that customer ID, and much choose their name and enter a password.
I am using Xamarin Studio 6.3 for Mac, Xamarin Forms 2.3.4.231, and Prism/Unity 6.3.0
My problem is that when I type the customer ID and click submit, the "OnNavigatingTo" code in the second login view runs, and I successfully retrieve the user list from my web service (this code is in the second ViewModel), but the second view never appears. When I step through code, all of the business logic runs as expected, but from the user's perspective, the view never changes. I am sure I am missing something simple, but I haven't found any help after reading through multiple blog posts on Prism navigation and looking through similar questions on S.O.
app.xaml.cs:
public partial class App : PrismApplication
{
public App(IPlatformInitializer initializer = null) : base(initializer) { }
protected override void OnInitialized()
{
InitializeComponent();
NavigationService.NavigateAsync("DealerLoginPage");
}
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation<DealerLoginPage>();
Container.RegisterTypeForNavigation<UserLoginPage>();
Container.RegisterTypeForNavigation<SalesSummaryPage>();
// Uncomment this section for design data
//Container.RegisterType<ISalesDataRepo, DesignSalesDataRepo>();
// Uncomment this section for runtime data
Container.RegisterType<ISalesDataRepo, AzureSalesDataRepo>();
}
}
DealerLoginPageViewModel.cs (step one of login - also initial view/VM)
public class DealerLoginPageViewModel : BindableBase
{
private string _chosenDealer;
public string ChosenDealer
{
get { return _chosenDealer; }
set { SetProperty(ref _chosenDealer, value); }
}
private INavigationService _navigationService;
public DelegateCommand Submit { get; set; }
public DealerLoginPageViewModel()
{
}
public DealerLoginPageViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
Submit = new DelegateCommand(Submit_Clicked);
}
void Submit_Clicked()
{
NavigationParameters parameters = new NavigationParameters();
parameters.Add("ChosenDealer", ChosenDealer);
_navigationService.NavigateAsync("UserLoginPage", parameters);
}
}
UserLoginViewModel.cs (step two of login process - navigation target)
public class UserLoginPageViewModel : BindableBase, INavigationAware
{
private List<string> _dealerUsers;
public List<string> DealerUsers
{
get { return _dealerUsers; }
set { SetProperty(ref _dealerUsers, value); }
}
private string _selectedUser;
public string SelectedUser
{
get { return _selectedUser; }
set { SetProperty(ref _selectedUser, value); }
}
private string _selectedDealer;
public string SelectedDealer
{
get { return _selectedDealer; }
set { SetProperty(ref _selectedDealer, value); }
}
private INavigationService _navigationService;
private ISalesDataRepo _salesDataRepo;
public DelegateCommand Submit { get; set; }
public UserLoginPageViewModel()
{
}
public UserLoginPageViewModel(INavigationService navigationService, ISalesDataRepo salesDataRepo)
{
_navigationService = navigationService;
_salesDataRepo = salesDataRepo;
Submit = new DelegateCommand(Submit_Clicked);
}
void Submit_Clicked()
{
// unrelated code
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public void OnNavigatedTo(NavigationParameters parameters)
{
}
public void OnNavigatingTo(NavigationParameters parameters)
{
// a breakpoint here gets hit - this code runs, but related view never appears!
if (parameters.ContainsKey("ChosenDealer"))
SelectedDealer = (string)parameters["ChosenDealer"];
if (DealerUsers == null)
{
// we have the dealership, but not the dealership's users yet. Get them asynchronously
var getUsers = _salesDataRepo.GetUserListAsync(SelectedDealer);
getUsers.Start();
// set up the listview once we have the users.
var setUsers = getUsers.ContinueWith((antecedent) =>
{
if (getUsers.Status == System.Threading.Tasks.TaskStatus.RanToCompletion)
{
DealerUsers = getUsers.Result;
}
});
}
}
}
I am navigating to the second view (not the ViewModel), and the expected code in the second ViewModel is running successfully, so it seems like everything is wired up correctly. Does anyone see what I'm doing wrong? Why is the view not changing?
* EXTRA CREDIT *: Does anyone know a way in my app.xaml.cs to create an if-else statement to register the appropriate repository depending on whether I am in design mode? I have seen workarounds where you can check whether App.Current == null to determine whether you are in design mode, but apparently Xamarin.Forms 6.2 "broke" that approach...
Thanks in advance!
Upvotes: 0
Views: 717
Reputation: 5799
To handle design time/mock services vs production services there are a number of techniques that you can use. But you will want a way to easily switch between them, for this I would suggest using compiler symbols. You will want to create a new build profile in your projects for example you might create one called Mock
and then define a symbol for that profile MOCK
.
#if MOCK
Container.Register<IFooService,MockFooService>();
#else
Container.Register<IFooService,FooService>();
#endif
Your csproj file might look like this:
<ItemGroup>
<Compile Include="Services\MockFooService.cs" Condition=" '$(Configuration)' == 'Mock' " />
<Compile Include="Services\FooService.cs" Condition=" '$(Configuration)' != 'Mock' " />
</ItemGroup>
Upvotes: 0
Reputation: 3001
I think I worked through the issue, and it is nothing specific to Prism or Xamarin.Forms; I'm just rusty on my MVVM skillset.
1) Because I am loading the data asyncronously, the view is attempting to bind to the DealerUsers property before it has been assigned a value, therefore the view is presumably throwing and swallowing a NullReferenceException and failing to load (which would be easier to diagnose if there were more visibility of what is happening.) In the ViewModel constructor, I had to new up the list (DealerUsers = new List<string>();
, and then rather than assigning it (DealerUsers = getUsers.Result
in the async method, I needed to loop through the result and add each string in getUsers.Result
to the existing (empty) list one at a time.
2) As I should have remembered, this still doesn't work, because even though the list is populated, the ListView in the View appears empty. To fix this issue, I had to add using System.Collections.ObjectModel;
and change the List<string>
to an ObservableCollection<string>
.
** NOTE ** this solved the bulk of my issue - I am awarding the correct answer to Dan S. for his help in troubleshooting and his working solution to my extra-credit question!
Upvotes: 0