How to display exceptions from HTTP request on Android screen - android

I am using a basic Flutter- based get request that is going to a RESTful server. The code for performing this request is below:
Future<List<Description>> getTheInfo() async {
List<Description> result;
try {
http.Response resp = await http.get(theUrl + "/home/devices");
if(resp.statusCode == 200) {
var jsonstr = json.decode(resp.body);
var list = jsonstr["devices"] as List;
result = list.map<Description>((json) => Description.fromJson(json)).toList();
}
else {
print('FAILURE: '+resp.statusCode.toString());
}
} catch(exe) {
print('Socket Failure: '+exe.toString());
throw CustomException("FAILURE: ",exe.toString());
}
return(result);
}
The custom exception is something I grabbed from the Internet below:
class CustomException implements Exception {
final _message;
final _prefix;
CustomException([this._message, this._prefix]);
String toString() {
return "$_prefix$_message";
}
}
My problem is that while I have the ability to print to the console any failures that occur in my development environment, I have no way of seeing what happens on a device. I am seeing failures when testing on my phone that I am not seeing in my development environment.
What I would like to do is have some way of displaying exceptions that are thrown from my GET request on the screen of my emulator (in my development environment) and the screen of my actual phone (when I create an APK to test on my device). Is there some way to do this?

Not sure by what you are trying to accomplish here, but there is an elegant way to display failures and exceptions on to the UI to let the user know about it. This would be a good practice as well in a production app.
What you need is dartz, dartz is a functional programming package.
With dartz you can use the Either type to return either a Future<List<Description>> or a CustomException
here is how it'll fit into your code.
Future<Either<CustomException,List<Description>>> getTheInfo() async {
List<Description> result;
try {
http.Response resp = await http.get(theUrl + "/home/devices");
if(resp.statusCode == 200) {
var jsonstr = json.decode(resp.body);
var list = jsonstr["devices"] as List;
result = list.map<Description>((json) => Description.fromJson(json)).toList();
return right(result);
}
else {
print('FAILURE: '+resp.statusCode.toString());
return left(CustomException("FAILURE: ",exe.toString()));
}
} catch(exe) {
print('Socket Failure: '+exe.toString());
return left(CustomException("FAILURE: ",exe.toString()));
}
}
On the UI side,
...
Widget build(BuildContext context) {
FutureBuilder(
future: getTheInfo(),
builder: (_, AysncSnapshot snapshot) {
if(snapshot.connectionState ==ConnectionStatet.waiting){
return CircularProgressIndicator();
}
snapshot.data.fold(
(l)=> Center(child: Text(l.message), // CustomException message
),
(r)=> Center(child: Text("Success"), // r contains the List<Description>
);
}
}
...
Depending on Success or Failure, you can render an appropriate widget.
edit:
This should work but highly recommend switching to provider or riverpord if not BLoc

Related

Accessing Web API from Hybrid Mobile App, Blazor Mobile

I'm working on a Blazor Hybrid App and currently trying to access a .NET Web API from my phone.
I have deployed a .NET Web Application to IIS. API returns just a WeatherForecast data (for those who're not familiar, data type is already defined in project and comes with the template) in JSON.
API Response is something like this:
[
{"date":"2020-09-18T15:55:27.4724752+03:00","temperatureC":-6,"temperatureF":22,"summary":"Hot"},
{"date":"2020-09-19T15:55:27.4725087+03:00","temperatureC":27,"temperatureF":80,"summary":"Bracing"},
{"date":"2020-09-20T15:55:27.4725155+03:00","temperatureC":54,"temperatureF":129,"summary":"Bracing"},
{"date":"2020-09-21T15:55:27.4725221+03:00","temperatureC":1,"temperatureF":33,"summary":"Scorching"},
{"date":"2020-09-22T15:55:27.4725302+03:00","temperatureC":-3,"temperatureF":27,"summary":"Chilly"}
]
I deployed it to my localhost at port 3004. So both in my PC's browser and my mobile phone's browser I can successfully reach to this address and get this response.
However in my mobile application there is a method responsible for retrieving the data, defined as:
AppState.cs:
public async Task<List<WeatherForecast>> GetForecastAsync()
{
return await _http.GetFromJsonAsync<List<WeatherForecast>>("http://192.168.1.22:3004/weatherforecast");
}
and this is called from Index.razor:
#inject AppState appState
#if(todos == null){
<p> Loading </p>
}
else {
// loop todos in foreach
}
#code {
List<Todo> todos;
protected override async Task OnInitializedAsync()
{
todos = await appState.GetForecastAsync();
appState.OnChange += UpdateState;
}
This GET Request returns null. I've tried it with JSON placeholder from https://jsonplaceholder.typicode.com/todos/ (Changed the WeatherForecast to Todo of course) there was no problem!
My Attemps
For possible solutions I've tried to
change my local IP to 10.0.2.2:3004 since I'm on the android phone but no use.
I've tried with both http:// and https:// but still no use.
Configure CORS to allow any origins in the API :
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options => options.AddDefaultPolicy(builder => builder.AllowAnyOrigin()));
services.AddControllers();
}
//...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//...
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors();
app.UseAuthorization();
//...
}
How can I reach the API from the mobile app?
After few hours of researching and trial and error I found it.
So I'll explain the flow step by step:
First when the page we want opens, we want to fetch the data from a remote database. In my case I call it from Index.razor, my home page:
Index.razor
#page "/"
#inject AppState appState
#implements IDisposable
#if (forecasts== null)
{
<p>Null</p>
}
else
{
#foreach (var forecast in forecasts)
{
<p>#forecast .Date</p>
<p>#forecast .TemperatureC</p>
<p>#forecast .TemperatureF</p>
<p>#forecast .Summary</p>
}
}
#code {
List<WeatherForecast> forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await appState.GetForecastAsync(); // We call the Get Forecast Async via appState
appState.OnChange += UpdateState;
}
public void Dispose() => appState.OnChange -= UpdateState;
private async Task UpdateState()
{
StateHasChanged();
}
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string Summary { get; set; }
}
}
We need the AppState because we need to keep track of state, so it's like a state manager.
After that we jump to AppState.cs:
class AppState
{
HttpClient _http;
public AppState()
{
_http = new HttpClient() { BaseAddress = new Uri("http://192.168.1.22:3000/") };
public async Task<List<WeatherForecast>> GetForecastAsync()
{
try
{
return await _http.GetFromJsonAsync<List<WeatherForecast>>("weatherforecast");
}
catch (Exception ex)
{
Debug.WriteLine(#"\tERROR {0}", ex.Message);
}
return new List<WeatherForecast>();
}
//There is also OnChange method here but it's irrelevant.
}
I've downloaded ISSexpress-proxy and proxied the api to another port because I was having "Invalid hostname" error.
When initializing HttpClient I gave the baseAddress as my local IP, not 10.0.2.2 where you use to access to localhost on Android.
I was still getting errors on HTTP communication. In AndroidManifest.xml file I added android:targetSandboxVersion="1" to <manifest> and android:usesCleartextTraffic="true" to <application>. Thanks to this answer I found how to solve it.
Final Result

The Cloud function that returns a batch commit does not wait for that commit to make changes to the database before it completes

In Cloud Functions, I have defined a function that makes somes updates using a batch that I commit. This commit is the return of the function. This function simply computes the number of likes of every post (likes and posts are two distinct collections in my Firestore database). Since the whole code is short and very simple to understand, I show it below.
The fact to like or unlike a post (to add or remove a like document from the likes collection) is done client-side by the app.
The fact to compute some statistics (number of likes per post for example) is down server-side in the following Cloud Function. (because if it was client side, it would be hackable, i. e. Bad statistics would be generated and saved + dealing with statistics doesn't concern the Android app directly and so it should definitely be computed server-side).
The important thing to note is: return batch.commit is the return of this Cloud Function.
exports.sortPostsByUsersPostsLikes = functions.https.onCall((data, context) => {
if(!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
}
const batch = admin.firestore().batch();
const posts = admin_firestore.collection('list_of_users_posts');
const likes = admin_firestore.collection('likes_of_users_posts');
const map_posts_id_with_number_of_likes = [];
likes.get().then(function(likes_docs) {
likes_docs.forEach(like_doc => {
if(!(like_doc.data().post in map_posts_id_with_number_of_likes)) {
map_posts_id_with_number_of_likes[like_doc.data().post] = 0;
}
map_posts_id_with_number_of_likes[like_doc.data().post] += 1;
});
return posts.get();
}).then(function(posts_docs) {
posts_docs.forEach(post_doc => {
if(post_doc.id in map_posts_id_with_number_of_likes) {
batch.update(post_doc.ref, "number_of_likes", map_posts_id_with_number_of_likes[post_doc.id]);
} else {
batch.update(post_doc.ref, "number_of_likes", 0);
}
});
return batch.commit();
}).catch(function(error) {
console.log("UNABLE TO SORT THE POSTS");
console.log(error);
throw new functions.https.HttpsError('unknown', 'An error occurred when trying to sort the posts.');
});
});
In my Android app, when the user, in the list of the posts, likes a post:
First, I add a like in the likes collection
When the like is successfully added in the likes collection in database, I refresh the list of the posts
When the list of the posts is shown (or refreshed), I call the above Cloud Function in order to re-compute the number of likes of the posts (soon, "of the shown posts").
When the number of likes of the posts is successfully recomputed, I show the posts (so the number of likes of each shown post would be correct).
Question
The problem is: at step 4., the number of likes of each shown post is NOT correct (sometimes it is, sometimes it is not). As if the Cloud Function didn't wait the batch commit ends. Is it a normal behavior? Is there any way to force the Cloud Function to wait for the batch's commit's success?
The code I use, in the Android app, to call the above Cloud Function and then, normally if it succeeds, to show the posts (so normally with the good number of likes, but it's not the case in practice) is:
FirebaseFunctions.getInstance()
.getHttpsCallable("sortPostsByUsersPostsLikes")
.call()
.continueWith(new Continuation<HttpsCallableResult, Void>() {
#Override
public Void then(#NonNull final Task<HttpsCallableResult> task) {
if(requireActivity().isDestroyed() || requireActivity().isFinishing()) {
return null;
}
if(!task.isSuccessful()) {
Exception e = task.getException();
if (e instanceof FirebaseFunctionsException) {
FirebaseFunctionsException ffe = (FirebaseFunctionsException) e;
if(ffe.getCode() == FirebaseFunctionsException.Code.UNKNOWN) {
miscellaneous.showErrorPopIn(requireActivity(), R.string.error_sortPostsByUsersPostsLikes);
}
}
return null;
}
postsDatabaseModel.getListOfPostsOfUser(the_posts_owner).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
Returning the batch commit and adding a then doesn't work
I have tried the following but it doesn't work:
.then(function(posts_docs) {
posts_docs.forEach(post_doc => {
if(post_doc.id in map_posts_id_with_number_of_likes) {
batch.update(post_doc.ref, "number_of_likes", map_posts_id_with_number_of_likes[post_doc.id]);
} else {
batch.update(post_doc.ref, "number_of_likes", 0);
}
});
return batch.commit();
}).then(function() {
return true;
}).catch(function(error) {
You are correctly chaining the promises returned by the asynchronous methods but your don't return this entire chain. You should do as follows:
exports.sortPostsByUsersPostsLikes = functions.https.onCall((data, context) => {
if(!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
}
const batch = admin.firestore().batch();
const posts = admin_firestore.collection('list_of_users_posts');
const likes = admin_firestore.collection('likes_of_users_posts');
const map_posts_id_with_number_of_likes = [];
// SEE THE ADDITION OF RETURN BELOW
return likes.get().then(function(likes_docs) {
likes_docs.forEach(like_doc => {
if(!(like_doc.data().post in map_posts_id_with_number_of_likes)) {
map_posts_id_with_number_of_likes[like_doc.data().post] = 0;
}
map_posts_id_with_number_of_likes[like_doc.data().post] += 1;
});
return posts.get();
}).then(function(posts_docs) {
posts_docs.forEach(post_doc => {
if(post_doc.id in map_posts_id_with_number_of_likes) {
batch.update(post_doc.ref, "number_of_likes", map_posts_id_with_number_of_likes[post_doc.id]);
} else {
batch.update(post_doc.ref, "number_of_likes", 0);
}
});
return batch.commit();
}).catch(function(error) {
console.log("UNABLE TO SORT THE POSTS");
console.log(error);
throw new functions.https.HttpsError('unknown', 'An error occurred when trying to sort the posts.');
});
});
I would suggest you watch the 3 videos about "JavaScript Promises" from the Firebase video series (https://firebase.google.com/docs/functions/video-series/) which emphasize how important it is to return a Promise. Without that, the Cloud Function may terminate at anytime before all the asynchronous operatins are completed.
UPDATE FOLLOWING YOUR COMMENTS
If you want to log the fact that the Cloud Function was successful, you could do as follows:
exports.sortPostsByUsersPostsLikes = functions.https.onCall((data, context) => {
if(!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called while authenticated.');
}
//...
return likes.get().then(function(likes_docs) {
//...
return posts.get();
}).then(function(posts_docs) {
//...
return batch.commit();
}).then(function() {
console.log("SUCCESS")
return null;
})
.catch(function(error) {
//...
});
});

"Unrecognized app. Please make sure you trust this app before proceeding" in flutter stripe payment

i'm beginner for flutter. i need connect stripe payment and flutter. so i use stripe_payment: ^1.0.6(https://pub.flutter-io.cn/packages/stripe_payment/versions/1.0.6). using this example i tried to build payment gate way. but their has error "Unrecognized app. Please make sure you trust this app before proceeding".
After that press the continuous button it provide,
error.
In my widget button has following codes,
void _payWithCard() {
StripePayment.createSourceWithParams(SourceParams(
type: 'ideal',
amount: 333,
currency: 'eur',
returnURL: 'projectXXXXX://payment_redirect',
)).then((source) {
setState(() {
_source = source;
});
}).catchError(setError);
}
their has some codes related with my Stripe payment gateway.
#override
void initState() {
super.initState();
StripePayment.setOptions(StripeOptions(
publishableKey: "piok_test_pIoKxxxxxxxxxxxxxxTzgP009ywg8JNs",
merchantId: "projectXXXXXsolutions",
androidPayMode: 'test'));
initPlatformState();
}
initPlatformState() async {
if (_type == UniLinksType.string) {
await initPlatformStateForStringUniLinks();
} else {
await initPlatformStateForUriUniLinks();
}
print('init platform state');
}
// Attach a second listener to the stream
getLinksStream().listen((String link) {
print(' got link success: $link');
lartChechOutLoading(context);
}, onError: (err) {
print('got err: $err');
});
// Get the latest link
String initialLink;
Uri initialUri;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
initialLink = await getInitialLink();
print('initial link: $initialLink');
if (initialLink != null) initialUri = Uri.parse(initialLink);
} on PlatformException {
initialLink = 'Failed to get initial link.';
initialUri = null;
} on FormatException {
initialLink = 'Failed to parse the initial link as Uri.';
initialUri = null;
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_latestLink = initialLink;
_latestUri = initialUri;
});
}
// Get the latest Uri
Uri initialUri;
String initialLink;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
initialUri = await getInitialUri();
print('initial uri: ${initialUri?.path}'
' ${initialUri?.queryParametersAll}');
initialLink = initialUri?.toString();
} on PlatformException {
initialUri = null;
initialLink = 'Failed to get initial uri.';
} on FormatException {
initialUri = null;
initialLink = 'Bad parse the initial link as Uri.';
}
-
This little note is shown while operating in TEST mode, and inform your users that your application hasn't gone through the process of being approved in Google Pay's Business Console (see the troubleshooting guide to learn more).
Also, I'd recommend taking a look at the new official Flutter plugin built by the community in collaboration with Stripe. If you are using other payment providers, you can also easily add Apple and Google Pay to your Flutter applications with the pay plugin.

Xamarin & Android - crash on exiting from method

I don't know what can I tell more.
I have this method:
public async Task<HttpResponseMessage> SendAsyncRequest(string uri, string content, HttpMethod method, bool tryReauthorizeOn401 = true)
{
HttpRequestMessage rm = new HttpRequestMessage(method, uri);
if (!string.IsNullOrWhiteSpace(content))
rm.Content = new StringContent(content, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.SendAsync(rm);
if (response.StatusCode == HttpStatusCode.Unauthorized && tryReauthorizeOn401)
{
var res = await AuthenticateUser();
if(res.user == null)
return response;
return await SendAsyncRequest(uri, content, method, false);
}
return response;
}
Nothing special.
client.SendAsync(rm) is executed, response.StatusCode is Ok.
Application just crashes when exiting from this method.
Output shows me just this assert:
12-16 20:09:22.025 F/ ( 1683): * Assertion at /Users/builder/jenkins/workspace/xamarin-android-d15-9/xamarin-android/external/mono/mono/mini/debugger-agent.c:4957, condition `is_ok (error)' not met, function:set_set_notification_for_wait_completion_flag, Could not execute the method because the containing type is not fully instantiated. assembly:<unknown assembly> type:<unknown type> member:(null)
12-16 20:09:22.025 F/libc ( 1683): Fatal signal 6 (SIGABRT), code -6 in tid 1683 (omerang.Android)
And nothing more.
client is HttpClient.
I have setting in my Android project: HttpClient Implementation set to Android.
Does anyone have any idea what could be wrong?
edit
SendAsyncRequest is used like that:
public async Task<(HttpResponseMessage response, IEnumerable<T> items)> GetListFromRequest<T>(string uri)
{
HttpResponseMessage response = await SendAsyncRequest(uri, null, HttpMethod.Get);
if (!response.IsSuccessStatusCode)
return (response, null);
string content = await response.Content.ReadAsStringAsync();
var items = JsonConvert.DeserializeObject<IEnumerable<T>>(content);
return (response, new List<T>(items));
}
Based on the provided example project code you provided
protected override async void OnStart()
{
Controller c = new Controller();
TodoItem item = await c.GetTodoItem(1);
TodoItem item2 = await c.GetTodoItem(2);
}
You are calling async void fire and forget on startup.
You wont be able to catch any thrown exceptions, which would explain why the App crashes with no warning.
Reference Async/Await - Best Practices in Asynchronous Programming
async void should only be used with event handlers, so I would suggest adding an event and handler.
based on the provided example, it could look like as follows
public partial class App : Application {
public App() {
InitializeComponent();
MainPage = new MainPage();
}
private even EventHander starting = delegate { };
private async void onStarting(object sender, EventArgs args) {
starting -= onStarting;
try {
var controller = new Controller();
TodoItem item = await controller.GetTodoItem(1);
TodoItem item2 = await controller.GetTodoItem(2);
} catch(Exception ex) {
//...handler error here
}
}
protected override void OnStart() {
starting += onStarting;
starting(this, EventArgs.Empty);
}
//...omitted for brevity
}
With that, you should now at least be able to catch the thrown exception to determine what is failing.
try to update your compileSdkVersion to a higher version and check.
also try following
> Go to: File > Invalidate Caches/Restart and select Invalidate and Restart

System.Net.Http.HttpClient with AutomaticDecompression and GetAsync (timeout) vs GetStringAsync (working

I have the following code to make requests to a REST API, using Xamarin and an Android device:
public class ApiBase
{
HttpClient m_HttpClient;
public ApiBase(string baseAddress, string username, string password)
{
if (!baseAddress.EndsWith("/"))
{
baseAddress += "/";
}
var handler = new HttpClientHandler();
if (handler.SupportsAutomaticDecompression)
{
handler.AutomaticDecompression = DecompressionMethods.GZip;
}
m_HttpClient = new HttpClient(handler);
m_HttpClient.BaseAddress = new Uri(baseAddress);
var credentialsString = Convert.ToBase64String(Encoding.UTF8.GetBytes(username + ":" + password));
m_HttpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", credentialsString);
m_HttpClient.Timeout = new TimeSpan(0, 0, 30);
}
protected async Task<XElement> HttpGetAsync(string method)
{
try
{
HttpResponseMessage response = await m_HttpClient.GetAsync(method);
if (response.IsSuccessStatusCode)
{
// the request was successful, parse the returned string as xml and return the XElement
var xml = await response.Content.ReadAsAsync<XElement>();
return xml;
}
// the request was not successful -> return null
else
{
return null;
}
}
// some exception occured -> return null
catch (Exception)
{
return null;
}
}
}
If i have it like this, the first and the second call to HttpGetAsync work perfectly, but from the 3rd on the GetAsyncstalls and eventually throws an exception due to the timeout. I send these calls consecutively, there are not 2 of them running simultaneously since the results of the previous call are needed to decide the next call.
I tried using the app Packet Capture to look at the requests and responses to find out if i'm sending an incorrect request. But it looks like the request which fails in the end is never even sent.
Through experimentation i found out that everything works fine if don't set the AutomaticDecompression.
It also works fine if i change the HttpGetAsync method to this:
protected async Task<XElement> HttpGetAsync(string method)
{
try
{
// send the request
var response = await m_HttpClient.GetStringAsync(method);
if (string.IsNullOrEmpty(response))
{
return null;
}
var xml = XElement.Parse(response);
return xml;
}
// some exception occured -> return null
catch (Exception)
{
return null;
}
}
So basically using i'm m_HttpClient.GetStringAsync instead of m_HttpClient.GetAsync and then change the fluff around it to work with the different return type. If i do it like this, everything works without any problems.
Does anyone have an idea why GetAsync doesn't work properly (doesn't seem to send the 3rd request) with AutomaticDecompression, where as GetStringAsync works flawlessly?
There are bug reports about this exact issue:
https://bugzilla.xamarin.com/show_bug.cgi?id=21477
The bug is marked as RESOLVED FIXED and the recomended action is to update to the latest stable build. But there are other (newer) bugreports that indicate the same thing that are still open, ex:
https://bugzilla.xamarin.com/show_bug.cgi?id=34747
I made a workaround by implementing my own HttpHandler like so:
public class DecompressionHttpClientHandler : HttpClientHandler
{
protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.AcceptEncoding.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("gzip"));
var msg = await base.SendAsync(request, cancellationToken);
if (msg.Content.Headers.ContentEncoding.Contains("gzip"))
{
var compressedStream = await msg.Content.ReadAsStreamAsync();
var uncompresedStream = new System.IO.Compression.GZipStream(compressedStream, System.IO.Compression.CompressionMode.Decompress);
msg.Content = new StreamContent(uncompresedStream);
}
return msg;
}
}
Note that the code above is just an example and not a final solution. For example the request will not be compressed and all headers will be striped from the result. But you get the idea.

Categories

Resources