I have implemented a hybrid web view in my Xamarin PCL app.
I am calling a C# function from an Html page using JavaScript in the aforementioned, hybrid web view.
The problem is that although my function is being called, an exception is thrown when I try to redirect from it.
Android.Util.AndroidRuntimeException:
Only the original thread that created a view hierarchy can touch its views.
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.
My code is as follows:
var isValid = AreCredentialsCorrect(user);
if (isValid)
{
try
{
await Navigation.PushAsync(new UserDashboard("local.html?auth_admin=true"));
}
catch { }
}
public UserDashboard(string uriname)
{
InitializeComponent();
hybridWebView.Uri = uriname;
hybridWebView.RegisterAction(data => userLogin(data));
}
Sounds like you're trying to update UI from a background thread. Try doing the navigation from the main thread:
Device.BeginInvokeOnMainThread(async () => await Navigation.PushAsync(new UserDashboard("local.html?auth_admin=true")));
Related
I have a project that needs notifications about upcoming events to show when the user opens the main page, and I'm having problems with async and await (I think). I am using the LocalNotifications Nuget plugin, because that's the only one I was allowed to use for this project. I have some methods that are intended to check for and display notifications. I have tried loading them from my App file, My MainPage.Xaml.cs's OnAppearing method, and from my MainPageViewModel.cs, and the closest that I've gotten to something working is with a combination of creating the Notifications as a class, adding them from the ViewModel and displaying them in the OnAppearing Method of the code behind. The code works right up until the if statement's body in the OnAppearing Method tries to execute and then it just stops and continues on with displaying the main page, but never showing the notifications. I am really new to using asynchronous programming, as well as Xamarin, so I'm pretty confused. This is my relevant code.
//Code starts in MainPageViewModel.cs
public MainPageViewModel()
{
Task.Run(async() => await CheckNotifications());
}
private async Task CheckNotifications()
{
await DatabaseService.Init();
var eventList = await DatabaseService.db.Table<Event>().ToListAsync();
if (eventList.Count > 0)
{
foreach (Event event in eventList)
{
if (event.NotifyStartDate && (event.StartDate.Date == DateTime.Today))
{
Notifications notification = new Notifications
{
Name = $"{event.Name}",
NotifyDate = event.StartDate,
Occurrence = "Starting"
};
await DatabaseService.AddNotification(notification);
}
if (event.NotifyEndDate && (event.EndDate.Date == DateTime.Today))
{
Notifications notification = new Notifications
{
Name = $"{event.Name}",
NotifyDate = event.EndDate,
Occurrence = "Ending"
};
await DatabaseService.AddNotification(notification);
}
}
}
}
This all seems to work fine, it creates a Notification with the event that starts today and adds it to the Notifications Table in the database. Next it moves to my MainPage Code Behind's OnAppearing method--> which is an async void method and I can't figure out any other way to do this, because I get an error that "MainPage.OnAppearing() return type must be void" if I try to make it a Task.
protected override async void OnAppearing()
{
//List fills fine here and holds my Test Event starting today information with a count of 1
var notifications = await DatabaseService.db.Table<Notifications>().ToListAsync();
if (notifications.Count > 0)
{
foreach(Notifications notification in notifications)
{
CrossLocalNotifications.Current.Show("Reminder", $"Event {notification.Name} is {notification.Occurrence} Today" +
$"on {notification.NotifyDate}");
//code stops at this line and continues the rest of the program without executing notification
}
}
base.OnAppearing();
}
My Init, FillSampleData, and AddNotifications methods in the database are all async tasks, and they all await other methods, but the problem seems to occur at OnAppearing. To add to that I am Navigating to the Main Page from App.Xaml.cs Synchrounously, because when I tried to make it an asynchronous method it got ignored there.
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new MainPage());
}
Everywhere else in my app the navigation awaits. I don't know if I'm just doing the notifications all wrong, if I'm just doing Asynchronous programming all wrong, or a little bit of both. Any help would be much appreciated. Thanks guys.
I have a UWP app in which one of the pages needs to do three tasks - the first is to load the main content for the page (a collection of 'Binders' objects retrieved from our API) then after that load some other content which are not dependent on the first task in any way.
My page is backed with a ViewModel (I'm using the default Template10 MVVM model) and when the page is navigated to I do this in the VM OnNavigatedToAsync method:
public async override Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> state)
{
if (mode == NavigationMode.New || mode == NavigationMode.Refresh)
{
IsBusy = true; //Show progress ring
CreateServices(); //Create API service
//Download binders for board and populate ObservableCollection<Binder>
//This has a cover image and other info I want to show in the UI immediately
await PopulateBinders();
//Get files and calendar events for board
//Here I want to run this on a different thread so it does
//not stop UI from updating when PopulateBinders() is finished
await Task.WhenAll(new[]
{
PopulateBoardFiles(),
PopulateBoardEvents()
});
IsBusy = false;
await base.OnNavigatedToAsync(parameter, mode, state);
return;
}
}
So the main task is PopulateBinders() - this calls the API, returns the data and loads it into an ObservableCollection of Binder. When this has run I want the UI to update it's bindings and show the Binder objects immediately but instead it waits until the two other tasks in the WhenAll Task) have run before updating the UI. (All three of these Tasks are defined as private async Task<bool>...)
I realise I'm missing something basic here - but I thought calling a Task from an async method would allow the UI to update? Since it clearly doesn't how do I refactor this to make my page bindings update after the first method?
I tried Task.Run(() => PopulateBinders()); but it made no difference.
Instead of running it inside OnNavigatedToAsync(), run the asynchronous task when the page is already loaded since you are unintentionally "block" the app to run base.OnNavigatedToAsync() for several seconds untill Task.WhenAll finished running.
Running on loaded event in MVVM can be achieved by implementing Microsoft.Xaml.Interactivity to bind Page.Loaded event with a DelegateCommand class in your viewmodel.
XAML Page ( assuming you are using Prism as your MVVM framework )
<Page ...
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Loaded">
<core:InvokeCommandAction Command="{x:Bind Path=Vm.PageLoaded}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Page>
and inside your viewmodel:
public class PageViewModel : ... //some interface or else
{
public DelegateCommand PageLoaded;
public PageViewModel(...)
{
PageLoaded = new DelegateCommand(async () =>
{
IsBusy = true;
CreateServices();
await PopulateBinders();
await Task.WhenAll(new[]
{
PopulateBoardFiles(),
PopulateBoardEvents()
});
IsBusy = false;
});
}
}
Read more : Binding UWP Page Loading/ Loaded to command with MVVM
I hope this code will help you to update the UI as expected:
public async override Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> state)
{
if (mode == NavigationMode.New || mode == NavigationMode.Refresh)
{
IsBusy = true; //Show progress ring
CreateServices(); //Create API service
//Download binders for board and populate ObservableCollection<Binder>
//This has a cover image and other info I want to show in the UI immediately
await PopulateBinders();
await PouplateBoardData();
await base.OnNavigatedToAsync(parameter, mode, state);
return;
}
}
private async void PopulateBoardData()
{
await Task.WhenAll(new[]
{
PopulateBoardFiles(),
PopulateBoardEvents()
});
IsBusy = false;
}
I have a strange problem.I used latest of Xamarin.forms(v 3.1.0.583944).
On iOS, I need to navigate from one page to another page. so I used following code:
NavigationPage Root;
public void SetRootView(NavigationPage root)
{
try
{
App.Instance.MainPage = root;
Root = root;
}
catch (System.Exception ex)
{
// handle exception
}
}
in another method(generic) i declare that has code to push to navigation stack:
public async Task PushViewAsync<TView>() where TView : Page
{
var view = GetView<TView>();
await Root.PushAsync(view);
}
but it does not push it to another page. There are no error or exception. But it just stay there. However if i tap on the screen, it goes to next page.
This is absolutely working fine on android. So not sure what it cause on iOS.
Any thoughts?
You need to be on the UI thread when navigating.
Device.BeginInvokeOnMainThread (async() => {
await Root.PushAsync(view);
});
There's a webview:
<WebView x:Name="webView" Margin="0,10" Grid.RowSpan="3" LoadCompleted="webView_LoadCompleted"/>
And there's a basic snippet of code, which starts a task that will listen to an azure device. Some code is missing from the below example, presume the device was created normally.
The problem, is that I'd like to tell the Webview to navigate to a certain webpage, depending on the content of the received message.
The problem is,. "Window.Current" is null, so it crashes.
public App()
{
Task.Run(ReceiveC2dAsync);
}
private async static Task ReceiveC2dAsync()
{
while (true)
{
Microsoft.Azure.Devices.Client.Message receivedMessage = await deviceClient.ReceiveAsync();
if(receivedMessage != null)
{
// Snip
Task.Run(Navigate);
}
}
}
private async static Task Navigate()
{
try
{
if(Window.Current.Content != null)
((Frame)Window.Current.Content).Navigate(typeof(MainPage), "http://www.google.com");
}
catch(Exception e)
{
Debug.WriteLine("{0} Exception caught.", e);
}
}
In the override code:
protected override void OnLaunched(LaunchActivatedEventArgs e)
The following code can be used to navigate to a desired website when the application launches:
Frame rootFrame = Window.Current.Content as Frame;
if(rootFrame == null) rootFrame = new Frame();
rootFrame.Navigate(typeof(MainPage), "ms-appx-web:///help.html");
So, current is not null at this point.
If i save rootframe as a static, and use it at a later point in the task, I get a marshal error - basically stating the object is referenced to be marshaled to another thread.
My C# knowledge is,. in progress I fear.
So far I've been unable to find a proper explanation on how to have the webview respond to the internal task. Is it possible? And if so, how?
PS: The initial sample code is from: https://github.com/ms-iot/samples/tree/develop/IoTBrowser
The problem, is that I'd like to tell the Webview to navigate to a certain webpage
According to your code snippet, you are developing a UWP app. If you want to know how WebView navigate to a web site, you should be able to use Navigate method of WebView, for example:
webView.Navigate(new Uri("http://www.google.com"));
The following code can be used to navigate to a desired website when the application launches:
The code snippet by default inside OnLaunched navigate to one page by Frame. Frame control supports navigation to Page instances, not web pages, you cannot navigate to a website by Frame. Your above code snippet can only let the rootFrame navigate to MainPage, not the help.html. But you can get the ms-appx-web:///help.html parameter on MainPage and navigate to it by a WebView on MainPage.
I get a marshal error - basically stating the object is referenced to be marshaled to another thread.
If you want to invoke UIElement in a different thread, you should be able to use Core​Dispatcher, cannot invoke directly.
For a conclusion, I think what you actually want to do is navigate to a web site by WebView from a non UI thread. For example:
await Task.Run(Navigate);
private async Task Navigate()
{
try
{
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
webView.Navigate(new Uri("http://www.google.com"));
});
}
catch (Exception e)
{
Debug.WriteLine("{0} Exception caught.", e);
}
}
Hi everyone i am using a WCF Soap Webservice and consuming it inside a Xamarin.Forms MVVM Client. (Mvvm light with MvvmLightNavigationExtension.
The Problem is when i use the Async Methods from the Webservice and corresponding Completed Event in my ViewModel the Navigation stucks / the screen is not changing.
When i debug the current Navigation Page says to be my second page but the first Page is still showing.
I Also tried it with Messengers but that wont work eather.
Down Below is some example Code.
Fun fact: When i make a second Button/Command and navigate to my second page after i called the GetLoginResponseCommand i can navigate Back two times to the first page.
Its Probably some weird Threading/UI Thread thing but i don't get it.
public LoginViewModel(INavigationService navigationService, MyWebService service)
{
_navigationService = navigationService;
_myWebService = service;
_myWebService.GetLoginInfoCompleted += MyWebServiceOnGetLoginInfoCompleted;
}
public RelayCommand GetLoginResponseCommand
{
get
{
return _getLoginResponseCommand ?? (_getLoginResponseCommand = new RelayCommand(
() =>
{
_orkaWebService.GetLoginInfoAsync(request);
}));
}
}
private void MyWebServiceOnGetLoginInfoCompleted(object sender, GetLoginInfoCompletedEventArgs e)
{
_navigationService.NavigateTo(VmKeys.ArtikelBestandKey);
}
Try to use async/ await in your async methods.
return _getLoginResponseCommand ?? (_getLoginResponseCommand = new RelayCommand(
async () =>
{
await _orkaWebService.GetLoginInfoAsync(request);
}));
I Got it myself.
The MyWebServiceOnGetLoginInfoCompleted runs in an own Thread. But the _navigationService needs to be run on the UIThread.
(Same for ContentPage.DisplayAlert or ContentPage.DisplayActionSheet)
When you run it like Device.BeginInvokeOnMainThread(() => _navigationService.NavigateTo(VmKeys.ArtikelBestandKey)); it works like a charm!
private void MyWebServiceOnGetLoginInfoCompleted(object sender, GetLoginInfoCompletedEventArgs e)
{
Device.BeginInvokeOnMainThread(() => _navigationService.NavigateTo(VmKeys.ArtikelBestandKey));
}
Thanks to Rohit for leading me to the right solution.