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);
            }
        }

Windows Phone 7 – and so it begins…

First round of WP7 development impressions:

(+) A good deal of the C# code/classes I wrote for monotouch (iOS) versions of apps can be re-used as-is from the WP7 version of the app.

(+) Full-on Visual Studio experience that I am used to from my ‘day job’.

(-) Anything using XmlDocument has to be re-done. But it’s not that hard.

(-) There doesn’t seem to be any built-in way to handle compression. You need to get some third party library, and re-write all the stuff I already have working on iPhone using GZipStream. BOO. It seems like the intent is to have Silverlight use the browser’s capabilities to handle gzip content, except for a little issue that prevents you from setting the Accept-Encoding (gzip,deflate) on HTTP requests where you would like the large REST return data to be compressed.

(+) Speed, polish & efficiency – this is obviously biased to some degree by my years of experience with Visual Studio & .NET,  but I am so much more productive, even though I still have to lookup Silverlight ‘nuances’ nearly as often as I do for Android or iOS.

(+) WP7 Emulator works with Fiddler!!! Well, if you read THIS, it does.

The joys of ‘anonymous’ feedback.

I would like to briefly share why I do not like the feedback model of the iPhone and Android markets. I will preface this by saying that I have built a few simple apps, do not charge for them (for the most part), and use them all personally. To be fair, I also have received a good amount of detailed, critical ratings that actually explained what they would like to see and what their issues were with the app - that kind of ‘bad’ feedback is always welcomed.

Anyway…enjoy.

Jeff (commenting on an app that i built in conjunction with a Christian parenting organization) – 1 star

“Its a infomercials lots of infomercials how dumb is this five stars of pooop and they call their self Christian LOL”

I suspect Jeff was not /really/ looking for parenting help. If he was, I pray for him and his children.

Tori (commenting on an app that facilitates tracking your ‘debt snowball’ a la Dave Ramsey) – 2 stars

“It is LLLAAMMEE it wont let you do anny thing it is boring”

Because tracking your debt should be EXCITING! And should also let you control your TV.