ReactiveCollection and invalid cross-thread access - windows-phone-7

I'm developing my first app for Windows Phone 7.1 using Reactive UI, and I'm facing difficulties working with ReactiveCollection class.
My app is roughly about accessing the WP7 SQL CE engine (LINQ to SQL). I would like to perform data operations in background, using ReactiveAsyncCommand. Data from database should "automagically" appear in UI, therefore I decided to use ReactiveCollection as a proxy between database and UI.
Here's my model, so you could have a better idea:
public class EncounteredModel
{
public ReactiveCollection<Fact> FactsCollection;
public ReactiveCollection<FactEntry> FactEntriesCollection;
private EncounteredModel()
{
using (EncounteredDataContext db = new EncounteredDataContext())
{
FactsCollection = new ReactiveCollection<Fact>(from fact in db.Facts select fact);
FactEntriesCollection = new ReactiveCollection<FactEntry>(from factEntry in db.FactEntries select factEntry);
}
FactsCollection.ItemsAdded.Subscribe(fact => { using (var db = new EncounteredDataContext()) { db.Facts.InsertOnSubmit(fact); db.SubmitChanges(); } });
FactsCollection.ItemsRemoved.Subscribe(fact => { using (var db = new EncounteredDataContext()) { db.Facts.DeleteOnSubmit(fact); db.SubmitChanges(); } });
FactEntriesCollection.ItemsAdded.Subscribe(factEntry => { using (var db = new EncounteredDataContext()) { db.FactEntries.InsertOnSubmit(factEntry); db.SubmitChanges(); } });
FactEntriesCollection.ItemsRemoved.Subscribe(factEntry => { using (var db = new EncounteredDataContext()) { db.FactEntries.DeleteOnSubmit(factEntry); db.SubmitChanges(); } });
}
private static EncounteredModel instance;
public static EncounteredModel Instance
{
get
{
if (instance == null)
instance = new EncounteredModel();
return instance;
}
}
}
In my view model I've tried using 2 different variants:
Create a derived reactive collection and bind UI to it (With ReactiveCollection.CreateDerivedCollection() method. It's derived from the EncounteredModel.FactsCollection, for example.
Use ObservableAsPropertyHelper<IEnumerable<Fact>>, then subscribe to Model's ReactiveCollection Changed IObservable to populate the OAPH.
Both variants, unfortunately, give me "Invalid cross-thread access." Stack trace is generally the same for both variants, here's one for the variant 1 (shortened to the significant part):
at MS.Internal.XcpImports.CheckThread()
at System.Windows.DependencyObject.GetValueInternal(DependencyProperty dp)
at System.Windows.FrameworkElement.GetValueInternal(DependencyProperty dp)
at System.Windows.DependencyObject.GetValue(DependencyProperty dp)
at System.Windows.Controls.ItemsControl.get_ItemsHost()
at System.Windows.Controls.ItemsControl.OnItemsChangedHandler(Object sender, ItemsChangedEventArgs args)
at System.Windows.Controls.ItemContainerGenerator.OnItemAdded(Object item, Int32 index, Boolean suppressEvent)
at System.Windows.Controls.ItemContainerGenerator.System.Windows.Controls.ICollectionChangedListener.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)
at System.Windows.Controls.WeakCollectionChangedListener.SourceCollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
at System.Windows.Controls.ItemCollection.NotifyCollectionChanged(NotifyCollectionChangedEventArgs e)
When I change to ReactiveCommand (not async one), everything's fine. Any ideas how to overcome this? Much thanks in advance.

ReactiveCollection doesn't proxy everything to the UI thread, if you add items to it from a worker thread, you will signal the UI on that same thread and get the crash.
However, one thing that ReactiveAsyncCommand does for you is give you back the results back on the UI thread, so you can do something like this:
var cmd = new ReactiveAsyncCommand();
cmd.RegisterAsyncFunc(() => getAllTheItems())
.Subscribe(theItems => theItems.ForEach(item => collection.Add(item)));
The Subscribe is guaranteed to be on the UI thread (if it isn't, that's a bug)

Related

UI gets stuck while loading lot of data from realm

