Friday, August 23, 2019

Publishing an Android app in China

Thinking about publishing your Android app in China? Just add half a dozen of questionable permissions and you're good to go!

For the last five months I've been trying to work with a service called AppTutti to publish my Android app in China. Due to some very unique approach of the Chinese government, developers are not (easily) able to publish their app for the Chinese market themselves. Instead you can work with third-party services to get your app published. I chose AppTutti for the reason that it seemed more or less legit and offered to do at least the basics without paying any money upfront (instead they would take a cut in revenue). That approach seemed appropriate for my situation, as I was not sure if there is a big market for my niche in China anyway.

The good

The good news is that AppTutti translated my app to Chinese for no charge. As far as I can tell the quality of the translation is decent.

The bad

As mentioned before, the basic service provided is free of charge due to revenue sharing. In order to generate any revenue at all, you have to add monetization libraries to your app that are specialized for the Chinese market. So far so good, but you don't receive any information about what those libraries do exactly. Worse, those libraries seem to be a data mining hell come true:
  • WRITE_EXTERNAL_STORAGE to read all your local data? Hm...
  • ACCESS_FINE_LOCATION to get your exact position? Of course!
  • GET_TASKS to get other apps running on your phone? Hell yeah.
There's also some code that is meant to download *something* in the background. You can find all necessary changes here:

Despite a good amount of skepticism I went ahead to see what comes next. Integration was fairly easy since most of the code just had to be copy-pasted into my project. However, for some reason the integration did not work despite doing exactly what the provided documentation (given in form of a docx) suggested. Overall it took their support 1-2 months to figure out what went wrong. Eventually I did not have to make any changes on my side, so it must have been something on their side.

Finally, Chinese market here we come!? No. "Your App File has been found at local Chinese Stores! Please provide your Authorisation Letter and ORDER our IP Certificate Services to upgrade to RSPP Plus service for Reclaiming Process (from the stores).".

... 🤔

Turns out a very old version of my app was published without authorization before. The only way to reclaim ownership was to pay 200$ to AppTutti so they can start the process to reclaim the app. Admittedly, 200$ is not so bad, but it has a bitter taste if you ask me: free service, but since some companies seem to publish apps in China without permission you have to pay anyway. :)

If one window closes another one opens - or so - and I figured it's not worth spending any more time on publishing my app in China anyway. The rogue copy of my app that is already available there only accumulated a few thousand installs so far! Not worth the hours I have wasted on this odyssey so far and not worth another hour for sure.

TLDR; check download stats of similar apps before attempting to publish your app in China.

Tuesday, August 13, 2019

Measuring the success of your app ad campaign using Firebase

We recently launched an iOS version of an app that was fairly popular on Android due to organic growth (that app being the first of its kind a few years ago). Now - better late than never - we figured it's worth a try on iOS too.

Problem is that there are a few similar apps available on iOS by now, so organic growth - while still working - is too slow for us to see if the app is worth spending more effort on. That's where we started looking at paid ads.

For starters, there are two main providers for promoting your app: Google (AdSense) and Apple Search Ads. Not having done this before we simply started throwing money at the problem and started getting conversions at 30 cent each (that's Euro). Of course, those users were mostly based in countries like India, Brazil, etc. Targeting countries like US or Germany would cost much more. However, our app is not location-dependent so whatever kind of user we get is fine for us :)

So users started to trickle in - at least Google and Apple told us so and happily requested more and more money from us. However, we had no clue what those users were doing in our app, i.e. did they use the core functionality? And more importantly, did they generate revenue via In-App purchases or clicking ads displayed inside the app?

Please welcome our next guest, Firebase Analytics: by default, you do not see how a user was acquired in Firebase for an iOS app (you do on Android). For some reason this is nowhere promoted as prominently as it should, but in order to attribute the source of your users properly on iOS you have to add the iAd framework to your app. The documentation mentions it here shortly among a bunch of other text: "You must add the iAd framework to the Xcode project file for your app in order to track Apple Search Ads." Doing so also changes the answers you have to provide during app submission in the App Store in regards to user privacy.

But that leads us to our next problem: Firebase can do so much, but some things it only does *after* you specifically told it to do so. By now you might have the information available how each user was acquired, however, you are not able to see analytics specifically for that group of users. Usually you would use an "audience" in Firebase to show statistics only for a specific group of users, but audiences only grow after creating them. Meaning: although you might see in the Firebase dashboard that you acquired 1000 users via Google ads, you have no clue what these users did after getting acquired. So go ahead and make sure you create a few audiences: I created one for users acquired via Apple and Google specifically, and another one for all paid acquisitions. That way you can quickly drill down if one of those two providers generates more active or "useful" users for you.

For us it turned out that for every thousand dollar or so spent on ads we earn 10 dollars back via ads shown in our app - oh well!

Thursday, May 2, 2019

How to include a CMake project in a Xcode project

For an iOS app I'm working on I needed to include a C++ project, have it build according to the current scheme (debug / release) and be able to debug it. I don't know much about C++, but I know that CMake is a popular build tool and for the project I was trying to import it was the preferred way to build it anyway. Turns out there are not a lot of people doing this as most of the information you'll find is either outdated (no, I'm not using Xcode 5 anymore) or requires a lot of background knowledge about both Xcode and CMake - which I'm both lacking.

