Xamarin Binding: Survey Monkey SDKs

Xamarin Binding: Survey Monkey SDKs

Recently I helped someone who was having problems binding Android and iOS native SDKs to Xamarin. Xamarin binding is a complicated subject and though Xamarin has documentation, there are some sticking points that are not quite obvious.

The two Survey Monkey SDKs can be found here and here:

The github project with my binding solution

Survey Monkey Android Binding Library

Binding native Android to Xamarin is done though through creating a special Android Binding project. In Visual Studio 2015 this project type can be found under installed templates under Android:

Once the project is created, it will contain a couple special folders: Jars and Transforms. Jars contains any jar (or aar) files that need binding. Additionally you need to add any jars it references. If they are already bound like Android Support v4, you can just add the Nuget package.

Transforms contains a set of xml files that use what appears to be xpath to select nodes and then perform tweaks.

The transformation process works like so:

1. Reflect over the jar and aar classes.

2. Produce an internal xml file detailing the classes, properties and methods. After building the project once you should be able to see an example of this at <project dir>/obj/Debug/api.xml.

3. The files in the transforms directories apply transforms to this api.xml file, which is then used to generate the C# wrapper code.

In the case of Survey Monkey after adding a nuget reference to Android Support v4, there were three errors:
Inspecting the code GetRespondentTaskLoader shows this method signature that’s causing the error.
GetRespondentTaskLoader derives from AsyncTaskLoader. AsyncTaskLoader’s LoadInBackground returns a Java Object. In C# an overridden method can’t change the return type. The fix is to update the transform code to change the return type. This is done in Metadata.xml in the Transforms directory with this line.

This tells the Android binding project to change the return type of the loadInBackground method on the GetRespondentTaskLoader class to a Java.Lang.Object.

The other error we encountered was exactly the same, but for another class. The fix for it is the same:

The last error is not fixed with the transform. The generated class is a partial class. We can fix this error by supplying another part of the partial class that contains the LoadInBackground method to fulfill the implementation:
With those changes, the build complete with 12 warnings and 0 errors:

 

12 Warning(s)

0 Error(s)

Time Elapsed 00:00:04.4374710

———————- Done ———————-

Build: 0 errors, 12 warnings

 

The Android Binding Project is ready. See the github for the example of how to use it. It is exactly the same as the native Android Survey Monkey SDK.

It is worth noting that it may not be necessary to access the 3 task classes RetrieveSPageTask, GetRespondentTaskLoader, and GetRespondentTokenTaskLoader directly. We could possibly just remove the class definitions using the Metadata.xml transforms file. I opted to leave them in case there is a use for them.

The latest version of Xamarin, as of this writing, will not run correctly out of the box on some x86 emulators. This is easily fixed by adding x86 or x86_64 to the ABIs for the Advanced tab under Android Build:

Survey Monkey iOS Binding Library

An iOS binding library is created through a completely different means. Rather than reflecting over the binaries, a command line tool named Objective Sharpie is used to scan header files and create decorated C# classes, similar to P/Invoke.

The default framework option with sharpie didn’t work. I think that is because Survey Monkey’s iOS SDK framework doesn’t follow the right naming convention. Fortunately it is possible to reference the header files and generate the stubs:

Which responds with:

 

Parsing 2 header files…

Binding…

[write] ApiDefinitions.cs

[write] StructsAndEnums.cs

Binding Analysis:

Automated binding is complete, but there are a few APIs which have been flagged with [Verify] attributes. While the entire binding should be audited for best API design practices, look more closely at APIs with the following Verify attribute hints:

MethodToProperty (1 instance): An Objective-C method was bound as a C# property due to convention such as taking no parameters and returning a value (non-void return). Often methods like these should be  bound as properties to surface a nicer API, but sometimes false-positives can occur and the binding should actually be a method.

StronglyTypedNSArray (2 instances): A native NSArray* was bound as NSObject[]. It might be possible to more strongly type the array in the binding based on expectations set through API documentation (e.g. comments in the header file) or by examining the array contents through testing. For example, an NSArray* containing only NSNumber* instances can be bound as NSNumber[]  instead of NSObject[].

Once you have verified a Verify attribute, you should remove it from the binding source code. The presence of Verify attributes intentionally cause build failures.

For more information about the Verify attribute hints above, consult the Objective Sharpie documentation by running ‘sharpie docs’ or visiting the following URL:

http://xmn.io/sharpie-docs

Submitting usage data to Xamarin…

Submitted – thank you for helping to improve Objective Sharpie!

Done.

 

It gives a couple warnings, which in this case can be ignored. The latter of the two StronglyTypedNSArray just means that we may have to cast an NSObject to a particular type before we can use it.

An important thing to notice is the Verify attribute. Sharpie will put this in the generated code, but will not compile. This will force you to look at the line and make sure the generated code is correct. In this case everything was ok, so I removed the [Verify] from the code.

