Error when calling MainActivity method from another class - c#

I have the following api call in my MainActivity.cs -
public async Task DoSomething()
{
var progressDialog = ShowLoading(_instance);
await Task.Factory.StartNew(() =>
{
var objectResponse = string.Empty;
string _apiUrl = "https://my-web-service-endpoint";
var request = (HttpWebRequest)WebRequest.Create(_apiUrl);
request.Method = "GET";
using (var response = _request.HttpWebResponse(request))
{
objectResponse = _request.HttpWebResponseBody(response);
}
progressDialog.Dismiss();
});
}
I am calling this method from a button click in SliderControl.cs -
public void loadTestView(View view)
{
Button btnDoSomething = view.FindViewById<Button>(Resource.Id.btnDoSomething);
if (btnDoSomething != null)
{
btnDoSomething .Click += async (sender, e) =>
{
await MainActivity.GetInstance().DoSomething();
};
}
}
However loadTestView is not called in the MainActivity.cs rather it is called when a used swipes onto a specific view in the application and handled in the SliderControl.cs , it is set like -
public override Java.Lang.Object InstantiateItem(ViewGroup container, int position)
{
var selectedView = Resource.Layout.Locator;
if (position == 0)
{
selectedView = Resource.Layout.Main;
}
if (position == 1)
{
selectedView = Resource.Layout.TestView;
}
View view = LayoutInflater.From(container.Context).Inflate(selectedView, container, false);
container.AddView(view);
if (position == 0)
{
loadMainView(view);
}
else if (position == 1)
{
loadTestView(view);
}
return view;
}
When I set a different buttons click event logic in the MainActivity.cs everything working accordingly, however when setting the button click login as it is above, I am met with the error -
Only the original thread that created a view hierarchy can touch its views.
The odd thing is the loader does actually come into view, however when I step into the await Task.Factory.StartNew(() => line the error hits.
I have tried changing the line to use Task.Run instead but same error, and also found some info on Device.BeginInvokeOnMainThread hwoever believe this is for Xamarin Forms. How can I get this to work?

You should use the UI Thread to update the UI / or any task that involve the UI:
Xamarin Forms Example:
Device.BeginInvokeOnMainThread (() => {
label.Text = "Async operation completed";
});
John wrote the great examples describing how to use Async/Await pattern. You may want to read more here https://johnthiriet.com/configure-await/#
Update: For Xamarin Native -
Problem:
new System.Threading.Thread(new System.Threading.ThreadStart(() => {
lblTest.Text = "updated in thread";
// Doesn't work because you can't modify UILabel on background thread!
})).Start();
Solution:
new System.Threading.Thread(new System.Threading.ThreadStart(() => {
InvokeOnMainThread (() => {
label1.Text = "updated in thread";
// this works! We are invoking in Main thread.
});
})).Start();

Related

App is not loading the Views a second time if receiving a message from MessagingCenter and updating the UI afterwards

I am sending an intent I am receiving through a BroadcastReceiver to multiple Views in my shared-project. The DisplayResult-method implements the MessagingCenter.Send.
public class MyBroadcast : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
String action = intent.Action;
if (action.Equals(MainActivity.Instance.Resources.GetString(intentString)))
{
//Device.BeginInvokeOnMainThread(() => MainActivity.Instance.DisplayResult(intent));
//Task.Run(() => MainActivity.Instance.DisplayResult(intent));
//MainActivity.Instance.RunOnUiThread(() => MainActivity.Instance.DisplayResult(intent));
}
}
}
If a message is received I want to update my Views e.g. like this:
MessagingCenter.Subscribe<object, Model>(this, "HI", (sender, arg) =>
{
var dt = DateTime.Now;
_logger.Debug($"Task started: {dt}");
ActivityIndicator.IsVisible = true;
ActivityIndicator.IsRunning = true;
Task.Run(async () =>
{
await SomeTask();
}).GetAwaiter().GetResult();
_logger.Debug($"Task finished: {DateTime.Now - dt}");
ActivityIndicator.IsRunning = false;
ActivityIndicator.IsVisible = false;
}
});
The issue is if I use the approach with Task.Run(...) my Views are only showing up once but the ActivityIndicator is running. That means if I close a View and go back to the MainPage and navigate to another or the same View again it only shows a white screen.
If I use either Device.BeginInvokeOnMainThread(...) or RunOnUiThread(...) I get the skipped frames error. And my app is frozen until every Task within the MessagingCenter.Subscribe in my Views is finished. The ActivityIndicator is Not showing up.
You can have a try with using async method to invoke these method as follow:
MessagingCenter.Subscribe<PageMain, string>(this, "HI", async (sender, arg) =>
{
var dt = DateTime.Now;
_logger.Debug($"Task started: {dt}");
ActivityIndicator.IsVisible = true;
ActivityIndicator.IsRunning = true;
await SomeTask();
_logger.Debug($"Task finished: {DateTime.Now - dt}");
ActivityIndicator.IsVisible = false;
ActivityIndicator.IsRunning = false;
});

