Xamarin Android: Dynamic update UI based on Viewmodel - xamarin

In my android project, the webview url is dynamic. I am using mvvmcros binding on view side but its not being dynamic. If the url content on view model is changing its not being updated on view. Can anyone help me out?
View
public string WebContentUrll { get; set; }
protected override void OnCreate(Android.OS.Bundle bundle)
{
var bindingSet = this.CreateBindingSet<view, ViewModel>();
bindingSet.Bind(this).For(v => v.WebContentUrll).To(vm => vm.WebContentUrl).TwoWay();
}
ViewModel
private string webContentUrl;
public string WebContentUrl
{
get
{
return webContentUrl;
}
set
{
webContentUrl = value;
RaisePropertyChanged(() => webContentUrl);
}
}
public void Init()
{
webContentUrl = "https://.."'
}
The value of web content url in the view model changes after the page is loaded but the android view is not able to get the new updated url.
Can anyone please advise. Thank you.
Update
The web view is opened on a button click and the url is updated after the page loads and before the button is clicked

From you description in the opening post. In your Activity you have defined a property WebContentUrll. You want to bind this and update something when it is changed.
The definition of WebContentUrll is:
public string WebContentUrll { get; set; }
This is not wrong and you should see the value reflected in WebContentUrll when it changes from the ViewModel through your binding. However, there is no code updating any visual states, views or anything based on that property.
If you have a WebView you want to change content for, you could modify your property to something like:
private string _webContentUrll;
public string WebContentUrll
{
get => _webContentUrll;
set
{
_webContentUrll = value;
_webView.LoadUrl(_webContentUrll);
}
}
Given that _webView is your instance of a WebView.

Related

Xamarin Shell navigation passing data to viewmodel doesn't work