The files generated by sharpie aren’t the same name as the default files generated when I created the binding project. If you look at the properties for the two .cs files created in the project you’ll see this. (From Xamarin Studio)

If you decide to just add the files, make sure the build command is set to ObjcBindingApiDefinition.

I simply pasted the contents into the existing files.
Fixing an objective sharpie error with enums. The code for StructsAndEnums did not compile:

If you know this will only run on 64 bit devices, then the quick fix is to change the type to a ulong:
The last thing I had to do was add some code to make sure it loaded the binary file. This is done in the SurveyMonkey.framework.linkwith.cs file:

Then the binding project is ready to go. Just add the binding library project to the Xamarin iOS project and you can start using.

Next Steps

There are a few things I will add in the future:

1. Create a nuget project. Everyone loves nuget. It would make it easier to use without having to muck around with projects.

2. Create a cross-platform Xamarin Forms plugin so that Survey Monkey can be used from a Forms App.

3. Unify the return data type between iOS and Android. Right now Android returns a json string to the calling activity. iOS returns an object hierarchy. Before a Xamarin Forms plugin can work in a useful way, the way the data is consumed needs to be the same.

Let me know if you have any questions, comments or inquiries at curtis@saltydogtechnology.com
User Experience:  Inviting users to your Android or iOS app Part 2

User Experience: Inviting users to your Android or iOS app Part 2

Previously in (User Experience:  Inviting users to your Android or iOS app Part 1) we explored a way of inviting and onboarding new users to an application. It worked but had some UX issues related to it.

The solution relied on putting deep links in SMS messages and then sending them to the recipient. Here are the 4 use cases again.

A ) Bulk invites, or outside of the app, inviting someone who may or may not have the app:

1.The user’s information, name, phone number and possibly custom message, is captured and inserted into the database along with a unique identifier.

2.The website then generates an SMS with an url that contains the unique identifier as a parameter.

3.The SMS is sent. That’s it!

B ) Someone uses the app to invite someone else who may or may not have the app:

1. The user selects a name from their contact list.

2. Optionally, they write a message for the receiving user.

3. The app then makes an authenticated rest call to the server, where the information is inserted into the database.

4. The rest call returns the unique id that was generated.

5. The app formats an url using the unique id, and then sends in in an SMS message to the recipient. That’s it!

C ) Someone receives an invite who does not have the app installed:

1. The user receives an SMS with a web url in it. The url has a unique id appended as a query parameter.

2. The SMS has a short message, asking them to click on the url for more information.

3. The user clicks on the url.

5. Since they don’t have the app installed the url starts the default browser and the visit the web page.

6. The web server, looks up the unique id from 1), and retrieves the details of the invite such as the recipient’s name, or even a custom message. It also includes a link to Apple App Store or Google Play Market for downloading the appropriate app for their operating system.

D ) Someone receives an invite who does have the app installed:

1. The user receives an SMS with a web url in it. The url has a unique id appended as a query parameter.

2. The SMS has a short message, asking them to click on the url for more information.

3. The user clicks on the url.

4. Since they have the app installed, and deep linking is enabled, the app is opened.

5. The app fetches the parameter from the url the performs a REST call to the server and looks up the details for the invite.

6. The app displays a screen with the details.

 

A and B dovetail into C and D. The problem is that if they have the app installed the will still be receiving SMS messages and the user has to click on it. That is at least two clicks just to get into the app.  1) Select the notification to open the SMS app. 2) Select the URL to open up our app. Depending on the operating system and version, there might be another step in there to verify that you want to open up the app. That might end up being really annoying! Fortunately, there is a better cleaner way to handle this. Basically, we want the app to automatically open when it receives an event notification.

We can easily do this using the push notification facilities in iOS and Android. The trick is knowing if we should send an SMS or send a push notification. We have part of the equation solved already. We know the phone number of the person when they originally were sent the invite. We can use that to create a user account that just hasn’t been tied to the information for push notifications. When the recipient installs the app we can associate the phone number with that app installation. This way in the future when we send an event to that phone number, we can check if it is associated with an installation. If it is, we send the push notification instead of an SMS. So we’re almost there. The only question is how do we associate the phone number to the installation. We can ask the phone number, or we can request permission to read it. However, there is a slicker way to handle it.

The Magic

iOS 9 comes with some new abilities. The new Safari View Controller can share the same cookie space as the main Safari app. This means you can verify if the install action originated from a click in safari. Google Play has an INSTALL_REFERRER Broadcast. Basically, you just need to append a custom referrer to the query parameter to the end of the Goole Play Store URL when you send the user there. Google Play will fire off a broadcast intent to your app that includes the referrer. Here is an updated use cases for the first time the app is installed

Someone receives an invite who does not have the app installed:

