sqlite

Xamarin.Forms, Akavache and I: ensuring protection of sensitive data

Xamarin.Forms, Akavache and I: ensuring protection of sensitive data

Recap

Some of you might remember my posts about encryption for Android, iOS and Windows 10. If not, take a look here:

Xamarin Android: asymmetric encryption without any user input or hardcoded values

How to perform asymmetric encryption without user input/hardcoded values with Xamarin iOS

Using the built-in UWP data protection for data encryption

It is no coincidence that I wrote these three posts before starting with this Akavache series, as we’ll use those techniques to protect sensitive data with Akavache. So you might have a look first before you read on.

Creating a secure blob cache in Akavache

Akavache has a special type for saving sensitive data  – based on the interface ISecureBlobCache. The first step is to extend the IBlobCacheInstanceHelperinterface we implemented in the first post of this series:

    public interface IBlobCacheInstanceHelper
    {
        void Init();

        IBlobCache LocalMachineCache { get; set; }

        ISecureBlobCache SecretLocalMachineCache { get; set; }
    }

Of course, all three platform implementations of the IBlobCacheInstanceHelperinterface need to be updated as well. The code to add for all three platform is the same:

public ISecureBlobCache SecretLocalMachineCache { get; set; }     

private void GetSecretLocalMachineCache()
{
    var secretCache = new Lazy<ISecureBlobCache>(() =>
                                                 {
                                                     _filesystemProvider.CreateRecursive(_filesystemProvider.GetDefaultSecretCacheDirectory()).SubscribeOn(BlobCache.TaskpoolScheduler).Wait();
                                                     return new SQLiteEncryptedBlobCache(Path.Combine(_filesystemProvider.GetDefaultSecretCacheDirectory(), "secret.db"), new PlatformCustomAkavacheEncryptionProvider(), BlobCache.TaskpoolScheduler);
                                                 });

    this.SecretLocalMachineCache = secretCache.Value;
}

As we will use the same name for all platform implementations, that’s already all we have to do here.

Platform specific encryption provider

Implementing the platform specific code is nothing new. Way before I used Akavache, others have already implemented solutions. The main issue is that there is no platform implementation for Android and iOS (and maybe others). My solution is inspired by this blog post by Kent Boogart, which is (as far as I can see), also broadly accepted amongst the community. The only thing I disliked about it was the requirement for a password – which either would be something reversible or causing a (maybe) bad user experience.

Akavache provides the IEncryptionProviderinterface, which contains two methods. One for encryption, the other one for decryption. Those two methods are working with byte[]both for input and output. You should be aware and know how to convert your data to that.

Implementing the  IEncryptionProvider interface

The implementation of Akavache’s encryption interface is following the same principle on all three platforms.

  • provide a reference to the internal TaskpoolSchedulerin the constructor
  • get an instance of our platform specific encryption provider
  • get or create keys (Android and iOS)
  • provide helper methods that perform encryption/decryption

Let’s have a look at the platform implementations. I will show the full class implementation and remarking them afterwards.

Android

[assembly: Xamarin.Forms.Dependency(typeof(PlatformCustomAkavacheEncryptionProvider))]
namespace XfAkavacheAndI.Android.PlatformImplementations
{
    public class PlatformCustomAkavacheEncryptionProvider : IEncryptionProvider
    {
        private readonly IScheduler _scheduler;

        private static readonly string KeyStoreName = $"{BlobCache.ApplicationName.ToLower()}_secureStore";

        private readonly PlatformEncryptionKeyHelper _encryptionKeyHelper;

        private const string TRANSFORMATION = "RSA/ECB/PKCS1Padding";
        private IKey _privateKey = null;
        private IKey _publicKey = null;

        public PlatformCustomAkavacheEncryptionProvider()
        {
            _scheduler = BlobCache.TaskpoolScheduler ?? throw new ArgumentNullException(nameof(_scheduler), "Scheduler is null");

            _encryptionKeyHelper = new PlatformEncryptionKeyHelper(Application.Context, KeyStoreName);
            GetOrCreateKeys();
        }

