ReadOnlyObservableCollection shows duplicate values - xamarin.forms

I am having trouble with my CollectionView which is showing duplicates objects each time I display the page, meaning the first time I will have 1 object (which is normal), but if I navigate and return to that page it will duplicate the same object, so I get it twice, then thrice ..etc...
Below is the code in the constructor of the ViewModel of my page:
Cache
.AutoRefresh(x => x.Code)
.ObserveOn(RxApp.MainThreadScheduler)
.Bind(out _list)
.DisposeMany();
_list is a ReadOnlyObservableCollection<AddressViewModel>
Cache is IObservable<IchangeSet<AddressModel, string>> obtained from a GetAllObjects from Akavache cache.
_blobCache
.GetAllObjects<AddressModel>()
.ToObservableChangeSet(t => t.Code)
.AsObservableCache()
.Connect();
The binding to my CollectionView is
this.OneWayBind(ViewModel, vm => vm.List, v => v.addressList.ItemsSource).DisposeWith(disposables);
Thank you for any help or hint
UPDATE
The duplicating behaviour happens ONLY when navigating between tabs, if I push a new page and go back I find again 1 object which is the intended behaviour.
Here is the code setting the BindingContext of the page
In code behind of the TabbedPage
public partial class MainTabbedPage : ReactiveTabbedPage<MainViewModel>
{
public MainTabbedPage()
{
InitializeComponent();
this.WhenActivated(
disposables =>
{
this
.OneWayBind(this.ViewModel, x => x.AddressVm, x => x.addressView.ViewModel)
.DisposeWith(disposables);
//other tabs...
});
}
}
In the MainViewModel
public AddressViewModel AddressVm => new AddressViewModel(HostScreen);
public MainViewModel(IScreen hostScreen) : base(hostScreen)
{
}

Related

Update OnPropertyChanged on a Parent View from a child view

In Xamarin for mac, I decided to make multiple views to be used within my main view using the MVVM pattern.
The thing is that I have a ListView within my MainPage which pulls a List of items from a model, and the list is populated within a child view, with its own ViewModel.
When I add a new service from the child view, I would like for the OnPropertyChanged event on the parent view model to trigger.
It is working by navigating to the parent view and setting the animation to false, but this is not really nice looking. It worked though when I had all code within one ViewModel.
How I tried to achieve this, and the errors I got:
0 - Accessing the command within the child model from the parent model, and passing the propertychanged event handler along.
I Couldn't do it. I tried this by making a bindable command like below, but this is not doable for me as I don't think it is possible for the command to know when the property will be changed, which is the whole point of this problem.
If it is doable, I don't know how.
//public static readonly BindableProperty SaveServiceClickedCommandProperty =
// BindableProperty.Create(
// "SaveServiceClicked",
// typeof(Command),
// typeof(NewServiceViewModel),
// null);
1 - Passing the parent view model on the child view model, and put a OnPropertyChanged(nameof(parentModel.List)) at the clicked event handler.
public class ChildViewModel : INotifyPropertyChanged
{
public ICommand AddEntryClickedCommand { get; private set; }
private MainModel mainModel;
// property changed handler
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public NewServiceViewModel()
{
Navigation = MainPage;
//async void execute() => await OpenPage();
//OpenPageCommand = new Command(execute, () => !IsBusy);
//async Task OpenPage()
//{
// await Navigation.PushAsync(new MainPage());
//}
// Here I tried to access the data from within the main model.
mainModel = new MainModel(Navigation);
InitMainModel();
void InitMainModel()
{
MainPage mainView = new MainPage();
mainView.BindingContext = mainModel;
}
async void c1() => await AddEntryClicked();
AddEntryClickedCommand = new Command(c1);
}
public async Task<bool> AddEntryClicked()
{
OnPropertyChanged(nameof(mainModel.List))
}
The attempt above created some errors as the object is already populated.
Leading to me thinking that I don't have the right approach altogether.
My solution being to re-introduce the child view within the parent view, and change IsVisible according to the button being clicked or not, as I already did with other smaller component.
I have thought about pulling the list from the child view, but that's raises the same issue of non-null collection.
Of course, the code has been modified to show only the gist.
Thanks in advance.

How to resolve possible scheduling error with async ReactiveCommand?

I'm relatively new to ReactiveUI and I'm trying to asynchronously execute a database query from a ReactiveCommand. From what I can tell, the problem isn't with executing the async query, but when I try to load the results into a ReactiveList in my view model. I believe that this is a scheduling issue but I'm not familiar enough with RxUI to come up with the correct approach.
I've tried subscribing to the command in the view model using ObserveOn with both RxApp.TaskPoolScheduler and RxApp.MainThreadScheduler but neither seems to help.
My view model:
public class UsersViewModel : ReactiveObject, IRoutableViewModel
{
ReactiveList<LisUser> _users;
IUserManagementService UsersService { get; }
public IScreen HostScreen { get; }
public ReactiveCommand<Unit, IEnumerable<LisUser>> LoadUsers { get; }
public String UrlPathSegment => "users";
public ReactiveList<LisUser> Users
{
get => _users;
set => this.RaiseAndSetIfChanged(ref _users, value);
}
public UsersSubPageViewModel(
IScreen screen,
IUserManagementService usersService)
{
HostScreen = screen ?? throw new ArgumentNullException(nameof(screen));
UsersService =
usersService ?? throw new ArgumentNullException(nameof(usersService));
Users = new ReactiveList<LisUser>();
LoadUsers = ReactiveCommand.CreateFromTask(async () =>
{
return await UsersService.GetUsersAsync().ConfigureAwait(false);
});
LoadUsers
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(list =>
{
Users.Clear();
foreach (var u in list)
{
Users.Add(u);
}
});
}
}
My view:
public partial class UsersView : ReactiveUserControl<UsersViewModel>
{
public UsersPageView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.WhenAnyValue(x => x.ViewModel.LoadUsers)
.SelectMany(x => x.Execute())
.Subscribe()
.DisposeWith(disposables);
});
}
}
What I want to occur is when the UsersView is activated, the GetUsers method of the UsersService executes asynchronously and loads the returned list of users into the Users ReactiveList. Instead, I see a new tab in VS with a title of "Source Not Found" and a message saying that "RxApp.cs not found". The actual exception is a System.Exception with a message stating "Cannot obtain value of the local variable or argument because it is not available at this instruction pointer, possibly because it has been optimized away."
So, my first question is "is this actually a scheduling issue?" And the second question is "how do I resolve it?"
. First as stated in my comment reactivelist is deprecated.
There is a problem with the way the LoadUsers command is invoked.
One is you can do WhenActivated in your view model. You derive your view model from ISupportsActivation, your view will call the WhenActivated block inside your view when it's WhenActivated is called. See https://reactiveui.net/docs/handbook/when-activated/#viewmodels for ideas.
Second way is to use
this.WhenAnyValue(x => x.ViewModel.LoadUsers)
.Select(x => x.Execute())
.Switch()
.Subscribe()
.DisposeWith(disposables);
So the above says get the LoadUsers command, get the Execute() which is a observable, Switch() means only subscribe to the newest first, and dispose the old execute when a new value comes in, subscribe to that we will run the command, and dispose to stop potentially.
It looks like the issue wasn't scheduling, but async related.
IUserManagementService has a reference to an IUserRepository, which itself has an GetUsersAsync method to actually query the database. The actual implementation of IUserManagementService was missing ConfigureAwait on the call to IUserRepository.GetUsersAsync. This wasn't an issue in the unit tests, but definitely a problem once an UI was involved.

