How to properly handle Core Data save errors wrt user experience on iPhone? - iphone

So, I have a basic app for storing, searching, and manipulating data. Basic CRUD operations. In various places of my code where I'm storing or updating this data, I basically have this:
NSError *error;
if (![self.managedObjectContext save:&error]) {
// TODO: Handle this error
NSLog(#"Error while saving data %#, %#", error, [error userInfo]);
}
What do most people do, user-experience-wise, when these sorts of things come up?
The only thing that comes to mind is to just pop up some horrible UIAlertView with a vague message that something went wrong; not really sure how to recover these things.
For the sake of argument, lets assume my model has little to no validations, so the only errors that might occur would either be something horribly wrong or a programming problem.
Any good ideas on the user experience?

Jakob Nielsen has a few concise guidelines you might want to check out; out of everything he proposes, the one I strongly suggest you indicate in an error message that it's not the user's fault. From my own experience in user testing, most users believe they did something wrong when an error pops up, and this leads to frustration.
Maybe something like:
There was a problem saving: don't worry it's not your fault! If you restart the app, you can try again. But please contact [developer contact] and tell him the error was [short, memorable error code]

In a case like this, I think the best thing to do would be to present an alert to the user and quit the app.

Short answer is that I have never had a save error occur in production. Normally I make these asserts and have them crash the app. This is to make sure I catch them during development.
If your application is well-written you should not have one of these occur when a user is running the application.
As for the text, it depends on your application and there is no general rule for them.

Related

Ways of handling serious problems on an iPhone app

Say you are doing something critical to the app's usability at startup (like copying the SQLite DB or setting up CoreData) and something goes wrong that doesn't cause a crash but you don't want the user to continue. What can you do?
Currently my app has abort() and NSAssert(false,...) calls to make sure the app doesn't continue, obviously after the error has been logged. But somehow I think its not going to score points with Apple on the app store.
Anyone have any ideas what I can do in such situations? I understand for instance that if there is no connectivity you can put your app in 'offline' mode but lets say the DB couldn't be properly setup (for argument sake). There is no 'offline' for that and so the user cannot continue. The user needs to quit the app and try again or report the problem. Wouldn't you agree, or am I missing something?
I just decided to create a view that has a message on it explaining to the user that a critical error has occurred and explained some steps to follow to resolve it. It prevents the user from using the app until it is resolved. So if the DB is missing or the model schema doesn't match the DB schema it will bring up that view.
Its one way of doing it that I took. If anyone has a different way, I'd like to hear it.

How delete from CoreData fast?

I have an app with some stocks information for example and store it in CoreData. I have two entities Stock(name) and DailyStockData(date,number) and I store two years information for each stock entity. Then user can delete some Stock names in TableView.
In my - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
I delete this Stock and DailyStockData cascade and save context then.
My save code looks like this
// Save the context.
NSError *error = nil;
if (![context save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
The problem is: when User deleting stocks fast - my app crashing. I understand that the reason is "abort" command when my device didn't finish save previous deleting transaction and call to save it again.
So I have two questions here ^)
1. Is there any way to prevent this crash?
2. What is best practice to deal with context save error? May be I can try save it again and again rather than just abort.
The simplest solution is redesign application to save context at the "very" end - for example when my app change view or something but I want to know all possibilities.
You should NOT invoke "save" frequently, it's bad for performance.
The "save" method let the CoreData actually save the modified data to your disk, it takes time if there are a lot of data need to be saved.
Just save once while your App is going to exit or going background is good enough.
If you worry about an unexpected crash will lose some data, you can invoke "save" when your users stop modifying data for a while or exited the edit view in your App. It depend on your decision, but just don't save every time your data is changed.
What i do is, i create the database using CoreData, but i dont use simulator to put in information into the database. I use Navicat lite to put in information into the database. If you want to delete the whole database, just delete it from the simulators folder under user/library/application support/iphone simulator//applications//documents/. That will delete the whole database, unless u have synched with ur application folder.
Is there any way to prevent this crash?
Yes, remove the abort(). The abort is only used in minor example code when you don't want to take the time to log, analyze or respond to errors. You would never use it in real code.
What is best practice to deal with context save error?
The first thing to do is log the error in detail so that you have some idea what is actually failing.
I understand that the reason is "abort" command when my device didn't
finish save previous deleting transaction and call to save it again.
Unlikely. It is almost impossible for UI driven action, which works at human speed, to overload any local computer operation. Saving even several hundred objects in Core Data takes only milliseconds while it takes a human a full second or more to delete a row in the UI. If the crash is happening while the UI is in use, then the error is probably related to the UI e.g. not properly updating the section and row count for the tableview.
Don't guess. Log the save error and find out what is actually happening.

SKRequest didFailWithError

-(void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
}
With in this method one could arrest the error that happened for ex - ("Failed to Connect to iTunes") etc. Now if one wants to show this error message as an alert it would be really helpful for users. Like one example is when the "Failed to Connect to iTunes" error is shown as an UIAlertView, it can lead the user to the problem that his wi-fi was off all this while. He can rectify this problem and retry. But before one chooses to display these errors as UIAlertView, one must know all the possible error values that are returned because a user won't be pleased to see something like "Error %^#)$()(!)#()+! code 123_123443 blah"i.e.something incomprehensible to him. How to best handle this ? As an after thought, it would be nice to know all the possible error messages of NSError thrown in this method.
Thanks in advance
one must know all the possible error
values that are returned because a
user won't be pleased to see something
like "Error %^#)$()(!)#()+! code
123_123443 blah"i.e.something
incomprehensible to him
I don't know all the possible errors that particular method can return but I think it is safe enough to use the localizedDescription method of NSError to present the error to the user.
From Apple docs:
Error objects in the Cocoa domain are
always localized and ready to present
to users, so they can often be
presented without further evaluation.

iPhone Production/Release Exception Handling

In advance, please excuse my lack of understanding for iPhone/Objective-C Best Practices; I come from a .NET/C# background.
Although, I've read many posts regarding exception handling for the iPhone, I'm still not exactly clear on what most people do for production code. Also, I haven't been able to find any open source apps with error handling that I would normally expect. Here are my questions:
1) If an unexpected result occurs that would cause the application to eventually fail, would you throw an exception or just wait for it to fail later? For example,
if (![fileManager createDirectoryAtPath: myNewDir
withIntermediateDirectories: YES
attributes: nil
error: &myError]) {
// My app shouldn't continue. Should I raise an exception?
// Or should I just log it and then wait for it to crash later?
}
2) Do you validate parameters? For example, in C# I would usually check for null, and if needed throw an ArgumentNullException.
3) When an app crashes, does the crash info get logged automatically or do I need to set the unhandled exception handler? Can I show a UIAlertView within this method to inform the user something bad happened, instead of having the app just disappear? (If so, I couldn't get it to work.)
4) And finally, why don't I see anyone actually using #try/#catch/#finally? It's used extensively in C#, but I haven't found open source apps using it. (Maybe I'm just looking at the wrong apps.)
Ad 1. The example you give is a somewhat classical example of when one should use a "checked exception" in Java. The caller of the method has to be prepared, that the call can fail for reasons which are outside of what it can control. In particular, in the example given, I would allow the error descriptor object allow to propagate back to the caller:
- (BOOL) prepareDirectories: (NSString*) path error: (NSError**) location {
if( ![fileManager createDirectoryAtPath: path
withIntermediateDirectories: YES
attributes: nil
error: location] ) {
return NO;
}
// ... additional set-up here
return YES;
}
If the error is really not recoverable, you may actually raise an exception, but that will also forfeit any chance to show a proper error message to the user of your application.
Ad 2. Yes, parameter validation is definitely something you should do. And raising an exception for unexpected nils is entirely appropriate. A bad parameter is a "programmer's error" and you cannot guarantee, that the program is still in a consistent state. For example, NSArray raises an exception, if you try to get an element using an non-existent index.
Ad 3. When the app crashes due to an uncaught exception, the fact is logged automatically. You can catch the exception if you really want to display an error message. However, the app is likely to be in an inconsistent state, and should not be allowed to continue, so simply allowing it to go down seems the best solution here.
Ad 4. I don't know.
In the specific case you mention, you should present a notification to the user about what just happened with instructions on how to fix the situation. Except in extreme circumstances your app should not quit. You should let the user fix whatever is wrong, then try again.
Parameter validation depends on a lot of factors. One thing to keep in mind is that in Obj-C it's perfectly valid to send messages to nil (nothing happens if you do) so in a lot of situations you don't need to check for nil. If I'm in a model class I always validate parameters in methods. If I'm not I almost never validate anything, type checking is good enough.
Some information should be logged but it's always helpful to log more specific information to your situation. Ideally you shouldn't ever reach the unhandled exception state.
Cocoa classes will rarely throw exceptions for environmental reasons, they are almost always because you did something wrong. For example, you wouldn't usually put a #try/#catch block around [NSString stringWithString:] because the only way it'll throw an exception is if you try and pass nil. Make sure you aren't passing nil and you won't have to worry about catching exceptions. When a method might fail because of something outside of your control, they usually communicate via an NSError. See the - (BOOL)save:(NSError **)error method of NSManagedObjectContext for example.

handling a fatal error in iphone app

how to handle error in an iphone app ?
log and exit ? show an alert dialog and exit ?
for exemple, if an image is missing from the bundle..even though it should not ...
You are never supposed to exit the app programmatically. Advise the user there was a problem, offer to try it again, etc. But don't kill the app. This is explicit in the Apple HIG.
Let the user decide your app needs exiting. Don't do it for them.
Ideally, don't get yourself in this situation. : ) Easier said than done I know.
#Genericrich has it pretty spot on:
Advise the user there was a problem, offer to try it again, etc. But don't kill the app. This is explicit in the Apple HIG.
The only advice I would add is to expect the unexpected. Just make sure your app is ready for those little blowups. This might be things like: default information to fill in the blanks, adequate alerts to let users know what's happening/retry, saving state before attempting failure prone destructive actions, and any other defensive programming habits you can think of.
As an added note if you are wanting to test network errors you may want to check out Craig Hockenberry's excellent post Slow ride, make it easy on the subject.
Alert box and exit should be fine. Short and sweet with just enough communication with the user to let them know why your software is about to not work.