I am trying to show 10,000 contacts on listview in xamarin forms using realm. But whenever user traverse to contact listing screen
it gets freezed and loads after a while.
Moreover , i have provided an option to search from list as well and that too gets stuck as search if performing on UI thread.
Following is the code to load data from realm
public override async Task Initialize(Object data )
{
private Realm _realmInstance = getRealm();
if (contactList != null)
{
contactList.Clear();
}
contactList = _realmInstance.All<Contact>().OrderByDescending(d => d.contactId).ToList();
// here the binding happens with realm data
contacts = new ObservableCollectionFast<Contact>(contactList);
}
public ObservableCollectionFast<Contact> contacts
{
get { return items; }
set
{
items = value;
OnPropertyChanged("contacts");
}
}
as it was taking time in loading i thought to fetch realm data in background and bind it on UI thread as follows
but that is throwing error
realm accessed from incorrect thread
await Task.Run(() => {
contactList = _realmInstance.All<Contact>().OrderByDescending(d => d.contactId).ToList();
});
if (contactList.Count() > 0)
{
ContactListView = true;
AddContactMsg = false;
}
else
{
AddContactMsg = true;
}
Device.BeginInvokeOnMainThread(() =>
{
contacts = new ObservableCollectionFast<Contact>(contactList);
});
i wanted to try limiting the results by using TAKE function of LINQ but unfortunately its not supported by realm yet. not sure how i can smoothly load records from realm to listview.
EDIT
as per the SushiHangover i have changed things from IList to IQueryable
public IQueryable<Contact> contacts
{
get { return items; }
set
{
items = value;
OnPropertyChanged("contacts");
}
}
public override async Task Initialize(Object data )
{
_realmInstance = getRealm();
contacts = dbContactList= _realmInstance.All<Contact>();
}
so far search is working pretty smoothly but IQueryable change leads to another issue. on repeatedly performing following steps results in app crash
tap on list item
detail page gets open then press back
scroll down to few records
perform step 1 and repeat
this results into stackoverflow error
04-19 06:05:13.980 F/art ( 3943): art/runtime/runtime.cc:289]
Pending exception java.lang.StackOverflowError thrown by 'void
md5b60ffeb829f638581ab2bb9b1a7f4f3f.CellAdapter.n_onItemClick(android.widget.AdapterView,
android.view.View, int, long):-2' 04-19 06:05:13.980 F/art (
3943): art/runtime/runtime.cc:289] java.lang.StackOverflowError: stack
size 8MB
Link to entire log file
code to fire item click command is
public ICommand OnContactSelectCommand => new Command<Contact>(OnContactSelect);
following code will open ups a detail page
private async void OnContactSelect(Contact contact)
{
if (contact != null)
{
await NavigationService.NavigateToAsync<ContactDetailViewModel>(contact.mContactId);
}
}
Note:
when i replace IQueryable with List i do not face any error
somehow my issue is related to realm github thread where user is getting exception on listView.SelectedItem = null while using IQueryable
here is my code of list view item tap
private static void ListViewOnItemTapped(object sender, ItemTappedEventArgs e)
{
var listView = sender as ListView;
if (listView != null && listView.IsEnabled && !listView.IsRefreshing)
{
// have commented this line because it was crashing the app
//listView.SelectedItem = null;
var command = GetItemTappedCommand(listView);
if (command != null && command.CanExecute(e.Item))
{
command.Execute(e.Item);
}
}
}

Web API Routing changes after DB Refresh