ASP.NET CORE Kendo Grid: Populating a grid dynamically

When a user clicks on a date using the Calendar it will populate the grid dynamically with the times. If someone enters data and there is no corresponding record the grid will insert a new record if the user edits an existing row that will fire an update. How do we implement this?
The telerik components operate on a datasource behind the scenes. I find the jquery datasource examples to be helpful for working with them telerik jquery datasource
when you configure your grid and add the save option it will call the sync method on the datasource. the grid will track what types of changes you have made to the datasource ie insert,update,delete and will call the appropriate method for each change that you made.datasource configuration and events. you just need to be sure you have a method in your controller to do what you want with each type of change.
if your grid is setup like below
#(Html.Kendo().Grid<Mymodel>()
.Name("MyGrid")
.Columns(col =>
{
col.Bound(x => x.MyId).Hidden();
col.Bound(x => x.AnotherField).Title("Product Name");
col.Bound(x => x.Athirdfield);
col.Command(cmd => { cmd.Edit(); }).Title("Actions");
})
.ToolBar(tb => { tb.Save(); })
.Editable(ed => ed.Mode(GridEditMode.InLine))
.DataSource(ds => ds
.Ajax()
.Model(md =>
{
md.Id(m => m.MyId);
})
.PageSize(10)
.Read(read => read.Action("Read", "MyGrid"))
.Create(cr => cr.Action("Create","MyGrid"))
.Update(up => up.Action("Update", "MyGrid"))
.Destroy(de => de.Action("Delete", "MyGrid")))
.Filterable()
.Pageable()
)
Now when your grid sees that an insert (or creation) is necessary it will use the method and controller you identified in the .Create() method.
Then your controller just needs to handle the business logic of your operations like below (i only included a read and update method here)
public class MYGridController : Controller
{
private readonly dbContext _context;
public MyGridController(DB context)
{
_context = context;
}
public IActionResult Index()
{
return View();
}
public async Task<IActionResult> Read([DataSourceRequest]DataSourceRequest request)
{
return Json(await _context.MyModel.ToDataSourceResultAsync(request));
}
public async Task<IActionResult> Update([DataSourceRequest]DataSourceRequest request, MyModel pm)
{
try
{
_context.Update(pm);
await _context.SaveChangesAsync();
return Json(await new[] { pm }.ToDataSourceResultAsync(request, ModelState));
}
catch (DbUpdateException)
{
ModelState.AddModelError("", "Error");
}
return Json(await new[] { pm }.ToDataSourceResultAsync(request, ModelState));
}
}
I hope this helps. I know how hard it can be to get the hang of these grids, but they are great once you understand how they work