UWP MvvmLight Update UI after getting Messenger.default.Receive

I am using MVVMLight in an UWP application, I have two screens: on MainScreen I have a button to open a second screen, and I also have another button to send some data from MainScreen to the second screen. I am using
Messenger.Default.Send(someobject)
and similarly
Messenger.Default.Register<Some>(this, (action) => ReceiveMsg(action));
I have to click the button on the main screen and send data to other view.
The problem is the data is not getting updated on the second screen, and causing an exception
The application called an interface that was marshalled for a different thread
I tried several ways to update UI, like this, this in fact I have tried all these possibilities like following
private async void ReceiveMsg(Some action)
{
try
{
//await Task.Factory.StartNew(() => {
// T1 = action.T1;
// RaisePropertyChanged(() => T1);
//});
//SharingData.UpdateScore(action);
//DispatcherHelper.Initialize();
//await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal
// , () =>
// {
// T1 = action.T1;
// RaisePropertyChanged(() => T1);
// });
//await Dispatcher.DispatchAsync(() =>
//{
// T1 = action.T1;
// RaisePropertyChanged(() => T1);
//},1000, Windows.UI.Core.CoreDispatcherPriority.Normal);
//await Window.Current.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
//{
// T1 = action.T1;
// RaisePropertyChanged(() => T1);
//});
//var views= Windows.ApplicationModel.Core.CoreApplication.Views.ToList()[0];
//await views.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => {
// T1 = action.T1;
// RaisePropertyChanged(() => T1);
//});
var thiswindow = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher;
await thiswindow.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
team1 = action.Team1;
this.RaisePropertyChanged("Team1");
});
//DispatcherHelper.CheckBeginInvokeOnUI(
//() =>
//{
// T1 = action.T1;
// RaisePropertyChanged(() => T1);
//});
//DispatcherHelper.Reset();
//DispatcherHelper.Initialize();
}
catch (Exception ex)
{
//DispatcherHelper.Reset();
//Console
}
}
I have tried all the above segments one by one, but nothing works and still getting the "marshalled for a different thread" error.
Please tell me what I am doing wrong?
The property is getting update, like T1='Some Value' but not reflecting on UI and on RaisePropertyChanged its giving the exception.
I have to click the button on the main screen and send data to other view. The problem is the data is not getting updated on the second screen, and causing an exception
The application called an interface that was marshalled for a different thread.
The Send and Receive action of Messenger will be invoked in same thread. When you click the button in the MainScreen, the CoreWindow.Dispatcher is dispatcher of MainScreen. If you use this dispatcher to modify the content of second screen, it will throw exception.
To resolve it, you should invoke Send method in the main thread of second screen.
private async void Button_Click(object sender, RoutedEventArgs e)
{
newView = CoreApplication.CreateNewView();
int newViewId = 0;
await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
var id = Environment.CurrentManagedThreadId;
Frame frame = new Frame();
frame.Navigate(typeof(SecondPage), null);
Window.Current.Content = frame;
Window.Current.Activate();
newViewId = ApplicationView.GetForCurrentView().Id;
});
bool viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId);
}
private async void SendBtn_Click(object sender, RoutedEventArgs e)
{
await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
var id = Environment.CurrentManagedThreadId;
Messenger.Default.Send("Test send message!!!");
});
}

Network Activity Spinner Spins Forever