This is an exceedingly strange problem and I'm only including it as an oddity in case someone's seen it before.
I am on C#, ASP.Net web api, Fluent Nhibernate and SQL Server 2012
I have a Meal object that I pass to a web api controller method :
[HttpPost]
[ActionName("PostMeal")]
public HttpResponseMessage PostMeal(Meal mealToPost)
{
var helper = new Datahelper();
try
{
var fshelper = new FoodServiceHelper(helper);
fshelper.SaveMeal(mealToPost);
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (Exception ex)
{
var response = Request.CreateErrorResponse(HttpStatusCode.NotAcceptable, ex);
return response;
}
}
The method that posts is similarly very simple:
public void SaveMeal(Meal mealToPost)
{
var url = _connectionString + "food/PostMeal";
var client = new HttpClient();
var formatter = new JsonMediaTypeFormatter
{
SerializerSettings = { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }
};
var clientTask = client.PostAsync(url, mealToPost, formatter)
.ContinueWith(posttask => posttask.Result.EnsureSuccessStatusCode());
clientTask.wait();
}
The meal object is pretty straightforward. Some elementaries, some generic collections.
I am able to post normally for several (usually about 20) meals, at which point it is somehow no longer able to find the route. I verified this by adding breakpoints to the server side. It hits it the first several times, then it won't even find the controller method.
Here's the weird part. If I drop and recreate the database, it's ok again for another 20 or so meals.
I suspect that this is some kind of mapping issue, but I cannot prove it.
Curious.

Prism Shell buttons shared by modules

I am using Prism 2, trying to add four navigation buttons (First Record, Last Record, Previous Record, Next Record) in shell to be used by modules. I also want these buttons to be disable if active View/ViewModel does not provide these functions.
I tried using events but didn't know how to achieve my second goal regarding disabling buttons. It seems I need to check current active View/ViewModel to see if they subscribed the click event during View switch. But I think publisher should be unaware of subscriber...
Somehow I tried my own way. I create an IDocNavigation interface which has four method corresponding to my four buttons. At runtime I check modules' ViewModel if they implemented that interface or not, and change the ICommand on fly. Below is my code. I include one LastRecordCommand only:
public ShellViewModel(Views.Shell shell)
{
this.Shell = shell;
shell.DataContext = this;
shell.MainDocking.ActivePaneChanged += (s, e) =>
{
if (e.NewPane.Content is UserControl &&
((UserControl)e.NewPane.Content).DataContext is IDocumentNavigate)
{
IDocumentNavigate vm = ((UserControl)e.NewPane.Content).DataContext as IDocumentNavigate;
LastRecordCommand = new RelayCommand(x => vm.GotoLastRecord(), x => true);
}
else
{
LastRecordCommand = new RelayCommand(x => { }, x => false);
}
};
//...
I feel these are quite ugly. Creating an empty RelayCommand is also stupid. How can I improve ? or how can I achieve disabling command if event is more suitable in my case ?
You can make use of CompositeCommand in prism.
Define a globally available CompositeCommand
public static readonly CompositeCommand FirstRecord= new CompositeCommand(true);
Then in your your module view models
class Module1
{
public DelegateCommand Module1Firstrecord{ get; set; }
Module1()
{
Module1Firstrecord = new DelegateCommand(this.FirstRecord, CanExecute);
}
private void FirstRecord()
{
//do whatever you want
}
private bool CanExecute()
{
return true;
}
private void Module1_IsActiveChanged(object sender, EventArgs e)
{
//Find if your window is acive
// if it is active Module1Firstrecord.IsActive = true
//else false.
}
}
With IActiveAware you can handle the active window scenario easily. According to whether your active module have a handler for the command on not the buttons will enable/disable.

Accessing User in Entity partial class via OnContextCreated()?

All of my tables have common audit fields: modifiedBy,modifiedDateTime, etc.
I would like to have these automatically set, and can set most of them with the following code:
partial class myEntities
{
partial void OnContextCreated()
{
this.SavingChanges += new EventHandler(Entities_SavingChanges);
}
private void Entities_SavingChanges(object sender, EventArgs e)
{
IEnumerable<ObjectStateEntry> objectStateEntries =
from ose
in this.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified)
where ose.Entity != null
select ose;
var auditDate = DateTime.Now;
var auditUser = System.Web.HttpContext.Current.User.Identity.Name;//I wish
foreach (ObjectStateEntry entry in objectStateEntries)
{
ReadOnlyCollection<FieldMetadata> fieldsMetaData = entry.CurrentValues.DataRecordInfo.FieldMetadata;
FieldMetadata modifiedField = fieldsMetaData.Where(f => f.FieldType.Name == "ModifiedBy").FirstOrDefault();
if (modifiedField.FieldType != null)
{
string fieldTypeName = modifiedField.FieldType.TypeUsage.EdmType.Name;
if (fieldTypeName == PrimitiveTypeKind.String.ToString())
{
entry.CurrentValues.SetString(modifiedField.Ordinal, auditUser);
}
}
}
}
}
The problem is that there doesn't appear to be any way to get access to the current user. The app is intranet only, using Windows auth.
Is there a way to either pass in a parameter, or get access to the HttpContext (which doesn't seem like it would be a good idea, but I'm stuck)? Is there a way to populate the EventArgs with information?
Check out the section where the poster has overridden the SaveChanges method (6th code box down on the page). This way you can pass in the UserID and perform your audit and not have to use an event handler.

Errors using a LINQ query in a Silverlight application

I am trying to consume the WCF services using Silverlight application in Sharepoint.
It's going to display all the data from a list in a grid. Somehow it is throwing a error.
Cannot convert lambda expression to type 'system.Delegate' because it is not a delegate type.
using the generic type 'system.collections.generic.ienumerable' requires 1 type arguments
SLprojectsCRUD2010WCF.sharepointservice.list1item' is a type but is used like a variable.
How can this be solved?
private SharePointService.SkinnyBlondeDataContext _context;
public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(LayoutRoot_Loaded);
}
private void ShowProjects()
{
// Declare the LINQ query
var projectsQuery = (from p in _context.Tasks
select p) as DataServiceQuery<SharePointService.TasksItem>;
// Execute the LINQ query
projectsQuery.BeginExecute((IAsyncResult asyncResult) => Dispatcher.BeginInvoke(() =>
{ // Runs in the UI thread
// EndExecute returns
IEnumerable < TasksItem > this.dataGridProjects.ItemsSource = projectsQuery.EndExecute(asyncResult).ToList();
}), projectsQuery);
}
private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
{
// Get the context
_context = new SharePointService.SkinnyBlondeDataContext(
new Uri("http://vanir0269/_vti_bin/listdata.svc", UriKind.Absolute));
ShowProjects();
}
Until your source code is formatted properly it'll be a pain to see what the LINQ problem is, but the lambda expression problem is easy: Dispatcher.BeginInvoke takes a Delegate, and lambda expressions can only be converted into specific delegate types. This is easy to fix:
projectsQuery.BeginExecute((IAsyncResult asyncResult) => {
Action action = () => {
// Code in here
};
Dispatcher.BeginInvoke(action, null);
});

Resources