Xamarin Forms: WhenActivated not being fired reactiveui for viewmodel

I can't understand why the WhenActivated func is not fired for a Xamarin Forms application. The application has a LoginViewModel and a LoginView
LoginView: inherits from ContentPageBase which itself derives from : ContentPage, IViewFor which I think is expected
LoginViewModel: ReactiveObject, IRoutableViewModel, ISupportsActivation
Here's the sample application code.
The first view LoginView is loaded as expected. However, I would ideally like to load services and setup bindings in the WhenActivated func but it is not firing. See the LoginViewModel.
Any ideas?
Source code
thanks
Ok ossentoo, I'm not sure why but seems like if you want to use WhenActivated on your viewmodel you must use when activated on your view, the bindings are placed on view's code behind. Here is a little example:
public class LoginViewModel : ViewModelBase /*IRoutableViewModel, ISupportsActivation etc*/
{
this.WhenActivated(disposables =>
{
Debug.WriteLine("ViewModel activated.").DisposeWith(disposables);
});
}
On the view:
public partial class LoginView : ContentPageBase<LoginViewModel>
{
public LoginView()
{
InitializeComponent ();
this.WhenActivated(new Action<CompositeDisposable>(c =>
{
System.Diagnostics.Debug.WriteLine("From View ! (When Activated)");
/*instead of xaml bindings you can use ReactiveUI bindings*/
this.Bind(ViewModel, vm => vm.UserName,
v => v.UserName.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.Password,
v => v.Password.Text).DisposeWith(disposables);
}));
}
}
Let me know if this helped you. :)

Orchard custom module showing blank "Create" page

I created a custom module for Orchard following this wonderful guide.
I have created a controller called BarberAdminController as follows:
[Admin]
public class BarberAdminController : Controller
{
...
public BarberAdminController(IOrchardServices services, IRepository<BarberPart> repository)
{
_repository = repository;
_services = services;
}
...
public ActionResult Create()
{
var barber = _services.ContentManager.New(typeof(BarberPart).ToString());
dynamic model = _services.ContentManager.BuildEditor(barber);
return View(model);
}
}
View:
#{ Layout.Title = T("New Barber").ToString(); }
#using (Html.BeginFormAntiForgeryPost()) {
#Html.ValidationSummary()
// Model is a Shape, calling Display() so that it is rendered using the most specific template for its Shape type
#Display(Model)
}
Upon clicking the link from the admin menu to create a Barber, I get a blank page with nothing but a "Save" button. (URL: /Admin/BarberShop/Barbers/Create)
Does anyone know what I might be doing wrong?
I've set up the routes and admin links and they seem to work fine. I followed the guide as closely as I could on creating the Drivers and Handlers for BarberPart correctly. Including down to the Migration.cs file database schema.
Any help would be great!
I figured it out.
I needed to define a Content Part and Content Type for BarberPart. In Migrations.cs, do:
ContentDefinitionManager.AlterPartDefinition(typeof(BarberPart).Name, p => p
.Attachable(false));
ContentDefinitionManager.AlterTypeDefinition("Barber", t => t
.WithPart(typeof(BarberPart).Name));
In the "Create" method of the Controller, replace:
var barber = _services.ContentManager.New(typeof(BarberPart).ToString());
with:
BarberPart barber = _services.ContentManager.New<BarberPart>("Barber");
Make sure that you have a Drivers/BarberDriver.cs file as such:
public class BarberDriver : ContentPartDriver<BarberPart>
{
protected override DriverResult Editor(BarberPart part, dynamic shapeHelper)
{
return ContentShape("Parts_Barber_Edit", () => shapeHelper.EditorTemplate(TemplateName: "Parts/Barber", Model: part, Prefix: Prefix));
}
protected override DriverResult Editor(BarberPart part, IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
}
Be sure to have a part edit template located in /Views/EditorTemplates/Parts/Barber.cshtml that looks like this:
#model SDKU.Barbr.Models.BarberPart
<fieldset>
#Html.EditorFor(model => model.SomePropertyName)
etc...
</fieldset>

Resources