To save myself and others some time in the future, I'd like to walk through an example of how you can build a CMake project for iOS in Xcode:

1. Create iOS Xcode project

Doh! Assuming you don't have one already. There is nothing specific to do here.

2. Clone the C++ project you'd like to include in your project

I'll be using glog for the purpose of this tutorial. Please note that some projects do not build out of the box on iOS for different reasons. I have created a fork of some projects that I needed on iOS, maybe you are lucky and the one you are looking for is among them:

3. CMake, meet Xcode.

First you need to clone a toolchain for iOS to teach CMake how to compile for iOS. Please note: I was not able to use the latest version at the time of writing this article. Instead, I used a specific commit.
Next, run the following one-liner inside your project folder in the terminal:
mkdir build/ && cd build/ && cmake -G "Xcode" -DCMAKE_TOOLCHAIN_FILE=../ios-cmake/ios.toolchain.cmake ../glog.ios/ && cd ../
I usually place that in a file called as you will need to rerun this whenever the C++ project changes (i.e. new files added).

The above commands will take a while to complete, but at the end you should see something along the lines of:
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/tom/workspace/cmake-sample.ios/build
Please note that you might have to install CMake first and the Xcode command line tools.

4. Xcode, meet CMake!

If the previous step succeeded and did not report any errors in the last three lines you are already halfway there! Hang tight. You should see a new .xcodeproj-file now in a folder called "build" if you look at your project using the file explorer. In my case it's called glog.xcodeproj. Drag this file into the file panel (on the left) of your Xcode project to link them. It doesn't really matter where exactly you drag it to as the file structure of a Xcode project is purely virtual.
Files created by CMake after running the above commands
This is what your Xcode project might look like after dragging the project created by CMake into it

A few more things left to click-click configure in Xcode: go to your project file and under "General" click the Add-button for "Linked Frameworks and Libraries". In the dialog that pops up you should see your C++ project show up. Add it!
Linking the framework generated by CMake

