Wednesday, August 22, 2012

App rejected because Apple uses sandbox account when testing

I encountered this about 2 weeks ago when my newsstand app was rejected by Apple because "in-app purchases weren't working". I was quite shocked at the time because I have tested everything rigorously and it was definitely working for me during my own testing. So after further investigation inspecting the server logs, I see the following errors:

 INFO | Apple's response: {"status":21007}

The receipt status code of 21007 means "This receipt is a sandbox receipt, but it was sent to the production service for verification." You can see the complete list of auto-renewable verification status codes here. So it seems that when reviewing apps, apple could be using a sandbox iTunes account to purchase stuff in prod! This was something that I definitely did not expect and cater for in my production server code.

I wasn't 100% convinced about this but further research confirms this: "Always verify your receipt first with the production URL; proceed to verify with the sandbox URL if you receive a 21007 status code. Following this approach ensures that you do not have to switch between URLs while your application is being tested or reviewed in the sandbox or is live in the App Store.". You can see the full article here (faq #16).

That pretty much confirmed it. I was still quite bewildered that after 5 app approvals this is the first time that I have encountered this issue. I guess it depends on your luck what kind of tester you get when your app is being reviewed. Long story cut short the changes I made to my server code looks something like this:

public static Receipt GetReceipt(string receiptData)
{
    const int SandboxReceiptSentToProd = 21007;
    var receipt = GetReceipt(urlProduction, receiptData);

    // GOTCHA: During apple approval, apple could be using a sandbox itunes account to purchase stuff in prod!
    // We have to check if receipt comes back with status 21007, that means the tester had used a sanbox account and
    // that we have to post the receipt to sandbox url instead of production
    if (receipt.Status == SandboxReceiptSentToProd)
    {
        // send receipt to sandbox now
        receipt = GetReceipt(urlSandbox, receiptData);
    }

    return receipt;
}

I'm still not happy that I need to have a sandbox reference in my production code, but you just have to follow by Apple's rules. Hope this helps someone else!

Monday, August 20, 2012

CheckForIllegalCrossThreadCalls on MonoTouch 5.99

One of the first things I encountered after I upgraded to iOS6 (beta 4) and MonoTouch 5.99.2 was that my app would crash intermittently at random places. That got me quite worried, but a bit of googling reveals that this is because of a new feature in MonoTouch 6.0.

So the new version of MonoTouch will check for illegal cross thread calls and crash your app if its been naughty! The property UIApplication.CheckForIllegalCrossThreadCalls defaults to true for DEBUG ONLY. This is meant to educate the developers to fix the illegal cross thread calls.
If you are after a quick fix, you can do this in your AppDelegate FinishedLaunching method:

public override void FinishedLaunching (UIApplication app)
{
    UIApplication.CheckForIllegalCrossThreadCalls = false;

    // your other init code here ...
}

However beware that the correct thing to do will be to fix your actual code!

Tuesday, August 7, 2012

iOS 6 Auto Rotate and Orientation Changes on MonoTouch 5.99 (alpha)

In iOS6, auto rotate and orientation changes have changed quite significantly.

In a nutshell, these are the steps I took to upgrade my monotouch project to work with the new changes:


1. You will need to assign a root view controller to your main application window on FinishedLaunching. This is the crucial bit! I spent quite sometime figuring this out. Basically if you don't set your root view controller, you will get a warning in build output that looks like this: 

Application windows are expected to have a root view controller at the end of application launch

So if previously like me you have this in your FinishedLaunching(UIApplication app) method in main.cs:
window.AddSubview(mainVC.View);

Replace it with this:
window.RootViewController = mainVC;


2. Replace this:
public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)

With these two lines:

public override bool ShouldAutorotate()
public override UIInterfaceOrientationMask SupportedInterfaceOrientations()


Obviously you will need to specify the orientation masks your view controller support in SupportedInterfaceOrientstions(). For example, if you support all orientations you might want to do this:

public override bool ShouldAutorotate()
{
return true;
}

public override UIInterfaceOrientationMask SupportedInterfaceOrientations()
{
return UIInterfaceOrientationMask.All;

}

That's my experience in getting auto rotation to work in ios 6 on MonoTouch 5.99. Check out the full release notes in the developer portal or if you don't have a login, you can check it out here.