Windows Phone 7 - wait for Webclient to complete - windows-phone-7

I'm developing an app and have run into a problem with asynchronous calls... Here's what i'm trying to do.
The app consumes a JSON API, and, when run, fills the ListBox within a panorama item with the necessary values (i.e. a single news article). When a user selects a ListBox item, the SelectionChanged event is fired - it picks up the articleID from the selected item, and passes it to an Update method to download the JSON response for the article, deserialize it with JSON.NET, and taking the user to the WebBrowser control which renders a html page from the response received.
The problem with this is that I have to wait for the response before I start the NavigationService, but I'm not sure how to do that properly. This way, the code runs "too fast" and I don't get my response in time to render the page.
The event code:
private void lstNews_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (lstNews.SelectedIndex == -1)
{
return;
}
ShowArticle _article = new ShowArticle();
ListBox lb = (ListBox)sender;
GetArticles item = (GetArticles)lb.SelectedItem;
string passId = ApiRepository.ApiEndpoints.GetArticleResponseByID(item.Id);
App.Current.JsonModel.JsonUri = passId;
App.Current.JsonModel.Update();
lstNews.SelectedIndex = -1;
NavigationService.Navigate(new Uri("/View.xaml?id=" + item.Id, UriKind.Relative));
}
OnNavigatedTo method in the View:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
long sentString = long.Parse(NavigationContext.QueryString["id"]);
string articleUri = ApiRepository.ApiEndpoints.GetArticleResponseByID(Convert.ToInt32(sentString));
//this throws an error, runs "too fast"
_article = App.Current.JsonModel.ArticleItems[0];
}
The update method:
public void Update()
{
ShowArticle article = new ShowArticle();
try
{
webClient.DownloadStringCompleted += (p, q) =>
{
if (q.Error == null)
{
var deserialized = JsonConvert.DeserializeObject<ShowArticle>(q.Result);
_articleItems.Clear();
_articleItems.Add(deserialized);
}
};
}
catch (Exception ex)
{
//ignore this
}
webClient.DownloadStringAsync(new Uri(jsonUri));
}

async callback pattern:
public void Update(Action callback, Action<Exception> error)
{
webClient.DownloadStringCompleted += (p, q) =>
{
if (q.Error == null)
{
// do something
callback();
}
else
{
error(q.Error);
}
};
webClient.DownloadStringAsync(new Uri(jsonUri));
}
call:
App.Current.JsonModel.Update(() =>
{
// executes after async completion
NavigationService.Navigate(new Uri("/View.xaml?id=" + item.Id, UriKind.Relative));
},
(error) =>
{
// error handling
});
// executes just after async call above

Related

AlertDialog not showing at .show() - Xamarin Android

I have the following code:
private void CloseOrder(object sender, EventArgs e)
{
Android.Support.V7.App.AlertDialog.Builder alert = new Android.Support.V7.App.AlertDialog.Builder(this);
alert.SetTitle("Cerrar Pedido");
alert.SetMessage("Are you sure?");
alert.SetCancelable(true);
alert.SetPositiveButton("Confirm", delegate { this.Rta = true; });
alert.SetNegativeButton("Cancel", delegate { this.Rta = false; });
Dialog dialog = alert.Create();
dialog.Show();
if (this.Rta)
{
//Some code here
}
}
this.Rta is a property of my class.
The problem is that the alert doesn't show at dialog.show(), it shows once the method CloseOrder() ended, so this.Rta never gets the corresponding value assigned.
I've been searching a lot but I can't find a solution, if anyone can help me that'd be great!
dialog.Show() is asynchronous method, that means CloseOrder(object sender, EventArgs e) and dialog.Show() end up at the same time.
You can not get the 'Rta' assigned value at the CloseOrder function.
You will get the value when you click the confirm or cancel button of the dialog.
I suggest you to use message sender in the delegate{this.Rta = true}
For example:
mHandler handler = new mHandler();
Message message = new Message();
message.What = 1;
alert.SetPositiveButton("Confirm", delegate { this.Rta = true; handler.SendMessage(message); });
alert.SetNegativeButton("Cancel", delegate { this.Rta = false; handler.SendMessage(message); });
//....
class mHandler : Handler{
public override void HandleMessage(Message message) {
switch (message.What) {
case 1:
if (this.Rta)
{
//Some code here
}
break;
}
}
}