        public IObservable<byte[]> DecryptBlock(byte[] block)
        {
            if (block == null)
            {
                throw new ArgumentNullException(nameof(block), "block cannot be null");
            }

            return Observable.Start(() => Decrypt(block), _scheduler);
        }

        public IObservable<byte[]> EncryptBlock(byte[] block)
        {
            if (block == null)
            {
                throw new ArgumentNullException(nameof(block), "block cannot be null");
            }

            return Observable.Start(() => Encrypt(block), _scheduler);
        }


        private void GetOrCreateKeys()
        {
            if (!_encryptionKeyHelper.KeysExist())
                _encryptionKeyHelper.CreateKeyPair();

            _privateKey = _encryptionKeyHelper.GetPrivateKey();
            _publicKey = _encryptionKeyHelper.GetPublicKey();
        }


        public byte[] Encrypt(byte[] rawBytes)
        {
            if (_publicKey == null)
            {
                throw new ArgumentNullException(nameof(_publicKey), "Public key cannot be null");
            }

            var cipher = Cipher.GetInstance(TRANSFORMATION);
            cipher.Init(CipherMode.EncryptMode, _publicKey);

            return cipher.DoFinal(rawBytes);
        }

        public byte[] Decrypt(byte[] encyrptedBytes)
        {
            if (_privateKey == null)
            {
                throw new ArgumentNullException(nameof(_privateKey), "Private key cannot be null");
            }

            var cipher = Cipher.GetInstance(TRANSFORMATION);
            cipher.Init(CipherMode.DecryptMode, _privateKey);

            return cipher.DoFinal(encyrptedBytes);
        }
    }

As you can see, I am getting Akavache’s  internal TaskpoolSchedulerin the constructor, like initial stated. Then, for this sample, I am using RSA encryption. The helper methods pretty much implement the same code like in the post about my KeyStore implementation. The only thing to do is to use these methods in the EncryptBlock and DecyrptBlock method implementations, which is done asynchronously via Observable.Start.

iOS

[assembly: Xamarin.Forms.Dependency(typeof(PlatformCustomAkavacheEncryptionProvider))]
namespace XfAkavacheAndI.iOS.PlatformImplementations
{
    public class PlatformCustomAkavacheEncryptionProvider : IEncryptionProvider
    {
        private readonly IScheduler _scheduler;

        private readonly PlatformEncryptionKeyHelper _encryptionKeyHelper;


        private SecKey _privateKey = null;
        private SecKey _publicKey  = null;

        public PlatformCustomAkavacheEncryptionProvider()
        {
            _scheduler = BlobCache.TaskpoolScheduler ??
                         throw new ArgumentNullException(nameof(_scheduler), "Scheduler is null");

            _encryptionKeyHelper = new PlatformEncryptionKeyHelper(BlobCache.ApplicationName.ToLower());
            GetOrCreateKeys();
        }

        public IObservable<byte[]> DecryptBlock(byte[] block)
        {
            if (block == null)
            {
                throw new ArgumentNullException(nameof(block), "block can't be null");
            }

            return Observable.Start(() => Decrypt(block), _scheduler);
        }

        public IObservable<byte[]> EncryptBlock(byte[] block)
        {
            if (block == null)
            {
                throw new ArgumentNullException(nameof(block), "block can't be null");
            }

            return Observable.Start(() => Encrypt(block), _scheduler);
        }


        private void GetOrCreateKeys()
        {
            if (!_encryptionKeyHelper.KeysExist())
                _encryptionKeyHelper.CreateKeyPair();

            _privateKey = _encryptionKeyHelper.GetPrivateKey();
            _publicKey = _encryptionKeyHelper.GetPublicKey();
        }