1. The user receives an SMS with a web url in it. The url has a unique id appended as a query parameter.

2. The SMS has a short message, asking them to click on the url for more information.

3. The user clicks on the url.

4. Since they don’t have the app installed the url starts the default browser and the visit the web page.

5. The web server, looks up the unique id from 1), and retrieves the details of the invite such as the recipient’s name, or even a custom message. It also includes a link to Apple App Store or Google Play Market for downloading the appropriate app for their operating system.

6. The web page sets a cookie (for the iOS case) and the link to the Google Play Market appends a referrer that includes the unique id created in 1.

7. The user installs the app.

8. (iOS) When the app is run the first time, it loads the Safari View Controller, then looks for that cookie. If it is there then the user installed via visiting the web page and we can look up the invite and associate the phone number with the user record via a REST call.

9. (Android) When the app is run the first time, it waits for the broadcast from the Google Market App. If it is received, then the user installed via visiting the web page and we can look up the invite and associate the phone number with the user record via a REST call.

 

Now you have a very slick invite and onboarding process!

User Experience:  Inviting users to your Android or iOS app Part 1

User Experience: Inviting users to your Android or iOS app Part 1

Often the use cases and implementation around inter-app invites is overlooked. Handling it easily and well can be a bit subtle.

You have to deal with the situation if an intended recipient has the app installed. If they do not, you will want to do one thing. If they do have the app installed, then the invite notification should be handled in app. It becomes a bit of a chicken and egg problem — how does someone receive an invite if they don’t have the app installed?

Fortunately, there is a clever thing you can do for invites that handles both cases. It leverages a feature that both iOS and Android have: The ability to map an url to an app, so when the user taps that URL in email or sms (or a web-page) it starts the app if it is installed. This is called Deep Linking.

In Android this is done using deep linking and an intent filter in the manifest:

When Android detects that url, it will open up this activity. More details here: https://developer.android.com/training/app-indexing/deep-linking.html

In iOS 9+, this is done with universal links. Basically you include an apple-app-site-association file that describes what urls your app should open. For example:

Note: don’t append “.json” to the file.

See more details here.

Let’s talk about the use cases in a bit more detail and then walk through how these are solved using the feature I described above.

 

There are 4 use cases you need to consider:

1. Someone receives an invite who does not have the app installed

2. Someone receives an invite who does have the app installed.

3. Bulk invites, or outside of the app, inviting someone who may or may not have the app.

4. Someone uses the app to invite someone else who may or may not have the app.

 

The first are almost the same. It is just a matter where the SMS is sent from.

Bulk invites, or outside of the app, inviting someone who may or may not have the app:

1. The user’s information, name, phone number and possibly custom message, is captured and inserted into the database along with a unique identifier.

2. The website then generates an SMS with an url that contains the unique identifier as a parameter.

3. The SMS is sent. That’s it!

Someone uses the app to invite someone else who may or may not have the app:

1. The user selects a name from their contact list.

2. Optionally, they write a message for the receiving user.

3. The app then makes an authenticated rest call to the server, where the information is inserted into the database.

4. The rest call returns the unique id that was generated.

5. The app formats an url using the unique id, and then sends in in an SMS message to the recipient. That’s it!

 

A couple notes about this. In step 3, want the user to be authenticated so that someone couldn’t call the REST api directly and use it to spam. In step 5, the app sends the SMS. The server could send it, and in the future you may want it to, but if the app sends it then the user pays the SMS costs.

Someone receives an invite who does not have the app installed:

1. The user receives an SMS with a web url in it. The url has a unique id appended as a query parameter.

2. The SMS has a short message, asking them to click on the url for more information.

3. The user clicks on the url.

4. Since they don’t have the app installed the url starts the default browser and the visit the web page.

5. The web server, looks up the unique id, and retrieves the details of the invite such as the recipient’s name, or even a custom message. It also includes a link to Apple App Store or Google Play Market for downloading the appropriate app for their operating system.

Someone receives an invite who does have the app installed:

1. The user receives an SMS with a web url in it. The url has a unique id appended as a query parameter.

2. The SMS has a short message, asking them to click on the url for more information.

3. The user clicks on the url.

4. Since they have the app installed, and deep linking is enabled, the app is opened.

5. The app fetches the parameter from the url the performs a REST call to the server and looks up the details for the invite.

6. The app displays a screen with the details.

 

So there we have a basic way of sending invites users whether or not they have the app installed. However, if they have the app installed the will still be receiving SMS messages and the user has to click on it. That is at least two clicks just to get into the app.

1. Select the notification to open the SMS app.

2. Select the URL to open up our app. Depending on the operating system and version, there might be another step in there to verify that you want to open up the app.

 

It works though it is a bit clunky. Every time an invite is sent, the user will have to click on the link in the SMS. In a future blog post, we’ll see how to optimize this and make it better.