OnNavigatedTo in ViewModel isn't firing when page first loads

I'm trying to implement Azure Active Directory B2C in Xamarin.Forms. If I just copy their example, I can get it to work without a problem. But when I try to use Prism, I run into problems.
I took this code that was sitting in the codebehind of the XAML:
protected override async void OnAppearing ()
{
base.OnAppearing ();
App.PCApplication.PlatformParameters = platformParameters;
try {
var ar = await App.PCApplication.AcquireTokenSilentAsync(
AuthenticationInfo.Scopes, string.Empty, AuthenticationInfo.Authority,
AuthenticationInfo.SignUpSignInpolicy, false);
AuthenticationInfo.UserAuthentication = ar;
} catch {
}
}
async void OnSignUpSignIn(object sender, EventArgs e)
{
try {
var ar = await App.PCApplication.AcquireTokenAsync(
AuthenticationInfo.Scopes, string.Empty, UiOptions.SelectAccount,
string.Empty, null, AuthenticationInfo.Authority,
AuthenticationInfo.SignUpSignInpolicy);
AuthenticationInfo.UserAuthentication = ar;
} catch (Exception ex) {
if (ex != null) {
}
}
}
and moved it to the ViewModel's OnNavigatedTo:
public async void OnNavigatedTo (NavigationParameters parameters)
{
if (parameters.ContainsKey ("title"))
Title = (string)parameters ["title"];
listen2asmr.App.PCApplication.PlatformParameters = platformParameters;
try {
var ar = await listen2asmr.App.PCApplication.AcquireTokenSilentAsync(
AuthenticationInfo.Scopes, string.Empty, AuthenticationInfo.Authority,
AuthenticationInfo.SignUpSignInpolicy, false);
AuthenticationInfo.UserAuthentication = ar;
} catch {
}
}
This is in the Bootstrapper:
protected override Xamarin.Forms.Page CreateMainPage ()
{
return Container.Resolve<LoginPage> ();
}
protected override void RegisterTypes ()
{
Container.RegisterTypeForNavigation<LoginPage>();
}
OnNavigatedTo never seems to get called though. Is there some other method I should be using, or did I miss something else? The only other thing I could think of was to call the code in OnNavigatedTo from the ViewModel constructor, but the async/await does work with the constructor.
This has been fixed in the latest preview version of Prism for Xamarin.Forms. Try using these packages instead:
https://www.nuget.org/packages/Prism.Forms/6.1.0-pre4
https://www.nuget.org/packages/Prism.Unity.Forms/6.2.0-pre4
Also the bootstrapping process has changed. Read this for more information:
Prism.Forms 5.7.0 Preview - http://brianlagunas.com/first-look-at-the-prism-for-xamarin-forms-preview/
Prism.Forms 6.2.0 Preview - http://brianlagunas.com/prism-for-xamarin-forms-6-2-0-preview/
Prism.Forms 6.2.0 Preview 3 - http://brianlagunas.com/prism-for-xamarin-forms-6-2-0-preview-3/
Preview 4 Post Coming Soon
My advice is use your View events as triggers for your ViewModel.
For Instance:
View.xaml.cs
protected override async void OnAppearing () {
base.OnAppearing ();
viewModel.OnAppearing();
}
async void OnSignUpSignIn(object sender, EventArgs e) {
viewModel.OnSignUpSignIn(sender, e);
}
ViewModel.cs
protected override async void OnAppearing () {
App.PCApplication.PlatformParameters = platformParameters;
try {
var ar = await App.PCApplication.AcquireTokenSilentAsync(
AuthenticationInfo.Scopes, string.Empty,
AuthenticationInfo.Authority,
AuthenticationInfo.SignUpSignInpolicy, false);
AuthenticationInfo.UserAuthentication = ar;
} catch {
}
}
async void OnSignUpSignIn(object sender, EventArgs e) {
try {
var ar = await App.PCApplication.AcquireTokenAsync(
AuthenticationInfo.Scopes, string.Empty,
UiOptions.SelectAccount,
string.Empty, null, AuthenticationInfo.Authority,
AuthenticationInfo.SignUpSignInpolicy);
AuthenticationInfo.UserAuthentication = ar;
} catch (Exception ex) {
if (ex != null) {
}
}
}
Reasons:
View should only involve visuals, and the events your page receives. Logic should be forwarded to the ViewModel, unless it deals with representation of information (for instance, logic to use a toggle-box for 2 choices but a combo-box for 3+).
Nearly vice-versa, the ViewModel should keep track of "model state" (ex. the user still needs to enter their payment information) as opposed to "view state" (ex. the user has navigated to the payment page).