I have a route that is registered using the following
Routing.RegisterRoute("SchoolHome", typeof(SchoolHomePage));
I have a page with a list of schools and when I tap a School it executes this just fine from the ViewModel Command
Shell.Current.GoToAsync($"SchoolHome?schoolid={selectedSchool.Id}");
Here is my SchoolHomePage.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class SchoolHomePage : ContentPage
{
public SchoolHomePage()
{
InitializeComponent();
BindingContext = new SchoolHomeViewModel();
}
}
Here is a snippet of SchoolHomeViewModel.cs
[QueryProperty("School", "schoolid")]
public class SchoolHomeViewModel : BaseViewModel
{
string school;
public string School
{
get
{
return school;
}
set
{
SetProperty(ref school, Uri.UnescapeDataString(value));
}
}
So Shell doesn't map my QueryProperty to my property School and the page doesn't navigate. If I put the QueryProperty in the ContentPage's code behind and set the ViewModel's School property there, it will pass the value along but the page still doesn't navigate. It just stays on the page with the list of schools with the one I just tapped being selected.
I found the problem. I had an issue with a property in my viewmodel. Once I fixed that it worked.

Xamarin Native, Binding actions to listview items

I would like to ask about bindings. What is the best approach to bind some actions in listview items in ios and android using xamarin in mvvm world. As I understand, we have few approaches.
1.
For every list item we have some Model, and to this model we have to add some Commands.
For example:
public class ItemModel
{
public string MyName { get; set; }
public ICommand RemoveCommand { get; set; }
}
Where in ViewModel we have SomeInitMethod
public class ViewModel
{
public ObservableCollection<ItemModel> Items {get;set;}
public async Task SomeInitMethod
{
Items = new ObservableCollection(await _myApiService.FetchItemsAsync());
foreach(var item in Items)
{
item.Command = new RelayCommand(RemoveItem);
}
}
public void RemoveItem(ItemModel item)
{
Items.Remove(item);
}
}
But I see a drawback in SomeInitMethod where we should set RemoveCommand. What if we should to set 2 or even more commands than we duplicate code in ListItemView(somehow we need to bind all these commands)?
Next approach is somehow handle events of remove/toggle buttons and others in Listview and then delegate this commands directly to ViewModel.
Example:
ContactsListView.ItemRemoveClicked += (ItemModel model) => ViewModel.RemoveItem
Advantages is: we no longer need to handle commands in ViewModel
Drawback is: we need every time to write custom ListView and support event handling in code-behind.
The last approach is to send ViewModel to ListItem to set Commands.
Example
somewhere we have method CreateListViewItem on the view, let's say on iOS.
private void InitTableView() {
TableView.RegisterNibForCellReuse(ItemViewCell.Nib, ItemViewCell.Key);
var source = new ObservableTableViewSource <ItemModel>
{
DataSource = ViewModel.Items,
BindCellDelegate = (cell, viewModel, index) =>
{
if (cell is ItemModel memberCell)
{
memberCell.BindViewModel(viewModel);
memberCell.RemoveItem = (item) => ViewModel.RemoveItem;
}
}
};
TableView.Source = source;
}
Advantages: we no longer need to have Commands in Model, and we don't need to setup this Commands in ViewModel.
Possibly, drawback is that we somehow need to have ViewModel reference.
In WPF or UWP you have DataContext, you can binding directly to ViewModel.
Which approach you use, maybe I miss something, and it would be perfect if you provide some examples or thoughts.
Thanks.

The model item passed into the dictionary is of type 'Sitecore.Mvc.Presentation.RenderingModel', but this dictionary requires a model item of type 'X'

I am building a solution with Sitecore 7 and ASP.NET-MVC 3 and trying to use a custom model class as described in this blog post by john west.
I have seen several other questions here on SO reporting a similar error with ASP.NET-MVC (without Sitecore), usually related to passing the wrong type of object in controller code, or there being a configuration error with the \Views\web.config file, but neither seem to be the issue here.
this issue is caused when you create a view rendering (possibly others but i haven't tried it) and you have not set up the model in sitecore, so sitecore is passing in its default model.
To fix this you have to go to the layouts section and create a model.
this is the path in sitecore '/sitecore/layout/Models/', in this folder create a 'Model' item and in the model type field you add the reference to your model in the format 'my.model.namespace, my.assembly' without the quotes.
your model needs to inherit 'Sitecore.Mvc.Presentation.IRenderingModel' which forces you to implement the 'Initialize' method, in here you populate data from the sitecore item into the properties of the model. here is an example model...
namespace Custom.Models.ContentBlocks
{
using Sitecore.Data.Fields;
using Sitecore.Mvc.Presentation;
public class BgImageTitleText : IRenderingModel
{
public string Title { get; set; }
public string BgImage { get; set; }
public string BgImageAlt { get; set; }
public string BgColour { get; set; }
public string CtaText { get; set; }
public string CtaLink { get; set; }
public void Initialize(Rendering rendering)
{
var dataSourceItem = rendering.Item;
if (dataSourceItem == null)
{
return;
}
ImageField bgImage = dataSourceItem.Fields[Fields.ContentBlocks.BgImageTitleTextItem.BgImage];
if (bgImage != null && bgImage.MediaItem != null)
{
this.BgImageAlt = bgImage.Alt;
this.BgImage = Sitecore.Resources.Media.MediaManager.GetMediaUrl(bgImage.MediaItem);
}
var title = dataSourceItem.Fields[Fields.ContentBlocks.BgImageTitleTextItem.Title];
if (title != null)
{
this.Title = title.Value;
}
var link = (LinkField)dataSourceItem.Fields[Fields.ContentBlocks.BgImageTitleTextItem.CtaLink];
if (link != null)
{
this.CtaLink = link.GetLinkFieldUrl();
}
var ctaText = dataSourceItem.Fields[Fields.ContentBlocks.BgImageTitleTextItem.CtaText];
if (ctaText != null)
{
this.CtaText = ctaText.Value;
}
var bgColour = dataSourceItem.Fields[Fields.ContentBlocks.BgImageTitleTextItem.BgColour];
if (bgColour != null)
{
this.BgColour = bgColour.Value;
}
}
}
}
Then you have to go to your view rendering (or possibly other types of rendering) and in the 'Model' field you click insert link and click on your newly created model.
This error can be caused when a controller rendering invokes a controller method which returns an ActionResult object instead of a PartialViewResult. In my case I had a rendering model associated with the layout which I believe Sitecore was trying to pass to my controller rendering.
RenderingModel is used when you create a Rendering based on the View Rendering template. This model is created by the sitecore MVC pipelines and is automatically assigned to the view.
To have control over what model to bind to the view, you probably want to use a Controller Rendering, then you can pass in your own model from your controller.

mvvmlight - what's the "proper way" of picking up url parameters for a view model

I'm just switching a project across to mvvmlight and trying to do things "the right way"
I've got a simple app with a listbox
When an item is selected in the listbox, then I've hooked up a RelayCommand
This RelayCommand causes a call on an INavigationService (http://geekswithblogs.net/lbugnion/archive/2011/01/06/navigation-in-a-wp7-application-with-mvvm-light.aspx) which navigates to a url like "/DetailPage.xaml?DetailId=12"
The DetailPage.xaml is then loaded and ... this is where I'm a bit unsure...
how should the DetailPage get hooked up to a DetailView with DetailId of 12?
should I do this in Xaml somehow using a property on the ViewLocator?
should I do this in the NavigatedTo method?
Please feel free to point me to a full sample - sure this has been done a (hundred) thousand times before, but all the blogs and tutorials seem to be skipping this last trivial detail (focussing instead on the messaging and on the ioc on on the navigationservice)
Thanks!
The only place you can retrieve the URL parameter is in the view. So since your view is likely depending on it, you should fetch it in the OnNavigatedTo method.
Then, you should pass it along to your viewmodel, either using messaging (to expensive if you ask me), or by referring to your datacontext (which is the viewmodel I presume), and execeuting a method on that.
private AddTilePageViewModel ViewModel
{
get
{
return DataContext as AddTilePageViewModel;
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var postalCode = NavigationContext.TryGetKey("PostalCode");
var country = NavigationContext.TryGetStringKey("Country");
if (postalCode.HasValue && string.IsNullOrEmpty(country) == false)
{
ViewModel.LoadCity(postalCode.Value, country);
}
base.OnNavigatedTo(e);
}
I'm using some special extensions for the NavigationContext to make it easier.
namespace System.Windows.Navigation
{
public static class NavigationExtensions
{
public static int? TryGetKey(this NavigationContext source, string key)
{
if (source.QueryString.ContainsKey(key))
{
string value = source.QueryString[key];
int result = 0;
if (int.TryParse(value, out result))
{
return result;
}
}
return null;
}
public static string TryGetStringKey(this NavigationContext source, string key)
{
if (source.QueryString.ContainsKey(key))
{
return source.QueryString[key];
}
return null;
}
}
}
Create a new WindowsPhoneDataBound application, it has an example of how to handle navigation between views. Basically you handle the navigation part in your view, then set the view's DataContext accord to the query string. I think it plays nicely with the MVVM pattern since your ViewModels don't have to know anything about navigation (which IMO should be handled at the UI level).

Automatic mapping seems to not like 'Name' fields

I have a model that looks like this.
class Aspect {
Guid Id { get; set; }
string Name { get; set; }
string Description { get; set; }
// multiple other properties
}
In my View (ASP.NET MVC 3.0) I am trying to use the KnockoutJS mapping plugin. I call upon it like this. (Html Helpers listed beneath)
// attempt to bind any data we received from the server
var serverData = #Html.Interpret(Model);
// auto map the knockout attributes from the server data
var viewModel = ko.mapping.fromJS(serverData);
// apply the knockout binding to the viewModel
ko.applyBindings(viewModel, $("#__frmAspect")[0]);
// attach the jquery unobtrusive validator
$.validator.unobtrusive.parse("#__frmAspect");
viewModel.Save = function() {
// we will try to send the model to the server.
ko.utils.postJson(
$("#__frmAspect").attr('action'), { model: ko.toJS(viewModel) }
);
};
// bind the submit handler to unobtrusive validation.
$("#__frmAspect").data("validator").settings.submitHandler = viewModel.Save;
For the most part, this actually works. However, for whatever reason, it does not like the Name field.
It creates it, mind you. If I place a breakpoint at postJson in the knockout.js file, I can sit there and see that the ko.observable() does exist. It just is not getting set by the input field.
Can anyone tell me why this might be?
My Html Helpers:
namespace System.Web.Mvc {
public static class KnockoutHelpers {
public static MvcHtmlString Interpret<TModel>(this HtmlHelper htmlHelper, TModel model) {
return new MvcHtmlString(model.ToJson());
}
}
public static string ToJson ( this object item ) {
return new System.Web.Script.Serialization.JavaScriptSerializer( ).Serialize( item );
}
}
Looks like we got this solved on the KO forums. Autocomplete was not firing the change event on the Name field.
Defined the data bind like: data-bind="value: Name, valueUpdate: 'blur'" to make this work.

Resources