        private byte[] Encrypt(byte[] rawBytes)
        {
            if (_publicKey == null)
            {
                throw new ArgumentNullException(nameof(_publicKey), "Public key cannot be null");
            }

            var code = _publicKey.Encrypt(SecPadding.PKCS1, rawBytes, out var encryptedBytes);

            return code == SecStatusCode.Success ? encryptedBytes : null;
        }

        private byte[] Decrypt(byte[] encyrptedBytes)
        {
            if (_privateKey == null)
            {
                throw new ArgumentNullException(nameof(_privateKey), "Private key cannot be null");
            }

            var code = _privateKey.Decrypt(SecPadding.PKCS1, encyrptedBytes, out var decryptedBytes);

            return code == SecStatusCode.Success ? decryptedBytes : null;
        }

    }
}

The iOS implementation follows the same schema as the Android implementation. However, iOS uses the KeyChain, which makes the encryption helper methods itself different.

UWP

[assembly: Xamarin.Forms.Dependency(typeof(PlatformCustomAkavacheEncryptionProvider))]
namespace XfAkavacheAndI.UWP.PlatformImplementations
{
    public class PlatformCustomAkavacheEncryptionProvider : IEncryptionProvider
    {
        private readonly IScheduler _scheduler;

        private string _localUserDescriptor = "LOCAL=user";
        private string _localMachineDescriptor = "LOCAL=machine";

        public bool UseForAllUsers { get; set; } = false;

        public PlatformCustomAkavacheEncryptionProvider()
        {
            _scheduler = BlobCache.TaskpoolScheduler ??
                         throw new ArgumentNullException(nameof(_scheduler), "Scheduler is null");
        }

        public IObservable<byte[]> EncryptBlock(byte[] block)
        {
            if (block == null)
            {
                throw new ArgumentNullException(nameof(block), "block can't be null");
            }

            return Observable.Start(() => Encrypt(block).GetAwaiter().GetResult(), _scheduler);
        }

        public IObservable<byte[]> DecryptBlock(byte[] block)
        {
            if (block == null)
            {
                throw new ArgumentNullException(nameof(block), "block can't be null");
            }

            return Observable.Start(() => Decrypt(block).GetAwaiter().GetResult(), _scheduler);
        }


        public async Task<byte[]> Encrypt(byte[] data)
        {
            var provider = new DataProtectionProvider(UseForAllUsers ? _localMachineDescriptor : _localUserDescriptor);

            var contentBuffer = CryptographicBuffer.CreateFromByteArray(data);
            var contentInputStream = new InMemoryRandomAccessStream();
            var protectedContentStream = new InMemoryRandomAccessStream();

            //storing data in the stream
            IOutputStream outputStream = contentInputStream.GetOutputStreamAt(0);
            var dataWriter = new DataWriter(outputStream);
            dataWriter.WriteBuffer(contentBuffer);
            await dataWriter.StoreAsync();
            await dataWriter.FlushAsync();

            //reopening in input mode
            IInputStream encodingInputStream = contentInputStream.GetInputStreamAt(0);

            IOutputStream protectedOutputStream = protectedContentStream.GetOutputStreamAt(0);
            await provider.ProtectStreamAsync(encodingInputStream, protectedOutputStream);
            await protectedOutputStream.FlushAsync();

            //verify that encryption happened
            var inputReader = new DataReader(contentInputStream.GetInputStreamAt(0));
            var protectedReader = new DataReader(protectedContentStream.GetInputStreamAt(0));

            await inputReader.LoadAsync((uint)contentInputStream.Size);
            await protectedReader.LoadAsync((uint)protectedContentStream.Size);

            var inputBuffer = inputReader.ReadBuffer((uint)contentInputStream.Size);
            var protectedBuffer = protectedReader.ReadBuffer((uint)protectedContentStream.Size);

            if (!CryptographicBuffer.Compare(inputBuffer, protectedBuffer))
            {
               return protectedBuffer.ToArray();
            }
            else
            {
                return null;
            }
        }