Sharing an image in Windows Phone 8.1

I'd like to share an image in my app. However, the image is not located in a folder but it is "taken dynamically". Basically i have an Image object
Image i = new Image() { Source = await CreateBitmapFromElement(stackpanel1) };
where CreateBitmapFromElement is defined as follows
private async Task<RenderTargetBitmap> CreateBitmapFromElement(FrameworkElement uielement)
{
try
{
var renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(uielement);
return renderTargetBitmap;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
}
return null;
}
The Windows Phone Share Contract allows to share images located in the Picture Library (for example), but what should i use in this case?
protected override void OnNavigatedTo(NavigationEventArgs e)
{
DataTransferManager.GetForCurrentView().DataRequested += OnShareDataRequested;
base.OnNavigatedTo(e);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
DataTransferManager.GetForCurrentView().DataRequested -= OnShareDataRequested;
base.OnNavigatedFrom(e);
}
private void OnShareDataRequested(DataTransferManager sender, DataRequestedEventArgs _dataRequestedEventArgs)
{
DataRequest request = _dataRequestedEventArgs.Request;
request.Data.Properties.Title = "KeyTreat Sticker";
request.Data.Properties.Description = "KeyTreat Sticker: " + StickerName;
// Because we are making async calls in the DataRequested event handler,
// we need to get the deferral first.
DataRequestDeferral deferral = request.GetDeferral();
// Make sure we always call Complete on the deferral.
try
{
request.Data.SetStorageItems(storageItemsObject);
request.Data.Properties.Thumbnail = RandomAccessStreamReference.CreateFromFile(StorageFileObject);
request.Data.SetBitmap(RandomAccessStreamReference.CreateFromFile(StorageFileObject));
}
finally
{
deferral.Complete();
}
}

webclient I/O error while sending multiple request to API