I'm building a Xamarin Forms app and have a problem with the network activity spinner in the Status Bar (iOS) continuing to spin forever. Below is the code I'm using to display a loading screen.
private async Task ExecuteConfigureCampaignCommand()
{
if (IsBusy)
{
return;
}
IsBusy = true;
//ConfigureCampaignCommand.ChangeCanExecute();
bool showAlert = false;
string campaign = null;
try
{
campaign = CrossSecureStorage.Current.GetValue(SettingNames.Campaign);
if (!String.IsNullOrEmpty(campaign))
{
StatusMessage = "Blah";
OnPropertyChanged("StatusMessage");
await Task.Delay(1000);
StatusMessage = "Blah";
OnPropertyChanged("StatusMessage");
await Task.Delay(700);
StatusMessage = "Blah";
OnPropertyChanged("StatusMessage");
await Task.Delay(500);
MessagingService.Current.SendMessage<string>(SettingNames.NavigationSales, campaign);
}
}
catch (Exception e)
{
var abc = e;
showAlert = true;
}
finally
{
IsBusy = false;
//ConfigureCampaignCommand.ChangeCanExecute();
}
if (showAlert)
{
await page.DisplayAlert("Uh Oh :(", "Unable to load campaign.", "OK");
}
}
This is called from a command:
public Command ConfigureCampaignCommand
{
get
{
return configureCampaignCommand ?? (configureCampaignCommand = new Command(async () => await ExecuteConfigureCampaignCommand(), () => { return !IsBusy; }));
}
}
Which is called from OnAppearing:
protected override void OnAppearing()
{
base.OnAppearing();
viewModel.ConfigureCampaignCommand.Execute(null);
}
The logic is fine - the View that I expect is loaded, however the network activity spinner continues spinning. If I add a 1 second delay:
Device.StartTimer(TimeSpan.FromMilliseconds(1), () =>
{
MessagingService.Current.SendMessage<string>(SettingNames.NavigationSales, campaign);
return false;
});
The spinner stops once the new view is loaded. If I remove the await Task.Delay's, the spinner stops once the view is loaded, however the loading messages are not visible to the user.
My question is, why does the network spinner continue?
Whenever you change IsBusy
OnPropertyChanged("IsBusy");
As you are calling this for your other properties but not for IsBusy I guess that this is probably the reason the change doesn't get picked up.

Show a modal UI in the middle of background operation and continue