        public async Task<byte[]> Decrypt(byte[] encryptedBytes)
        {
            var provider = new DataProtectionProvider();

            var encryptedContentBuffer = CryptographicBuffer.CreateFromByteArray(encryptedBytes);
            var contentInputStream = new InMemoryRandomAccessStream();
            var unprotectedContentStream = new InMemoryRandomAccessStream();

            IOutputStream outputStream = contentInputStream.GetOutputStreamAt(0);
            var dataWriter = new DataWriter(outputStream);
            dataWriter.WriteBuffer(encryptedContentBuffer);
            await dataWriter.StoreAsync();
            await dataWriter.FlushAsync();

            IInputStream decodingInputStream = contentInputStream.GetInputStreamAt(0);

            IOutputStream protectedOutputStream = unprotectedContentStream.GetOutputStreamAt(0);
            await provider.UnprotectStreamAsync(decodingInputStream, protectedOutputStream);
            await protectedOutputStream.FlushAsync();

            DataReader reader2 = new DataReader(unprotectedContentStream.GetInputStreamAt(0));
            await reader2.LoadAsync((uint)unprotectedContentStream.Size);
            IBuffer unprotectedBuffer = reader2.ReadBuffer((uint)unprotectedContentStream.Size);

            return unprotectedBuffer.ToArray();
        }
    }   
}

Last but not least, we have also an implementation for Windows applications. It is using the DataProtection API, which does handle all that key stuff and let’s us focus on the encryption itself. As the API is asynchronously, I am using .GetAwaiter().GetResult()Task extensions to make it compatible with Observable.Start.

Conclusion

Using the implementations above paired with our instance helper makes it easy to protect data in our apps. With all those data breach scandals and law changes around, this is one possible way secure way to handle sensitive data, as we do not have hardcoded values or any user interaction involved.

For better understanding of all that code, I made a sample project available that has all the referenced and mentioned classes implemented. Feel free to fork it and play with it (or even give me some feedback). For using the implementations, please refer to my post about common usages I wrote a few days ago. The only difference is that you would use SecretLocalMachineCacheinstead of LocalMachineCache for sensitive data.

As always, I hope this post is helpful for some of you.

Until the next post, happy coding!


P.S. Feel free to download my official app for msicc.net, which – of course – uses the implementations above:
iOS Android Windows 10

Posted by msicc in Android, Dev Stories, iOS, Windows, Xamarin, 3 comments
Xamarin.Forms, Akavache and I: storing, retrieving and deleting data

Xamarin.Forms, Akavache and I: storing, retrieving and deleting data

Caching always has the same job: provide data that is frequently used in very little time. As I mentioned in my first post of this series, Akavache is my first choice because it is fast. It also provides a very easy way to interact with it (once one gets used to Reactive Extensions). The code I am showing here is living in the Forms project, but can also be called from the platform projects thanks to the interface we defined already before.

Enabling async support

First things first: we should write our code asynchronously, that’s why we need to enable async support by adding using System.Reactive.Linq;to the using statements in our class. This one is not so obvious, and I read a lot of questions on the web where this was the simple solution. So now you know, let’s go ahead.

Simple case

The most simple case of storing data is just throwing data with a key into the underlying database:

//getting a reference to the cache instance
var cache = SimpleIoc.Default.GetInstance<IBlobCacheInstanceHelper>().LocalMachineCache;
var dataToSave = "this is a simple string to save into the database";
await cache.InsertObject<string>("YourKeyHere", dataToSave);

Of course, we need a reference to the IBlobCacheinstance we have already in place. I am saving a simple string here for demo purposes, but you can also save more complex types like a list of blog posts into the cache. Akavache uses Json.NET , which will serialize the data into a valid json string that you can be saved. Similarly, it is very easy to get the data deserialized from the database:

var dataFromCache = cache.GetObject<string>("YourKeyHere");

That’s it. For things like storing Boolean values, simple strings (unencrypted), dates etc., this might already be everything you need.

Caching data from the web

Of course it wouldn’t be necessary to implement an advanced library if we would have only this scenario. More often, we are fetching data from the web and need to save it in our apps. There are several reasons to do this, with saving (mobile) data volume and performance being the two major reasons.

Akavache provides a bunch of very useful Extensions. The most prominent one I am using is the GetOrFetchObject<T>method. A typical implementation looks like this:

var postsCache = await cache.GetOrFetchObject<List<BlogPost>>(feedName,
    async () =>
    {
        var newPosts = await _postsHandler.GetPostsAsync(BaseUrl, 20, 20, 1, feedName.ToCategoryId()).ConfigureAwait(false);

        await cache.InsertObject<List<BlogPost>>(feedName, newPostsDto);

        return newPosts;
    });

The GetOrFetchObject<T>method’s minimum parameters are the key of the cache entry and an asynchronous function that shall be executed when there is no data in the cache. In the sample above, it loads the latest 20 posts from a WordPress blog (utilizing my WordPressReader lib) and saves it into the cache instance before returning the downloaded data. The method has an optional parameter of DateTimeOffset, which may be interesting if you need to expire the saved data after some time.

Saving images/documents from the web

If you need to download files, be it images or other documents, from the web, Akavache provides another helper extension:

byte[] bytes = await cache.DownloadUrl("YourFileKeyHere", url);

Personally, I am loading all files with this method, even though there are some special image loading methods available as well (see the readme at Akavache’s repo). The main reason I am doing so is that until now, I always have a platform specific implementation for such cases – mainly due to performance reasons. I one of the following blog posts you will see such an implementation for image caching using a custom renderer on each platform.

Deleting data from the cache

When working with caches, one cannot avoid the situation that data needs to be removed manually from the cache.

//delete a single entry by key:
cache.Invalidate("KeyToDelete");

//delete all entries with the same type:
cache.InvalidateAllObjects<BlogPost>();

//delete all entries
cache.InvalidateAll();

If you want to continue with some other action after deletion completes, you can use the Subscribe method to invoke this action:

cache.InvalidateAll().Subscribe(x => YourMethodToInvoke());

Conclusion

Even though Akavache provides more methods to store and retrieve data, the ones I mentioned above are those that I use frequently and without problems in my Xamarin.Forms applications, while still being able to invoke them in platform specific code as well. If you want to have a look at the other methods that are available, click the link above to the GitHub repo of Akavache. As always, I hope this blog post is helpful for some of you.

Until the next post, happy coding, everyone!

Posted by msicc in Android, Dev Stories, iOS, Windows, Xamarin, 1 comment
Xamarin.Forms, Akavache and I: Initial setup (new series)

Xamarin.Forms, Akavache and I: Initial setup (new series)

Caching is never a trivial task. Sometimes, we can use built-in storages, but more often, these take quite some time when we are storing a large amount of data (eg. large datasets or large json strings). I tried quite a few approaches, including:

