How to correctly provide networking feedback to the user? [closed] - iphone

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
My app retrieves currency exchange rates from a web service using a synchronous NSURLConnection in a background GCD queue, like this:
// This method is called in background queue
- (NSData*)fetchDataWithURLStr:(NSString*)urlStr {
NSData *jsonData = nil;
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
NSURL *url = [NSURL URLWithString:urlStr];
NSURLResponse *response = nil;
NSError *error = nil;
NSURLRequest *request = [NSURLRequest requestWithURL:url];
jsonData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (error != nil) {
NSString *errorMsg = nil;
NSInteger ec = [error code];
if (ec == NSURLErrorTimedOut || ec == NSURLErrorCannotConnectToHost) {
errorMsg = #"Data temporarily not available.";
}
// Call on main thread
dispatch_async(dispatch_get_main_queue(), ^{
// Present the error
[self showErrorWithCode:ec title:#"ERROR" message:errorMsg];
});
jsonData = nil;
}
return jsonData;
}
But the problem is often the app tries to fetch data, and the download seems to be running forever and nothing happens. No status updates just nothing. Often my WiFi is just stalled and I must go to Settings, disable and re-enable this. Or internet connectivity of my WiFi router at home is down but the device is connected to WiFi.
What I really want to do is give precise feedback about what exactly is happening on the network right now. For example
"Trying to contact server..."
"Wait... still trying..."
"Your internet seems broken..."
"Trying again..."
"Response received..."
"Downloaded 20%"
"Downloaded 40%"
"Finished!"
Just exact feedback about what is going on.
Someone recommended MKNetworkKit but it just feels as dead, no feedback whatsoever.
Are there solutions to this problem which work for iOS?
EDIT: I have Reachability in place but it does not give me this kind of feedback I want to display during networking. Also, Reachability does not tell me what is going on when there is a WiFi connection but the internet is stalled.

The fundamental problem here is that it is impossible (yes impossible) to give a reliable diagnosis of a network problem based on the information that is available to your app. There are simply too many possible causes, and some of them are simply not distinguishable without knowledge of the actual networks and / or access to other sources of diagnostic information.

U can user Reachability classes.
Here is a sample code which uses this reachability classes, and notifies us which type connection we are using. The sample code is from apple.
Have a look at & implement in the same way.
In order for showing the progress to the user, i suggest to use NSURLConnection from the its delegate methods you can easily get the status of the connection/request.
In one of its delegate it gives error description.

You should use the asynchronous API instead. Using the synchronous API in a separate worker thread/queue is often not the right way to go (see the WWDC'12 videos about those subjects)
A better solution would be to use the newer NSURLConnection API and the +sendAsynchronousRequest:queue:completionHandler: method instead of using sendSynchronousRequest: returningResponse: error:. This way you would avoid blocking your API and be informed when the request fails (either fails starting or fails while running because the network went down etc).
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse*, NSData*, NSError*) {
// Code that will be executed asynchronously
// when the response to the request has been received
}];
// After the call to this method above, the code will continue executing
// WITHOUT WAITING for the network request to have its response.
This way your UI won't "freeze" and the rest of your code will continue to run, so you can, like, show some progress indicator on your views for example, and so on. The code in the completionHandler block will be called asynchronously (independently of the rest of your code) only once the response has arrived.
Moreover, to be informed when the network itself is unreachable (went down, etc), use Reachability for that [EDIT] You seem to do this already as you added in the EDIT of your question, so you should already be informed about that and being able to inform the user in this case)
Tip: you may also use some third party frameworks, like the excellent [AFNetworking(https://github.com/AFNetworking/AFNetworking), that allows you to do much more when sending network requests, like having an Objective-C block of code to be called while the network request is in progress, allowing you to know the progression of the download easily.
Once you have integrated the AFNetworking project in your workspace, you would be able to do stuff like this:
AFHTTPRequestOperation* reqOp = [[[AFHTTPRequestOperation alloc] initWithRequest:request] autorelease];
[reqOp setCompletionBlockWithSuccess: ^(AFHTTPRequestOperation *operation, id responseObject)
{
// Code to execute asynchronously when you successfully received the whole page/file requested
// The received data is accessible in the responseObject variable.
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Code to execute asynchronously when you request failed, for example if you have a network error, or received some 404 error code, etc.
progressLabel.text = [NSString stringWithFormat:#"Download error: %#", error];
}];
[reqOp setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead)
{
// Code to execute periodically each time a partial chunk of data is received
// So that you can update your progression. For example:
progressLabel.text = [NSString stringWithFormat:#"Downloading %.1f%%", (float)totalBytesRead*100.f/totalBytesExpectedToRead];
}];
[reqOp start]; // start the request
// The rest of the code will continue to execute, and the blocks mentioned above will be called asynchronously when necessary.

Here is how i check for internet connection . You need to add the Reachability first .
+ (BOOL) checkNetworkStatus
{
Reachability *reachability = [Reachability reachabilityForInternetConnection];
NetworkStatus networkStatus = [reachability currentReachabilityStatus];
return !(networkStatus == NotReachable);
}

Related

Right way to handle user session with IOS NSURLSession

Hi I am beginner to IOS development. I am developing small IOS application. Required functionality of that application is like this. Application have login for user. once your logged in it will not ask for login again until user do logout. I completed login part. I done with login using NSURLSession.Now after login I want to fetch some thing from server but it is giving error with status code 0 or 1012. I don't know why it is giving this error. So my questions are like this
How NSURLConnection works?
Is it taking care of session for user or I have to take care of that?
After login process I am not able to do anything, mostly because of unauthorised access. I did login in following way
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:url completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error)
{
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
NSLog(#"sttaus code %i", httpResp.statusCode);
if(error)
{
[self.delegate signinWithError:error];
}
else
{
[self.delegate signinWithJson:data];
}
}] resume];
I have to set NSURLSession globally or I can use it locally as well?
Is there any one have good explanation about it? Any good example ? Need Help. Thank you.

Facebook iOS SDK 3.0, implement like action on a url?

I'm trying to implement Like via the facebook open-graph-api with the Facebook iOS SDK 3.0.
Everything seems to work except the FbGraphObject and that's because I have no idea how it should look because this clearly does not work.
What I'm trying to do is to like a url posted as an object. A simple Like with via the open-graph.
The error message I get the the code below is:
The action you're trying to publish is invalid because it does not specify any
reference objects. At least one of the following properties must be specified: object.
The code I use is this:
FBGraphObject *objectToLike = [[FBGraphObject alloc]initWithContentsOfURL:[NSURL URLWithString:facebookLike.titleLabel.text]];
FBRequest *requestLike = [[FBRequest alloc]initForPostWithSession:[FBSession activeSession] graphPath:#"me/og.likes" graphObject:objectToLike];
FBRequestConnection *connection = [[FBRequestConnection alloc] init];
[connection addRequest:requestLike
completionHandler:
^(FBRequestConnection *connection, id result, NSError *error) {
if (!error &&
result) {
DLog(#"NothingWentWrong");
}
DLog(#"MajorError: %#", error);
}
];
[connection start];
UPDATE:
Checked some more info and my guess it to use this method:
https://developers.facebook.com/docs/sdk-reference/iossdk/3.0/class/FBGraphObject/#//api/name/graphObject
To somehow create an object. It's the graphObject method that I probably need to do something with. Any help at all would be appreciated.
I've actually manage to create a simple and quite dirty solution of this.
The solution does not seem optimal but it's currently a working solution.
If anybody has used the explorer tool on facebook on this url:
https://developers.facebook.com/tools/explorer/
You know how the URL will look like when facebook is sharing a like. It has to have the URL and an access-token.
So my solution became just to disregard sending anything from the Facebook SDK and just send a post request to the same URL that I've used in the explorer tool.
There seems to be some referencing to it on the facebooks docs if you look closely and deep, but no one explains exactly how to actually make the connection, so this is my solution:
NSString *urlToLikeFor = facebookLike.titleLabel.text;
NSString *theWholeUrl = [NSString stringWithFormat:#"https://graph.facebook.com/me/og.likes?object=%#&access_token=%#", urlToLikeFor, FBSession.activeSession.accessToken];
NSLog(#"TheWholeUrl: %#", theWholeUrl);
NSURL *facebookUrl = [NSURL URLWithString:theWholeUrl];
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:facebookUrl];
[req setHTTPMethod:#"POST"];
NSURLResponse *response;
NSError *err;
NSData *responseData = [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&err];
NSString *content = [NSString stringWithUTF8String:[responseData bytes]];
NSLog(#"responseData: %#", content);
If you look at the code I just take the url and puts two dynamic strings in the url, one with the object-url and one with the access token. I create a URLRequest and make it a POST request, and the response from facebook gets logged so one actually can see if the like go through or not.
There might be some performance improvements that can be done with the actual requests but I will leave it up to you if you see any slowdowns.
I'm still interested in other solutions but this is the one I will use for now.
We don't currently support Like through our Graph API.
What you can look through is something like this :
https://developers.facebook.com/docs/opengraph/actions/builtin/likes/
I’m not sure what initWithContentsOfURL does, but from the name I guess it tries to actually load content from a given URL(?).
You only have to give the URL as a text parameter – a URL is what represents an Open Graph object. Facebook will do the rest, scraping the page behind that URL and reading it’s OG meta tags, etc.
Maybe just this?
FBRequest *requestLike = [[FBRequest alloc]initForPostWithSession:[FBSession activeSession]
graphPath:#"me/og.likes"
graphObject:[NSURL URLWithString:facebookLike.titleLabel.text]];

Network push notification

I am wondering if there is a simple way to push a notification, to all your users currently using your application.
So the next time they launch the application and they are connected to Wi-Fi, they receive a alert telling them that, i.e., "An update is available".
EDIT:
To explain in greater detail what I am looking for. I am developing an application that should only be used when it is the most recent version of the software, so I would like to be able to send out a message (notification) for when an update is available in the AppStore. This is seen in some games such as AngryBirds and Cut the Rope.
Maybe even change a BOOL in the code to TRUE, leaving a red flag if{} BOOL is TRUE. (Of course first I'd like an answer to the more basic version. This would be helpful though as well)
Hope this clears things up.
With regards,
SirKaydian
Local Notification Solution
Essentially what Oscar said previously, you could make this really simple. If you wanted to check after every launch of the application you could easily call some sort of local API (or similar) call from your application to a web service you might host. So for example you have a PHP file:
<?php
$currentVersion = 1.3;
echo $currentVersion;
?>
That simple PHP script can be updated by you whenever you release an update. The iPhone can recognize this by querying that PHP file on every startup (check NSURLConnection delegate methods to get the response from any particular URL). Now from previous checks by your application it can store those in an NSUserDefaults value by the following:
NSUserDefaults *defaults = [[NSUserDefaults alloc] init];
NSString *loadedVersion = [defaults valueForKey:#"MYAPP_CURR_VERSION"];
Now we need to compare the two from your NSURLConnection delegate method that receives the string back from the PHP file on your web service.
NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://www.website.com/folder/version.php"]];
[urlRequest setHTTPMethod:#"GET"];
[urlRequest setHTTPBody:[postParams dataUsingEncoding:NSUTF8StringEncoding]];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self startImmediately:YES];
[connection start];
The above code will send the request to your server URL that you tell it to. Now when we get the response we'll do it like this:
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSString *responseVersionFromServer = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if(![loadedVersion isEqualToString:responseVersionFromServer]) {
//execute code here to show a local notification or UIAlertView
}
}
That will do all the comparison for you.
NOTE: You must add NSURLConnectionDelegate in your .h Header file!
Push Notifications Solution
There are services out there for allowing your application to have push notifications such as Urban Airship that you can log into and send a push notification to all of your users when an update becomes available. It's really simple to use, check out their website. (www.urbanairship.com)
You could consume a webservice and compare versions of your app (maybe save the version to NSUserDefaults), I'm sure there are other solutions but this one comes to mind. You could show an alert that links to the app store if the versions are different.

Sending different values to API than stored in NSUserDefaults

I have an app in the App Store that uses geofences to post updates to our local API. To date, this has been a smashing success. A couple months ago I installed Flurry to get some insight into customer usage and any unhandled exceptions out there. What I started getting back were some errors pertaining to the saved token we use to authenticate the user id.
This token is an MD5 salted hash of the username and password. No issues here, we use it for everything in the app. I store it in the NSUserDefaults and retrieve it before every API call. As we started narrowing down our search for the culprit, it is showing that we are sending a token that doesn't exist anywhere on our servers. When the user trips a geofence, it updates their status automatically, but a very small percent has been failing in the background causing some customer concerns.
Sorry for the long precursor, on to the question. What would cause my value in NSUserDefaults to get loaded to an NSString differently from one time to the next? I have tested the logic, it will update me 3 times in a row when I show up for work, but the 4th, I'll get a token failure error back from the server. There is no rhyme or reason to why it fails. Our server is logging these failed tokens and we can't match them up to anything.
So if anyone has any insight on this matter, I would very much appreciate it. Could it be messing up when I store it to NSString? Could it be some inconsistencies with my POST method? It might even be on our server, I dunno. I'm hoping everyone on SO can lend a hand and help me get some new insight. Thanks in advance.
//Loading the token... done this way for EVERY API call
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *token = [defaults objectForKey:#"token"];
Code taken from my API class
//My POST method for updating our API
NSString *requestData = [NSString stringWithFormat:#"?auth_token=%#", token];
NSData *myRequestData = [NSData dataWithBytes: [requestData UTF8String] length: [requestData length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL: [NSURL URLWithString:[NSString stringWithFormat:#"%#/api/user.json%#", webAddress, requestData]]];
[request setHTTPMethod: #"POST"];
[request setHTTPBody: myRequestData];
NSData *jsonData = [NSURLConnection sendSynchronousRequest: request returningResponse: nil error: nil];
NSString *json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSDictionary *payloadData = [json JSONValue];
if([[payloadData objectForKey:#"success"] boolValue]) {
// if i get here, successful update
} else {
// update unsuccessful, error message returned... bad token
}
Are you using any 3rd party libraries? "token" is a very common string, I would change it to something else. It's possible some other code is stomping on your token with their token.
I don't want this question to linger out there and waste anyone's time or energy. After some recent testing, I don't believe my issue is with the user defaults or the saved token. There are already answers out there, so I can't delete the question. So I'll just leave a small answer here and close it out.
I ended up moving all the login credentials to the iOS keychain. I salt and hash the email and password each time I need an API token. It creates a little more overhead for each API, but knowing my users' credentials are safe and secure is probably worth it. I still have my issue, but I think I'm getting closer to nailing it down. Thank you to anyone that has looked at my question.

Handling redirects correctly with NSURLConnection

For the purposes of this, I'm going to pretend the original url is http://host/form and the new url is https://host/form. (Note that before I ship this, both URLs are going to be secure. However, the nonsecure-to-secure seems like a convenient redirect to test this on.)
I'm accessing a web API using NSURLConnection that redirects me. Basically, I want to take everything I just submitted to http://hostaform and re-submit it to https://host/form. I thought this would be the default behavior, but it looks like the body is being lost in the redirect.
So I think I need to handle the connection:willSendRequest:redirectResponse: event of the NSURLConnection's delegate and re-attach the body. The problem is this message seems woefully underdocumented. The only info I can find on this method is NSURLConnection Class Reference, which isn't very helpful. Among other things, it includes this:
redirectResponse: The URL response that caused the redirect. May be nil in cases where this method is not being sent as a result of involving the delegate in redirect processing.
I'm not sure what this means. Combined with an initial willSendRequest: invocation, I think this is means willSendRequest: is being sent even for my initial request, prior to the redirect response. Is that correct?
So I've added code to my delegate to retain the body an extra time, and added this willSendRequest: handler:
- (NSURLRequest *)connection: (NSURLConnection *)inConnection
willSendRequest: (NSURLRequest *)inRequest
redirectResponse: (NSURLResponse *)inRedirectResponse;
{
if (inRedirectResponse) {
NSMutableURLRequest *r = [[inRequest mutableCopy] autorelease];
[r setURL: [inRedirectResponse URL]];
[r setHTTPBody: body];
return r;
} else {
return inRequest;
}
}
It doesn't work. But I'm not even sure if this is the right approach. It seems excessively hackish to me. What should I be doing? Is this documented anywhere? I've found nothing useful in Apple's documentation or using Google so far.
(This is on the iPhone, although there doesn't seem to be much difference in these classes.)
There's a note in section 10.3.2 of RFC 2616 about this behaviour:
Note: When automatically redirecting a POST request after
receiving a 301 status code, some existing HTTP/1.0 user agents
will erroneously change it into a GET request.
So this behaviour seems to be non-standard but historical. That GET request is not a POST, and it'll be missing the payload.
Interestingly enough, this is also in the same section:
If the 301 status code is received in response to a request other
than GET or HEAD, the user agent MUST NOT automatically redirect the
request unless it can be confirmed by the user, since this might
change the conditions under which the request was issued.
That's pretty clear and seems to indicate we can't fix this, but I think ignoring this for the purpose of our own web service clients for services we pick (or control) is probably the least bad alternative.
So how do we solve this?
Instead of the willSendResponse: in the original question, I'm using this:
- (NSURLRequest *)connection: (NSURLConnection *)connection
willSendRequest: (NSURLRequest *)request
redirectResponse: (NSURLResponse *)redirectResponse;
{
if (redirectResponse) {
// we don't use the new request built for us, except for the URL
NSURL *newURL = [request URL];
// Previously, store the original request in _originalRequest.
// We rely on that here!
NSMutableURLRequest *newRequest = [_originalRequest mutableCopy];
[newRequest setURL: newURL];
return newRequest;
} else {
return request;
}
}
The idea here is that instead of cloning the new request and trying to shape it the same as the one Cocoa Touch sends me, I create a clone of the original request and change just the URL to match the request Cocoa Touch sent me. That original request is still a POST with the payload attached.
If you control the server, it's worth reading RFC 2616, section 10.3 in its entirety to see if there's a better code you can use (while checking, of course, that iOS handles the better code as it should).
You could also make a mutable copy of the redirected request and replace its HTTP method with the HTTP method of the original request. Same general principle, though that would favour keeping things from the new request rather than the old. In some circumstances that might work better, but I haven't tested this yet.
You should be checking the HTTP response status code sent by the server to determine whether to send a GET or repeat the POST. For 303 (or 302), send a GET request. For 307, repeat the POST.
i had the same problem with redirecting.
Thanks to AJSoaks!
I tried as he suggested and the problem is resolved.
So, i was trying to post the username and password through the POST method, and i saw that server redirected my request. As AJSoaks says, in case if there is 302 error you should repeat the request but this time using GET method instead of previous POST.
... at some point you have the following lines:
... it can be inside if your IBAction (button pressed) method or wherever you want...
NSMutableString *postString = [[NSMutableString alloc] init];
[postString appendString:#"username=YourUsername&password=YourPassword"];
//the original URL (https means that it supports SSL protocol)
//it doesn't change anything, don't worry about it
NSURL *URL = [NSURL URLWithString:#"https://loginWebpageURL"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL];
[request setHTTPMethod:#"POST"];
[request setValue:[NSString stringWithFormat:#"%d", [postString length]] forHTTPHeaderField:#"Content-length"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-type"];
[request setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];
[NSURLConnection connectionWithRequest:request delegate:self];
[postString release];
[request release];
Than you should also implement the redirect NSURLConnection delegate method, with the following signature:
- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse
inside of this method, in case if you have SERVER's Error 302 or 303 you should implement something similar to the code bellow, just copy the code that you see and replace it with the new URL (redirected). The new URL you can see in the browser or if you want it is very useful, also in the future, checking it with Firebug (Firefox plugin) or Safari WEB INSPECTOR. If you use Firebug all information you can find under the "Net" option:
if (redirectResponse) {
NSLog(#"REDIRECT");
NSMutableURLRequest *requestTmp = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"https://areaclienti.tre.it/selfcare/areaclienti133/4552_infoCosti_ITA_HTML.xsl"]];
return [requestTmp autorelease];
}
//return original request in case thay there is no redirecting...
else return request;
NSURLConnection does not add the originalRequest headers into the redirected request in the "willSendRequest: (NSURLRequest *)inRequest".
You can workaround this problem by adding "originalRequest.headers" into the redirected request.