Windows 8 – Cannot Link to Facebook

So there is this whole ‘charms’ interface in Windows 8 (hover your pointer in the upper left corner) with a nice ‘Share’ button. However, since Facebook doesn’t have an official Windows 8 app, it doesn’t show up as an option when you click the ‘Share’ button.

However, you DO get a ‘Share’ option called ‘People’ – if you click that, then ‘Connect to Facebook’ is an option.

EVERY time I tried to ‘Connect to Facebook’ from the People app, I got the message “We can not connect to the service you need right now. Please check your network settings or try again later.” You may get similar or other errors when trying to connect Twitter or Google accounts in the same fashion.

The fix (at least for me) was to go to https://profile.live.com/services and authorize the Facebook/Twitter/Google connections from there first.

It’s not me, it’s you

I just finished my annual pilgrimage of the mind…the one where I contemplate NOT renewing my iOS developer license because once again, I procrastinated a year away of nothing but a few minor updates…and, as usual I rationalize myself into another year. Usually this coincides roughly with a new major version of iOS about to be released, and realizing that I need to re-up my MonoTouch license as well, so I can recompile my laggard apps so they don’t crash when everyone downloads iOS #NEW. So last night, I fire everything up, it’s all going smoothly, until I hit Application Loader.

“Unable to process application Info.plist validation at this time due to a general error ”

Well after redoing my Info.plist twice from scratch, one line at a time, and watching it break eventually after adding all required parts, I finally found some current threads by other people exhibiting the exact same issue, as well as periodic similar issues over the last few years. So I slept on it, and tonight, the package submitted successfully right away. All is right with the world.

Windows 8 – Copy file from Assets to Local Folder

When porting mobile apps to Windows 8, I immediately came across the same issue as each of the previous platforms, namely, how to copy my ‘starting’ data files into local storage where they can be modified.

In this case, I have a file in my Assets folder called ‘Prayers.xml’. At application startup I want to verify if the file already exists in the local store – if not, copy it from the Assets folder in the installation directory.

This is how you end up handling it in Windows 8.

        async void CopyPrayerFile()
        {
            //get the storage for your app 
            Windows.Storage.StorageFolder store = Windows.Storage.ApplicationData.Current.LocalFolder;
            StorageFile prayerFile = null;
            try
            {
                prayerFile = await store.GetFileAsync("Prayers.xml");
            }
            catch (System.IO.FileNotFoundException)
            {}

            if (prayerFile == null)
            {
                //get the file from Assets

                StorageFolder install = Windows.ApplicationModel.Package.Current.InstalledLocation;
                StorageFile installFile = await install.GetFileAsync("Assets\Prayers.xml");

                installFile.CopyAsync(store);   
            }
        }

The new Android Market Developer agreement…

So I logged in to my Android Market publisher account, because it’s been a while, and I’m thinking of updating some apps, and trying again to see if I can reproduce some of the fairly major (I admit it) bugs that keep getting reported that I just can’t track down…and lo and behold, there’s a new developer agreement. Normally, like most of the world’s population, I just scroll down and click ‘accept’ these agreements. For some reason this time, I decided to read it first.

Following are a list of concerns that struck me on reviewing this thing…

***SPOILER – I still accepted it – what choice do I really have?****

***DISCLAIMER – I am a pure hobbyist. Yes my personal apps are not polished, and I have nowhere near the appropriate level of QA resources to ship bug-free. The products work on my hardware, and any bugs I have been able to reproduce DO get fixed in my limited time.

3.2 Developer is responsible for determining if a Product is taxable and the applicable tax rate for the Payment Processor to collect for each taxing jurisdiction where Products are sold. Developer is responsible for remitting taxes to the appropriate taxing authority.

  • Did I, as a hobbyist selling apps through a required Google-authorized payment processor, just have said payment processor abdicate all responsibility of tax collection and remittance? Isn’t that much more suited to what a payment processor DOES? And now any taxes (even if there aren’t any NOW) that come up in the future, all come directly out of MY share.