In the same file under "Build Phases" add your C++ project to "Target dependencies" (note: there are lots of targets showing up here. It's usually the one with a grey house-like icon and the exact same name as your C++ project). If you don't do this, Xcode won't automatically rebuild your C++ project if necessary.
Adding the framework as a build dependency

Last but definitely not least you have to tell Xcode where to find the headers of the C++ project. Again in the project file, under "Build Settings" search for a setting called "Header Search Paths", add the path to the header files and make it "recursive" (should work with "non-recursive" too if you put the correct folder there). One would expect this setting to change automatically, but unfortunately you'll have to do this for each C++ dependency you add.
Specifying path to lo

5. Wrap C++ with Objective-C to use it in Swift

As of now Swift does not support calling C++ directly. Instead you have to call Objective-C, which can call C++. There's a lot of stuff about this online already, so give it a go. Feel free to leave a comment if you can't get it to work.

(A few tips and tricks to fix common quirks)

  • If your project doesn't build and you are completely out of ideas what went wrong, try removing the build-folder, rerunning the commands to create it and restart Xcode (yes, that does help sometimes).
  • Sometimes, cleaning the project twice makes a difference. I swear I'm not kidding.
  • All of this is very unstable in my experience, so it could be that it stops working with the next Mac / Xcode update. CMake versions do matter too (I'm using 3.14.0).

Wednesday, May 1, 2019

Ways to legitimately improve your rating on Google Play Store

User ratings on Google Play Store are... hard to read sometimes. It really hurts to see how people seemingly did not care to understand what your app actually does and then complain about it by leaving a bad rating. However, there is a big opportunity for you as developer to turn some of those heart-breaking-swear-ratings into something that does not drag down your average rating shown to future users.

By replying to reviews in a very polite way you can turn at least some of those - 50% in my case into something that is at least a little more positive. I had cases where people (accidentally?) rated the app with one star, but turned it into the full five stars after I had replied to their rating.

Again, it's important to not let your feelings take over and reply in a mean or emotional way. Stay professional, stay classy 😎. My opening phrase is usually "Thanks for your feedback" - even if the feedback was non-existent or not constructive at all. This is how you make some people aware of how stupid their review was in the first place. Next, I ask along the lines of "what exactly went wrong" or "how can we improve the app" to get a discussion going. Last but not least I often end with "contact me via email at [email protected]" in order to make it obvious for less technically versed people how to continue the discussion (if I'm not mistaken, there is a big button saying "REPLY" in the email that Google is sending to users, but still...).

Long story short: Keep an eye on your reviews, reply to them in a polite and professional manner and give the user concrete actions to take.

If your problem is not receiving enough ratings in the first place, I highly suggest to include a dialog that asks users to review your app. If you do this in a way that does not annoy users you'll see many more reviews for your app soon. In my case the conversion rate is very low - only 2% of users asked to leave a rating agree to do so.

Thursday, April 25, 2019

Create a simple landing page for your app in just a few clicks - for free

After redesigning one of my apps from scratch recently I decided it’s time to upgrade the accompanying website for it too. Previously it was hosted on a hidden subpage of my Blogger blog and I hardly ever updated it. Still, it received quite some traffic via the Google Play Store…

So a new website, right? Shouldn’t be that hard since I was looking for something simple: title, a short description, the logo and a few screenshots. Oh, a link to the app stores would be nice too of course. However I could not find any useful template / services to create a minimalist app landing page without designing it myself (my CSS is rusty to say the least). There are a gazillion templates that promise to deliver app landing pages, but most of them were bloated with unnecessary features or design elements. Shortly before giving up - and considering to refresh my CSS skills - I found LaunchKit. One of their tools allows you to create a website from the information available on the App Store (screenshot, logo, description, etc) - for free! Unfortunately they shut down their services after getting acquired by Google, but all of their code is open-source now and you can host it yourself:

Here’s a sneak peek of what I created:

Not too shabby, right? I even added some custom JavaScript to show a live chat for customers to ask questions (not shown on the screenshot). Here is the live version of it:

There is a few different ways how you can get something similar for your app too now that LaunchKit is not live anymore:

1. Host it yourself

LaunchKit released everything you need to host their services yourself. However, that means you’ll need a server to run it on - which costs a few bucks per month. Worth it, but definitely not the easiest solution if you keep in mind that you have to fix it yourself if the server goes down one night. Also, scalability? Nope. If you go down this route I would suggest to take a look at a fork that makes use of Docker:

2. Have someone else host it for you

Some people were smart enough to take the code that was published and host it so that others can use it. The only one I found so far is - it offers most of the features for free but some of them are paid (at an unreasonably high monthly rate).

3. Generate the website and export it as a static website

If you put in some extra effort you get the best of both worlds: create a website for free, host it for free and also have someone else worry about your servers. PLUS this scales infinitely (theoretically). And who else to use for hosting than good old Google? More specifically, we’ll use Firebase Hosting.

First generate a website using either a hosted or self-hosted version (localhost is enough) of LaunchKit. Afterwards export it as a static website using some bash magic:

Last but not least you upload the “exported” website to Firebase by leveraging their Hosting service:

I won’t go into details here, but the steps provided above give you an idea of how the process works. The website you end up with is scalable (please mind the limitations of the free Firebase plan), fast, customizable and even supports connecting to a custom domain. If you link this up with Cloudflare now you are pretty good to go. One less thing to worry about if your app becomes the next big thing over night. ;)