  • built-in storage
  • self handled files
  • plugins that use a one or all of the above
  • Akavache (which uses SQLite under the hood)

Why Akavache wins

Well, the major reason is quite easy. It is fast. Really fast. At least compared to the other options. You may not notice the difference until you are using a background task that relies on the cached data or until you try to truly optimize startup performance of your Xamarin Android app. Those two where the reason for me to switch, because once implemented, it does handle both jobs perfectly. Because it is so fast, there is quite an amount of apps that uses it. Bonus: there are a lot of tips on StackOverflow as well as on GitHub, as it is already used by a lot of developers.

Getting your projects ready

Well, as often, it all starts with the installation of NuGet packages. As I am trying to follow good practices wherever I can, I am using .netStandard whenever possible. The latest stable version of Akavache does work partially in .netStandard projects, but I recommend to use the latest alpha (by the time of this post) in your .netStandard project (even if VisualStudio keeps telling you that a pre release dependency is not a good idea). If you are using the package reference in your project files, there might be some additional work to bring everything to build and run smoothly, especially in a Xamarin.Android project.

You mileage may vary, but in my experience, you should install the following dependencies and Akavache separately:

After installing this packages in your Xamarin.Forms and platform projects, we are ready for the next step.

Initializing Akavache

Basically, you should be able to use Akavache in a very simple way, by just defining the application name like this during application initialization:

BlobCache.ApplicationName = "MyAkavachePoweredApp";

You can do this assignment in your platform project as well as in your Xamarin.Forms project, both ways will work. Just remember to do this, as also to get my code working, this is a needed step.

There are static properties  like BlobCache.LocalMachineone can use to cache data. However, once your app will use an advanced library like Akavache, it is very likely that he complexity of your app will force you into a more complex scenario. In my case, the usage of a scheduled job on Android was the reason why I am doing the initialization on my own. The scheduled job starts a process for the application, and the job updates data in the cache that the application uses. There were several cases where the standard initialization did not work, so I decided to make the special case to a standard case. The following code will also work in simple scenarios, but keeps doors open for more complex ones as well. The second reason why I did my own implementation is the MVVM structure of my apps.

IBlobCacheInstanceHelper rules them all

Like often when we want to use platform implementations, all starts with an interface that dictates the functionality. Let’s start with this simple one:

public interface IBlobCacheInstanceHelper
{
    void Init();
    IBlobCache LocalMachineCache { get; set; }
}

We are defining our own IBlobCacheinstance, which we will initialize with the Init() method on each platform. Let’s have a look on the platform implementations:

[assembly: Xamarin.Forms.Dependency(typeof(PlatformBlobCacheInstanceHelper))]
namespace [YOURNAMESPACEHERE]
{
    public class PlatformBlobCacheInstanceHelper : IBlobCacheInstanceHelper
    {
        private IFilesystemProvider _filesystemProvider;