3.5 Except in cases when multiple disputes are initiated by a user with abnormal dispute history, billing disputes received by Payment Processor for Products sold for less than $10 may be automatically charged back to the Developer, in addition to any handling fees charged by the Payment Processor. Chargeback requests for Products $10 or more will be handled in accordance with the Payment Processor’s standard policy. 

  • In my opinion, chargebacks are the WORST part of this whole business for a hobbyist developer. This policy has bounced back and forth a few times at Google, but now it seems that they are landing directly in the camp of “Developer pays all chargeback fees”. In case you’re not familiar, a chargeback is when someone uses a credit card to buy a $.99 app, then decides to dispute the charges on the credit card. So I don’t get paid my $.99, and guess what, I also get dinged a $10 ‘chargeback fee’ by Google Checkout – and I just personally lost $10 on a $.99 sale.
  • This ALONE is very close to enough for me to immediately take any non-free apps off the android market, just because some competing schmuck could decide to have all his buddies buy my app, give it a crappy rating, and then do chargebacks and cost me money directly. Unlikely? Sure. But absolutely possible.
  • How about this scenario? I can only test on the ONE Android device I own. According to the bug reports, there are people that the app crashes for, but I CANNOT PERSONALLY reproduce the issue, and I have no idea what hardware they are running. I have limited time and resources for this hobby, and if it crashes when you install it, PLEASE just get the 15-minute refund. But, suppose instead the user gets frustrated (totally understandable), and instead of just trying to get a refund, disputes the credit card charge. BAM.
  • The $10 price point is just Google thumbing their nose at Android developers. How many Android apps have you bought that have a price > $10? The OLD policy was that if the CHARGEBACK total amount was < $10, Google would NOT pass it back to the developer. Now, it says the developer gets it AUTOMATICALLY, unless the SALE price is > $10.
  • I’m sure somebody is probably thinking ‘GREAT, this will get all those useless apps off the Marketplace’ but to me it’s pretty shoddy behavior. If you want to curate the marketplace, then do it.

4.9 Your Products may be subject to user ratings to which you may not agree. You may contact Google if you have any questions or concerns regarding such ratings.

  • Translation: If someone decides to post a completely idiotic review (happens ALL the time) there is NOTHING you can do about it. If it’s egregious enough, and you happen to make us a lot of money, we might pursue it, but if you are making that much money, your legit-to-idiot rating ratio is probably not that bad so you’re not as concerned as an indie who is throwing out some proof-of-concept utility apps and getting trashed by folks who want an entire mini-Microsoft Office suite for free.
  • No clear, defined policy for trash reviews is a big failure here IMHO.

At this point I kind of just sighed and scrolled down the rest of the text.

“Accept”. Click.

Back in the [iOS] saddle again.

I thought I would take this opportunity to inform the world that, yes, I have relicensed MonoTouch [now by XAMARIN!] after a brief foray into Objective-C. MonoTouch is far too nice a crutch to give up now, especially given that I am developing on Android and Windows Phone as well, and just don’t have the time or patience to step back into the land of pointers and such.

In related news, I also updated my Mac Mini to Lion. And it immediately blew up my finely-tuned Vine Server VNC setup (the mac mini is hidden away on a shelf somewhere, and I access it only via VNC). But, soon after, I figured out ‘Screen Sharing’ at least and am up and running again. [No, I am /not/ a 'Mac' person].

MonoDevelop and MonoTouch have come a long way just in the last several months – I’m feeling motivated to put out some fresh iOS 5 updates!

Quick note on WP7 Advertising platform

I didn’t really see this clearly documented anywhere, I was probably looking in the wrong place, but when you are testing WP7 advertising, use ApplicationId = “test_client” and AdUnitId=”TextAd” instead of your ‘real’ pubCenter adunit/application id’s.

Also, I seem to have to set the Width of the control to 478 instead of 480 to get the border to appear all the way around the control. I’m not sure why, but that’s the only way I can avoid getting the left or right border of the ad being chopped off.

<my1:AdControl ApplicationId=”test_client” AdUnitId=”TextAd” Height=”80″Width=”478″/>

Bye Bye App Store (for me)

As much as I enjoyed the elusive achievement of getting published in the App Store, I got my thirty-day notice of my Apple Developer membership expiration…and I don’t think I’m going to renew. In no particular order, here is the ever-popular list form of why I’m leaning that direction at this time.

1. Building on the Apple platform was a really fun achievement, but was also the most problematic and time-consuming of the three mobile platforms I’ve touched so far (iOS, WP7, Android). And this is even WITH the use of MonoTouch to leverage my daily driver programming language, C#.

2. The last two apps I submitted were rejected for random inappropriate reasons. When I requested a review by the arbitration committee or whatever it is called, the response was basically ‘Yeah the reason the app was rejected was not really valid, but we still won’t approve it because we think it should do more.’ Like those crappy apps I see flooding the app store are somehow amazingly functional. I feel like the Apple approval process has become sort of like the proverbial insurance claim – the instructions are to deny at least 3 times before giving in, waiting at least a week in between each denial.

3. If I was doing this full-time I’d definitely hang in there and pay the $99, but as a hobbyist I think I’ll spend a year focusing on Android and WP7.

4. WP7 is really easy and intuitive for me. My iPhone 4 is actually starting to look and feel clunky to me after getting used to my WP7 phone.

5. If a client pops up who wants an iOS app, I can always sign up again later…