I want to make Twitter Sentiment Analysis Windows Phone Application.
the application works by retrieving all the related tweets based on what query terms that users entered. for example, if I enter "Windows Phone" in input search box, the results will show all the tweet that contains "windows phone" terms.
here's the code (that I get from Arik Poznanski's Blog)
/// <summary>
/// Searches the specified search text.
/// </summary>
/// <param name="searchText">The search text.</param>
/// <param name="onSearchCompleted">The on search completed.</param>
/// <param name="onError">The on error.</param>
public static void Search(string searchText, Action<IEnumerable<Twit>> onSearchCompleted = null, Action<Exception> onError = null, Action onFinally = null)
{
WebClient webClient = new WebClient();
// register on download complete event
webClient.OpenReadCompleted += delegate(object sender, OpenReadCompletedEventArgs e)
{
try
{
// report error
if (e.Error != null)
{
if (onError != null)
{
onError(e.Error);
}
return;
}
// convert json result to model
Stream stream = e.Result;
DataContractJsonSerializer dataContractJsonSerializer = new DataContractJsonSerializer(typeof(TwitterResults));
TwitterResults twitterResults = (TwitterResults)dataContractJsonSerializer.ReadObject(stream);
App thisApp = Application.Current as App;
thisApp.klasifikasi = new Klasifikasi();
foreach (Twit Tweet in twitterResults.results)
{
try
{
thisApp.klasifikasi.UploadData(Tweet); //requesting
break;
}
finally
{
// notify finally callback
if (onFinally != null)
{
onFinally();
}
}
}
//thisApp.klasifikasi.UploadDatas(twitterResults.results);
//thisApp.PositiveTweetModel = new PositiveTweetModel("Positive", twitterResults.results);
// notify completed callback
if (onSearchCompleted != null)
{
onSearchCompleted(twitterResults.results);
/// Divide the list here
}
}
finally
{
// notify finally callback
if (onFinally != null)
{
onFinally();
}
}
};
string encodedSearchText = HttpUtility.UrlEncode(searchText);
webClient.OpenReadAsync(new Uri(string.Format(TwitterSearchQuery, encodedSearchText)));
}
and to call the method
TwitterService.Search(
text,
(items) => { PositiveList.ItemsSource = items; },
(exception) => { MessageBox.Show(exception.Message); },
null
);
to upload POST Data into the API
public void UploadData(Twit tweetPerSend)
{
if (NetworkInterface.GetIsNetworkAvailable())
{
chatterbox.Headers[HttpRequestHeader.ContentType] = "application/x-www- form-urlencoded";
chatterbox.Headers["X-Mashape-Authorization"] = "MXBxYmptdjhlbzVnanJnYndicXNpN2NwdWlvMWE1OjA0YTljMWJjMDg4MzVkYWY2YmIzMzczZWFkNDlmYWRkNDYzNGU5NmI=";
var Uri = new Uri("https://chatterboxco-sentiment-analysis-for-social-media---nokia.p.mashape.com/sentiment/current/classify_text/");
StringBuilder postData = new StringBuilder();
postData.AppendFormat("{0}={1}", "lang", HttpUtility.UrlEncode("en"));
postData.AppendFormat("&{0}={1}", "text", HttpUtility.UrlEncode(tweetPerSend.DecodedText));
postData.AppendFormat("&{0}={1}", "exclude", HttpUtility.UrlEncode("is")); // disesuaikan
postData.AppendFormat("&{0}={1}", "detectlang", HttpUtility.UrlEncode("0"));
chatterbox.UploadStringAsync(Uri, "POST", postData.ToString());
chatterbox.UploadStringCompleted += new UploadStringCompletedEventHandler(chatterbox_UploadStringCompleted);
}
}
void chatterbox_UploadStringCompleted(object sender, UploadStringCompletedEventArgs e)
{
var chatterbox = sender as WebClient;
chatterbox.UploadStringCompleted -= chatterbox_UploadStringCompleted;
string response = string.Empty;
if (!e.Cancelled)
{
response = HttpUtility.UrlDecode(e.Result);
nilaiKlasifikasi = ParsingHasil(response);
MessageBox.Show(nilaiKlasifikasi.ToString()); //just testing
//textBlock1.Text = response;
}
}
private double ParsingHasil(String response)
{
var result = Regex.Match(#response, #"(?<=""value"": )(-?\d+(\.\d+)?)(?=,|$)");
Debug.WriteLine(result);
double hasil = Convert.ToDouble(result.ToString());
//return Convert.ToInt32(result);
return hasil;
}
However, there isn't only 1 tweet to retrieve, there'll be many tweets, so the main problem is, after I retrieve all the tweet and request the result to the API, I get this error "WebClient does not support concurrent I/O operations"
Does anyone know how to solve this problem?
any help would be appreciated
You'll have to execute UploadStringAsync synchronously one at a time. (i.e. chain execution of the next UploadStringAsync in the UploadStringCompleted handler.
Or, create a new WebClient for each UploadStringAsync.

UI not updating in async web request callback

I'm using this to make a web request and download some data:
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
var client = new WebClient();
client.DownloadStringCompleted += (s, e) => {
textBlock1.Text = e.Result;
};
client.DownloadStringAsync(new Uri("http://example.com"));
}
}
The text of textBlock1 never changes even though e.Result has the correct data. How do I update that from the callback?
Edit: If I add MessageBox.Show(e.Result); in the callback along with the textBlock1.Text assignment, both the messsage box and the text box show the correct data.
Edit Again: If I add a TextBox and set it's text right after the line textBlock1.Text line, they both show the correct text.
I think, it's a bug.
I also ran into some problems with updating the UI from different dispatchers. What I finally did was use the TextBlock's (or other UI Element) own dispatcher and that worked for me. I think the phone framework may be using different dispatchers between the app and UI Elements. Notice the change from dispatcher.BeginInvoke to textbox1.Dispatcher...
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var dispatcher = Deployment.Current.Dispatcher;
var client = new WebClient();
client.DownloadStringCompleted += (s, e) =>
{
var result = e.Result;
textBlock1.Dispatcher.BeginInvoke(
()=> textBlock1.Text = result
);
};
client.DownloadStringAsync(new Uri("http://example.com"));
}
From browsing through the WP7 forums, a bunch of people were reporting that this was related to a video card driver issue. I've updated my ATI Radeon HD 3400 drivers to the latest version and it appears to work now.
client.DownloadStringAsync is expecting a Uri like this:
client.DownloadStringAsync(new Uri("http://example.com"));
also, shouldn't you update your TextBlock through a Dispatcher.BeginInvoke like this:
client.DownloadStringCompleted += (s, e) =>
{
if (null == e.Error)
Dispatcher.BeginInvoke(() => UpdateStatus(e.Result));
else
Dispatcher.BeginInvoke(() => UpdateStatus("Operation failed: " + e.Error.Message));
};
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
Loaded += MainPage_Loaded;
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var dispatcher = Deployment.Current.Dispatcher;
var client = new WebClient();
client.DownloadStringCompleted += (s, e) =>
{
var result = e.Result;
dispatcher.BeginInvoke(
()=> textBlock1.Text = result
);
};
client.DownloadStringAsync(new Uri("http://example.com"));
}
}
I want to comment but can't yet. Yes, I have a very similar issue. In my case it's my viewmodel that is updating a DownloadStatus property, then when the download is completed I do some more work and continue updating this property.
The view stops updating once the ViewModel code hits the OpenReadCompleted method. I've stepped carefully through the code. PropertyChanged fires, and the view even comes back and retrieves the new property value, but never shows the change.
I was sure it was a bug, but then I created a brand new project to reproduce the issue, and it works fine!
Here's a snippet of my non-reproducing code. The UI textblock bound to "DownloadStatus" happily updates properly all the way through. But the same paradigm doesn't work in my main project. Infuriating!
public void BeginDownload(bool doWorkAfterDownload)
{
DownloadStatus = "Starting ...";
_doExtraWork = doWorkAfterDownload;
var webClient = new WebClient();
string auth = "Basic " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("test:password"));
webClient.Headers["Authorization"] = auth;
webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(webClient_DownloadProgressChanged);
webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(webClient_OpenReadCompleted);
webClient.OpenReadAsync(new Uri("http://www.ben.geek.nz/samsung1.jpg"));
}
void webClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
if (e.Error != null)
{
DownloadStatus = e.Error.Message;
return;
}
DownloadStatus = "Completed. Idle.";
if(_doExtraWork)
{
Thread t = new Thread(DoWork);
t.Start(e.Result);
}
}
void DoWork(object param)
{
InvokeDownloadCompleted(new EventArgs());
// just do some updating
for (int i = 1; i <= 10; i++)
{
DownloadStatus = string.Format("Doing work {0}/10", i);
Thread.Sleep(500);
}
DownloadStatus = "Completed extra work. Idle.";
InvokeExtraWorkCompleted(new EventArgs());
}

Resources