        public PlatformBlobCacheInstanceHelper() { }

        public void Init()
        {
            _filesystemProvider = Locator.Current.GetService<IFilesystemProvider>();
            GetLocalMachineCache();
        }

        public IBlobCache LocalMachineCache { get; set; }

        private void GetLocalMachineCache()
        {

            var localCache = new Lazy<IBlobCache>(() => 
                                                  {
                                                      _filesystemProvider.CreateRecursive(_filesystemProvider.GetDefaultLocalMachineCacheDirectory()).SubscribeOn(BlobCache.TaskpoolScheduler).Wait();
                                                      return new SQLitePersistentBlobCache(Path.Combine(_filesystemProvider.GetDefaultLocalMachineCacheDirectory(), "blobs.db"), BlobCache.TaskpoolScheduler);
                                                  });

            this.LocalMachineCache = localCache.Value;
        }

        //TODO: implement other cache types if necessary at some point
    }
}

Let me explain what this code does.

As SQLite, which is powering Akavache, is file based, we need to provide a file path. The Init() method assigns Akavache’s internal IFileSystemProviderinterface to the internal member. After getting an instance via Splat’s Locator, we can now use it to get the file path and create the .db-file for our local cache. The GetLocalMachineCache()method is basically a copy of Akavache’s internal registration. It lazily creates an instance of BlobCache through the IBlobCacheinterface. The create instance is then passed to the LocalMachineCacheproperty, which we will use later on. Finally, we will be using the DependencyServiceof Xamarin.Forms to get an instance of our platform implementation, which is why we need to define the Dependency attribute as well.

Note: you can name the file whatever you want. If you are already using Akavache and want to change the instance handling, you should keep the original names used by Akavache. This way, your users will not lose any data.

This implementation can be used your Android, iOS and UWP projects within your Xamarin.Forms app. If you are wondering why I do this separately for every platform, you are right. Until now, there is no need to do it that way. The code above would also work solely in your Xamarin.Forms project. Once you are coming to the point where you need encrypted data in your cache, the platform implementations will change on every platform. This will be topic of a future blog post, however.

If you have been reading my series about MVVMLight, you may guess the next step already. This is how I initialize the platform implementation within my ViewModelLocator:

//register:
var cacheInstanceHelper = DependencyService.Get<IBlobCacheInstanceHelper>();
if (!SimpleIoc.Default.IsRegistered<IBlobCacheInstanceHelper>())
     SimpleIoc.Default.Register<IBlobCacheInstanceHelper>(()=> cacheInstanceHelper);

//initialize:
//cacheInstanceHelper.Init();
//or
SimpleIoc.Default.GetInstance<IBlobCacheInstanceHelper>().Init();

So that’s it, we are now ready to use our local cache powered by Akavache within our Xamarin.Forms project. In the next post, we will have a look on how to use akavache for storing and retrieving data.

Until then, happy coding, everyone!

Posted by msicc in Android, Dev Stories, iOS, Windows, Xamarin, 1 comment

Book review: iOS Development with Xamarin Cookbook (Dimitris Tavlikos)

I love to learn and expand my knowledge. Because of this, I was absolutely happy when I was asked for a book review about Dimitris’ iOS book.

The book is a huge collection of iOS recipes using Xamarin. The first three chapters are going deeply into the UI of an iOS application, looking on a lot (almost all) possible aspects of UI elements. What I like very much is that the author shows the code, usually with a step by step guide, and after that delivers a detailed explanation why something works in the way it does.

The next two chapters are all about creating and displaying data, files and sqlite, providing the same experience as the first chapters.

The sixth chapter is all about consuming services, such as web services, REST services or even WCF services (I wasn’t even aware of this being possible). Very good starting point for so many app ideas.

So far, the book shows already a lot of what we can do with Xamarin. But modern apps often contain media content: videos, photos, capturing media – this is what chapter 7 is all about.

Like all modern Smartphone operating systems, iOS provides some methods to let our apps interact with the OS. The 8th chapter is all about those interactions, like contacts, mail and more and has the matching real world scenarios.

The most usable apps use a device’s sensors, touch and gestures. Of course, with Apple being the leader in this space for a long time (we just need to be fair in this point), iOS has a lot of APIs for these. Chapter nine has some good recipes to help us with improving our app’s UX.

If your app needs location services and maps, chapter 10 is your friend. It shows you how to interact with Apple’s map services, add annotations and a lot more.

Users love when apps have some nice animations when something changes in an app. iOS provides a lot of options, and chapter 11 explains a lot about animations and drawing methods.

One of the most important parts when developing an app is lifecycle handling. As with any other OS, also iOS has its specific methods to handle the lifecycle. Background operations are part of this handling. In chapter 12, Dimitri tells us a lot about handling the states of an app as well as background operations.

Chapter 13 consists of tips and recipes for localization of an iOS app.

One of the most important steps when creating an iOS app is deploying the app. Apps should of course be tested on real devices, and this what chapter 14 is about – but not only. Also the required steps to prepare and app for submission as well as the submission to the store are explained.

The final chapter contains some additional recipes that can make your app more valuable like content sharing or text-to-speech.

Conclusion

I only began with Xamarin.iOS a few month ago. This book provides a great insight into development for iOS using the Xamarin IDE. As I said already, I like the approach of showing code first and then explaining what it does exactly and provide additional info if suitable. This book is absolutely worth every single cent if you want to start with iOS and Xamarin.

If you’re interested in the book, you’re just a click away: http://bit.ly/1tnxmGX

Note: This post was completely written on my phone. If you find typos, you can keep them ;-).

Posted by msicc in Dev Stories, iOS, Xamarin, 0 comments