6. My apps are getting far higher download counts on Android and WP7 than iOS. As a hobbyist, that’s a big deal. Having something on the App Store is cool, but 1-5 downloads per day is kind of pitiful. That’s obviously my fault, not Apple’s, but I can compete a lot better in the other markets as a hobbyist who doesn’t want to spend tons of time building a detailed in-depth app, and prefers to stick to simpler, niche apps that I also use myself.

Files and local storage – Windows Phone 7

Every mobile platform seems to have its own nuances for application data storage on the device. Typically I use some sort of file or database to store application data, and so there are three primary things I need to do.

1. Copy the ‘initial’ file from my application package to the device.
2. Read the file from the device as part of the program’s operation.
3. Update the file on the device when the user adds or updates data.

Since I had problems figuring out how to do this on every platform, I’m going to quickly document it. Since Windows Phone 7 is my most recent platform, I’ll start there. Look for Android and iOS (monotouch) versions of this coming soon.

These examples are a little more than just the file I/O lines of code, I have left the context of my read/write methods in place as well just in case that makes things clearer. I essentially have an xml file that contains all the ‘pre-loaded’ objects serialized. Over time, the user can ‘sync’ with a web service to get new items, which then get added to my saved xml file.

I cut and pasted a lot of the code I started with from web searches, but I didn’t keep track of who I borrowed which code from, so apologies to anyone who might be offended if they see something that looks like their blog post code below. Just know that I did not write all code myself from scratch, and leave it at that. =)

This is from my WP7 app Prayer Cookie

1. Copy the initial file at app startup. Use the App.xaml.cs events to trigger on Application_Launching like this: (note that in my case, I have a folder ‘Data’ in my XAP, with a file called ‘Prayers.xml’ packaged as a resource – I want to copy this to local storage as ‘Prayers.xml’)

        // Code to execute when the application is launching (eg, from Start)
        // This code will not execute when the application is reactivated
        private void Application_Launching(object sender, LaunchingEventArgs e)
        {
            //get the storage for your app 
            IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication();
            //define a StreamWriter
            StreamWriter writeFile = null;

            if (!store.FileExists("Prayers.xml"))
            {
                //Create a new file and use a StreamWriter to the store a new file in the directory we just created
                writeFile = new StreamWriter(new IsolatedStorageFileStream("Prayers.xml", FileMode.CreateNew, store));
            }

            if (!(writeFile == null))
            {
                //use a StreamReader to open and read the file           
                StreamReader readFile = null;
                readFile = new StreamReader(GetResourceStream(new Uri("Data/Prayers.xml", UriKind.Relative)).Stream);

                string fileText = readFile.ReadToEnd();

                writeFile.Write(fileText);
                writeFile.Close();
            }
        }

2. Read the file from device as needed by page(s)

        private void LoadPrayers()
        {
            //get the storage for your app 
            IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication();

            IsolatedStorageFileStream loadStream = store.OpenFile("Prayers.xml", FileMode.Open);

            var xElem = XElement.Load(loadStream);

            loadStream.Close();
            
            prayers =
                (from elem in xElem.Descendants("prayer")
                    //orderby elem.Element("id").Value
                select new Prayer
                {
                    id = Convert.ToInt32(elem.Element("id").Value),
                    text = elem.Element("text").Value
                }).ToList<Prayer>();
            
            maxPrayerId = prayers.Max(x => x.id);

            ApplicationTitle.Text = "Prayer Cookie (" + maxPrayerId.ToString() + " total)";
        }

3. Save a modified version of the file to the device when changes are made

        private void SavePrayers(List<Prayer> newPrayers)
        {
            try
            {
                var store = IsolatedStorageFile.GetUserStoreForApplication();
                StringBuilder sb = new StringBuilder();

                //serialize the new prayers
                foreach (Prayer current in newPrayers)
                {
                    sb.Append("<prayer><id>" + current.id.ToString() + "</id><text>" + current.text + "</text></prayer>");
                }

                //read the current text of the file
                StreamReader readFile = null;

                IsolatedStorageFileStream fileStream = store.OpenFile("Prayers.xml", FileMode.Open, FileAccess.Read);


                readFile = new StreamReader(fileStream);
                string fileText = readFile.ReadToEnd();

                readFile.Close();

                //insert the text for the new prayers
                fileText = fileText.Replace("</prayers>", sb.ToString() + "</prayers>");

                //delete, then save the file
                store.DeleteFile("Prayers.xml");

                StreamWriter writeFile = new StreamWriter(store.CreateFile("Prayers.xml"));

                if (!(writeFile == null))
                {
                    writeFile.Write(fileText);
                    writeFile.Close();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "ERROR", MessageBoxButton.OK);
                Console.Error.WriteLine("Exception: " + ex.Message);
            }
        }