I have a WPF application running a background task which uses async/await. The task is updating the app's status UI as it progresses. During the process, if a certain condition has been met, I am required to show a modal window to make the user aware of such event, and then continue processing, now also updating the status UI of that modal window.
This is a sketch version of what I am trying to achieve:
async Task AsyncWork(int n, CancellationToken token)
{
// prepare the modal UI window
var modalUI = new Window();
modalUI.Width = 300; modalUI.Height = 200;
modalUI.Content = new TextBox();
using (var client = new HttpClient())
{
// main loop
for (var i = 0; i < n; i++)
{
token.ThrowIfCancellationRequested();
// do the next step of async process
var data = await client.GetStringAsync("http://www.bing.com/search?q=item" + i);
// update the main window status
var info = "#" + i + ", size: " + data.Length + Environment.NewLine;
((TextBox)this.Content).AppendText(info);
// show the modal UI if the data size is more than 42000 bytes (for example)
if (data.Length < 42000)
{
if (!modalUI.IsVisible)
{
// show the modal UI window
modalUI.ShowDialog();
// I want to continue while the modal UI is still visible
}
}
// update modal window status, if visible
if (modalUI.IsVisible)
((TextBox)modalUI.Content).AppendText(info);
}
}
}
The problem with modalUI.ShowDialog() is that it is a blocking call, so the processing stops until the dialog is closed. It would not be a problem if the window was modeless, but it has to be modal, as dictated by the project requirements.
Is there a way to get around this with async/await?
This can be achieved by executing modalUI.ShowDialog() asynchronously (upon a future iteration of the UI thread's message loop). The following implementation of ShowDialogAsync does that by using TaskCompletionSource (EAP task pattern) and SynchronizationContext.Post.
Such execution workflow might be a bit tricky to understand, because your asynchronous task is now spread across two separate WPF message loops: the main thread's one and the new nested one (started by ShowDialog). IMO, that's perfectly fine, we're just taking advantage of the async/await state machine provided by C# compiler.
Although, when your task comes to the end while the modal window is still open, you probably want to wait for user to close it. That's what CloseDialogAsync does below. Also, you probably should account for the case when user closes the dialog in the middle of the task (AFAIK, a WPF window can't be reused for multiple ShowDialog calls).
The following code works for me:
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace WpfAsyncApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Content = new TextBox();
this.Loaded += MainWindow_Loaded;
}
// AsyncWork
async Task AsyncWork(int n, CancellationToken token)
{
// prepare the modal UI window
var modalUI = new Window();
modalUI.Width = 300; modalUI.Height = 200;
modalUI.Content = new TextBox();
try
{
using (var client = new HttpClient())
{
// main loop
for (var i = 0; i < n; i++)
{
token.ThrowIfCancellationRequested();
// do the next step of async process
var data = await client.GetStringAsync("http://www.bing.com/search?q=item" + i);
// update the main window status
var info = "#" + i + ", size: " + data.Length + Environment.NewLine;
((TextBox)this.Content).AppendText(info);
// show the modal UI if the data size is more than 42000 bytes (for example)
if (data.Length < 42000)
{
if (!modalUI.IsVisible)
{
// show the modal UI window asynchronously
await ShowDialogAsync(modalUI, token);
// continue while the modal UI is still visible
}
}
// update modal window status, if visible
if (modalUI.IsVisible)
((TextBox)modalUI.Content).AppendText(info);
}
}
// wait for the user to close the dialog (if open)
if (modalUI.IsVisible)
await CloseDialogAsync(modalUI, token);
}
finally
{
// always close the window
modalUI.Close();
}
}
// show a modal dialog asynchronously
static async Task ShowDialogAsync(Window window, CancellationToken token)
{
var tcs = new TaskCompletionSource<bool>();
using (token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: true))
{
RoutedEventHandler loadedHandler = (s, e) =>
tcs.TrySetResult(true);
window.Loaded += loadedHandler;
try
{
// show the dialog asynchronously
// (presumably on the next iteration of the message loop)
SynchronizationContext.Current.Post((_) =>
window.ShowDialog(), null);
await tcs.Task;
Debug.Print("after await tcs.Task");
}
finally
{
window.Loaded -= loadedHandler;
}
}
}
// async wait for a dialog to get closed
static async Task CloseDialogAsync(Window window, CancellationToken token)
{
var tcs = new TaskCompletionSource<bool>();
using (token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: true))
{
EventHandler closedHandler = (s, e) =>
tcs.TrySetResult(true);
window.Closed += closedHandler;
try
{
await tcs.Task;
}
finally
{
window.Closed -= closedHandler;
}
}
}
// main window load event handler
async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var cts = new CancellationTokenSource(30000);
try
{
// test AsyncWork
await AsyncWork(10, cts.Token);
MessageBox.Show("Success!");
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
}
[EDITED] Below is a slightly different approach which uses Task.Factory.StartNew to invoke modalUI.ShowDialog() asynchronously. The returned Task can be awaited later to make sure the user has closed the modal dialog.
async Task AsyncWork(int n, CancellationToken token)
{
// prepare the modal UI window
var modalUI = new Window();
modalUI.Width = 300; modalUI.Height = 200;
modalUI.Content = new TextBox();
Task modalUITask = null;
try
{
using (var client = new HttpClient())
{
// main loop
for (var i = 0; i < n; i++)
{
token.ThrowIfCancellationRequested();
// do the next step of async process
var data = await client.GetStringAsync("http://www.bing.com/search?q=item" + i);
// update the main window status
var info = "#" + i + ", size: " + data.Length + Environment.NewLine;
((TextBox)this.Content).AppendText(info);
// show the modal UI if the data size is more than 42000 bytes (for example)
if (data.Length < 42000)
{
if (modalUITask == null)
{
// invoke modalUI.ShowDialog() asynchronously
modalUITask = Task.Factory.StartNew(
() => modalUI.ShowDialog(),
token,
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
// continue after modalUI.Loaded event
var modalUIReadyTcs = new TaskCompletionSource<bool>();
using (token.Register(() =>
modalUIReadyTcs.TrySetCanceled(), useSynchronizationContext: true))
{
modalUI.Loaded += (s, e) =>
modalUIReadyTcs.TrySetResult(true);
await modalUIReadyTcs.Task;
}
}
}
// update modal window status, if visible
if (modalUI.IsVisible)
((TextBox)modalUI.Content).AppendText(info);
}
}
// wait for the user to close the dialog (if open)
if (modalUITask != null)
await modalUITask;
}
finally
{
// always close the window
modalUI.Close();
}
}

Control.BeginInvoke does not call delegate when UI function is idle

I am modifying a windows desktop application that works with some external hardware. When the user activates the hardware from the application a progress (UI) form is started. This form creates a thread that performs all of the work with the hardware. The problem comes when I try to report progress back to the UI thread. It appears that the first of my Control.BeginInvoke ("Negotiating message") works fine. However, the second one (first adjustment to progressbar) never seems to call it's delegate and as a result the application locks up on the subsequent endinvoke. I believe the issue is that the GUI is now in an idle state, but I am not sure how to fix the situation. Any help would be appreciated. Code found below:
In the UI Load Method Thread:
private void frmTwainAquire_Load(object sender, EventArgs e)
{
try
{
//Show the GUI
this.Visible = showGUI;
pbScanningProgress.Value = 0;
btnCancel.Enabled = false;
btnCancel.Visible = false;
// Set the delegates.
SetScanMessageDelegate = new SetScanMessage(this.SetScanMessageMethod);
SetRegistrationMessageDelegate = new SetRegistrationMessage(this.SetRegistrationMessageMethod);
AddScanProgressDelegate = new AddScanProgress(this.AddScanProgressMethod);
AddRecogProgressDelegate = new AddRecogProgress(this.AddRecogProgressMethod);
// Set progress bars.
pbScanningProgress.Value = 0;
pbRecognition.Value = 0;
abortScan = false;
// Create thread here!
twainInstance = new rScan.Twain();
rScanning = new rScanThread(this, twainInstance);
// Start the thread.
rScanning.tScan = new Thread(rScanning.Scan);
rScanning.tScan.Start();
}
catch (Exception ex)
{
// Error checking here.
}
}
Delegate Methods:
public void SetScanMessageMethod(string scanMessage)
{
this.lblScanMessage.Text = scanMessage;
}
public void SetRegistrationMessageMethod(string recogMessage)
{
this.lblRecognition.Text = recogMessage;
}
public void AddScanProgressMethod(int progress)
{
this.pbScanningProgress.Value += progress;
}
public void AddRecogProgressMethod(int progress)
{
this.pbRecognition.Value += progress;
}
Thread method that is giving the problem. Please note that the thread is in a different class then the previous two code blocks (both are in the UI class):
public class rScanThread : IMessageFilter
public void Scan()
{
// Set progress bar message.
IAsyncResult result;
if (frmTwainAquireInstance.lblScanMessage.IsHandleCreated && frmTwainAquireInstance.lblScanMessage.InvokeRequired)
{
result = frmTwainAquireInstance.lblScanMessage.BeginInvoke(frmTwainAquireInstance.SetScanMessageDelegate, "Negotiating Capabilities with Scanner.");
frmTwainAquireInstance.lblScanMessage.EndInvoke(result);
}
else
{
frmTwainAquireInstance.lblScanMessage.Text = "Negotiating Capabilities with Scanner.";
}
// Start the intialization of the rScan process.
bool intializeSuccess = twainInstance.Initialize(frmTwainAquireInstance.Handle);
// If the process could not be started then quit.
if (!intializeSuccess)
{
frmTwainAquireInstance.Close();
return;
}
if (frmTwainAquireInstance.pbScanningProgress.IsHandleCreated && frmTwainAquireInstance.pbScanningProgress.InvokeRequired)
{
result = frmTwainAquireInstance.pbScanningProgress.BeginInvoke(frmTwainAquireInstance.AddScanProgressDelegate, 33);
frmTwainAquireInstance.pbScanningProgress.EndInvoke(result); // Lock up here.
}
else
{
frmTwainAquireInstance.pbScanningProgress.Value += 33;
}
// Do more work after. The code never makes it this far.
} // End of rScanThread.Scan()

Categories

Resources