11

We built an app using React Native to improve UX and features of our previous Cordova app.

Everything went fine. Several months of development, QA, App review and then we published to App Store. It worked on all devices we tried, from iPhone 4s to iPhone 6s+, we tested on iOS 8.3 (earliest simulator you can download through xCode) to 10.0.

After release lots of users started reporting that app crashes before splash screen even goes away. Behaviour we haven't seen in the app review, testing or anywhere else before.

We investigated "crashes" in xCode and they obviously didn't show up, because hundreds of users experienced a crash and we were able to only see few - which seemed unrelated to startup.

We released an updated version with Crashlytics integrated, but that didn't help either. We do not get Crashlytics errors for this specific problem either, meaning that problem is probably happening before

Any ideas where should I look next? We really do not want to revert to the old version and lose months of work.

The app uses around ~100MB of memory when everything is loaded, so that shouldn't be a problem I presume. It is happening on all versions of iOS across all devices. We cannot isolate the error to only specific users.

4
  • 1
    Is Crashlytics giving you a crash stack trace, or are you not getting anything back at all for your crashes? If you don't see anything at all, then it may be crashing even before the Crashlytics initialization happened. Commented Nov 24, 2016 at 21:56
  • 1
    Some info missing here, that we need in order to help you: First, what is crashlytics giving you? Second, can you isolate a device that is doing this (that would be most essential). Good news, is once you get a device that does this, you can usually publish to device locally and reproduce. Once you have the above, I feel this issue will be quickly resolved. A doctor can't treat someone without seeing the patient. Commented Nov 26, 2016 at 15:04
  • Crash must be happening before Crashlytics gets initialized. I cannot isolate the device that is happening on either. I will update the question accordingly. Commented Nov 28, 2016 at 18:53
  • if you have an iOS device where crashes, get the crash log from the device. otherwise a link to the app might be helpful so someone here can test and provide crash dump. once you have that use your apps debug symbols to get the crashing function. Commented Nov 30, 2016 at 20:24

3 Answers 3

6
+150

When there doesn't seem to be any other avenue of analysis, I resort to the humble fall-back of logging.

I have previously used the following technique in production iOS apps. This is a bit of work to set up, but once going it is tremendously useful for many other problems in the future. Not just crashes, but any other strange behaviour that users report which you cannot replicate in your testing environments.

  1. The very first thing the app should do is check if the PREVIOUS startup was successful or not by reading some values that should have been written to defaults at the beginning and end of the previous startup (details in the next step). If the PREVIOUS startup was NOT successful, give the user the option to run in some sort of 'safe mode' (what this means depends on what your app tries to do at startup, but for me it meant not loading any data, or doing anything much apart from displaying the UI devoid of any data-dependent items; for some apps, it could even go so far as to load a completely different UI which includes only diagnostics tools or data deletion/reset tools).
  2. The very next thing the app should do after determining that the the previous startup was OK (or that this is the first startup ever) is to write some sort of "startupBegan" status to defaults as soon as possible and then later some sort of "startupCompleted" status only when it has fully completed startup (what "fully completed startup" means is app-dependent, but you really want to be certain that the UI is fully responsive at this point, and is displaying everything it needs to; this can be a bit tricky to determine sometimes, as some things don't run until after the splash screen has disappeared, etc; if you cannot find any other way, I suppose you can trigger this with a timer, but that would be rather ugly - better to find some way to determine when startup really is fully completed). These values can be used to determine if the startup began, but did not complete and are what step 1 (above) uses to determine if the previous startup was successful or not.
  3. Include lots of logging in the app (my old custom Objective-C version of how to do this has been replaced with a more relevant Swift version in the "UPDATE" section below).
  4. Give the app the ability to email log files to your support email address - this must be available in the app's 'safe mode' (as well as in normal mode). In normal mode, I make this fairly obscure so as not to be noticed much by the user when all is well (eg, a button right at the bottom of a 'Settings' or 'About' view). I tell the user how to find the button when they've submitted a support request for which I really need the logs.

Many variations on this are possible. Including things like only enable logging if the user has configured a setting for it. You may have to add a whole lot of logging around particular areas of code sometimes when a user reports a particular problem, and then delete it again after the problem is resolved (if you are concerned about the performance/storage issues around logging).

For my (Objective-C) app, the places for including my code to write startup status to defaults were as below (there may be better places more suitable for your app):

  • "startupBegan" early in the app delegate's application:didFinishLaunchingWithOptions:
  • "startupCompleted" at end of view controller's viewDidAppear (NOT viewWillAppear! there's a lot of stuff can go wrong between these two being sent)

UPDATE:

Logging in iOS is a LOT better than it used to be when this post was first written, so I've removed the clunky custom Objective-C NSLog() and stdout file redirection instructions I originally had in this post. Instead I now (using Swift) do something like this, at the global level (eg, in AppDelegate):

let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "app") (Optionally, you can have multiple loggers which will identify themselves in each log line. I also have another one in one of my apps to which I redirecdt all JavaScript logging from a web view, like:

let jsLogger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "JS")

Then to actually write to the logs, use something like:

logger.log("Some message being logged: \(some variable, privacy: .public)")

Then to gather actual logs (eg, if the user hit's the 'Send Logs' button in a 'Diagnostics' view of the 'Settings' UI):

guard let logStore = try? OSLogStore(scope: .currentProcessIdentifier) else { return }
let oneHourAgo = logStore.position(date: Date().addingTimeInterval(-3600))
guard let allEntries = try? logStore.getEntries(at: oneHourAgo) else { return }
let filteredEntries = allEntries.compactMap { $0 as? OSLogEntryLog }.filter { $0.subsystem == Bundle.main.bundleIdentifier! }
                
var logText = ""
                
for entry in filteredEntries {
    logText.append(contentsOf: "\(entry.date): \(entry.composedMessage)\n")
}
                
let logData = Data(logText.utf8)

Then attach the logData to an email ready to send to support (eg, using a MFMailComposeViewController.)

Sign up to request clarification or add additional context in comments.

2 Comments

Your suggestion is much appreciated. I started the implementation but we got on track before I finished. I will give you the bounty because I got some nice ideas for the future from your answers that might help. Thanks!
Hey thanks! Glad to hear you got it resolved. I was curious to hear what was actually going on, so it was interesting to read you own answer.
3

The problem took so long to resolve because of bad communication between us and our users. App was actually NOT crashing, just not starting up (which is the same in the eyes of some users).

After we discovered that, we realized that one of the events is not firing (the one that hides extended splash screen) and this is where users got stuck. One of the libraries we were using didn't correctly handle the error scenario and it made our job much harder. I was lucky to get into that state while testing and I could continue from there.

I updated the code to handle that scenario and the issue is now resolved.

1 Comment

I have this issue. Which event was supposed to fire? How did you fix this exactly?
0

I had this issue and my case might be quite specific but I'll share my experience anyway.

The point here is the app only crashes in production, so it's something that's fired only on production that is messing up the build. In our case, the culprit was one of the React's dependencies that does minification.

Things to take away:

  1. Keep an eye on any updates of your dependencies
  2. Use crash log

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.