Android: Experience from just one project

I've been doing a lot of android development recently so I thought I could just share some experience with you from one of the latest projects.
Even though I kinda solved all the issues, for some problems I'm still looking for a good solution.

Here's the list of issues I had:


ContentProviders have to be unique on an android device

I haven't been working with ContentProviders and product flavors at the same time yet so this was new to me. It makes totally sense, because ContentProviders can be exported and used by other apps like the ContactsProvider or CalendarProvider of android already do.

The problem starts when you want to be able to install multiple product flavors of your app on the same device. As mentioned, ContentProviders need to be unique, so you have to change the name for every flavor you have. This is done be using a different applicationId for each flavor. The AndroidManifest will loom something like this for a Provider:

<provider android:name=".my.app.DemoProvider" android:authorities="${applicationId}.demo" />

That would be it, if you would not have the need to use the authority in you code which you usually do, so you need to add the variables to the static BuildConfig file via build.gradle for the specific flavor:

...
productFlavors {
  flavOne {
        applicationId = appId + ".flav_one"
        versionName = vName + "-F1"

        buildConfigField "String", "A_DEMO", "\"${applicationId}.demo\""
   }
}
...

in your code, you can now use this authority by using BuildConfig.A_DEMO.
If you want to use the authority as a string resource in some xml-file, you can add

resValue "string", "a_demo", "${applicationId}.demo"

in the flavor part and use it with @string/a_demo.

Now imagine having lets say five ContentProviders and 4 flavors. That's where the glich begins because right now I don't have a solution better than just copying all buildConfigField and resValue lines into each flavor. I tried writing a method that gets the applicationId as parameter, but the method is not known to gradle when building.

Also, I haven't found a solution for installing a release AND a debug build on the same device using the applicationIdSuffix for the buildType like this.

buildTypes {
    release {
        minifyEnabled false
        debuggable false
        signingConfig signingConfigs.release
    }
    debug {
        debuggable true
        minifyEnabled false
        applicationIdSuffix ".debug"
        versionNameSuffix '-DEBUG'
    }
}

The buildTypes and the productFlavor parts do not work together when using the above mentioned code for changing the provider authorities. I don't get a handle on the applicationId when the buildTypes are processed so copying the provider code into the buildTypes part also doesn't help.

If you know any solution to that please get in touch (Twitter) and let me know.


Google Cloud Messaging (GCM) needs a google-services.json file in app-root folder

If you want to add Push-Notifications to your app, you need to register your app for Google Cloud Messaging. You can do this here and get a configuration file.
That's the easy part. Just enter your app and package name and choose cloud messaging as service. Then download the config file and also remember the generated Sender ID and Server API Key to authenticate your app for the push service.

To use GCM with AndroidStudio, you also need to add the google-services plugin to gradle. Your projects build.gradle file need to add classpath 'com.google.gms:google-services:1.5.0-beta2' (currently there's already 2.0.0-beta6) under dependencies and your apps build.gradle file should look like this:

apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'

Now, as the title says, the config file needs to be placed into the root folder. If there's no config-file, the GCM gradle plugin will throw an error complaining about the missing config file. In the current beta version, the plugin does not copy the config-files from your flavor-folders to the app-root which is really annoying. The issues #54 has quite some history and the current alpha version should fix the problem of copying the config file from build flavors, but I somehow couldn't get it working (and I'm also not so happy to use alpha versions in production environments)

I used some code found on stackoverfow to copy the config-file from the flavor to the app-root before the google-service plugin runs. build.gradle looks like this:

...
afterEvaluate {
  // add missing flavors if needed
  processStagingDebugGoogleServices.dependsOn switchToDebug
  processProductionReleaseGoogleServices.dependsOn switchToProduction
}
...

def appModuleRootFolder = '.'
def srcDir = 'src'
def googleServicesJson = 'google-services.json'

task switchToDebug(type: Copy) {
  def buildFlav = 'staging'
  from "${srcDir}/${buildFlav}"
  include "$googleServicesJson"
  into "$appModuleRootFolder"
}

task switchToProduction(type: Copy) {
  def buildFlav = 'production'
  from "${srcDir}/${buildFlav}/"
  include "$googleServicesJson"
  into "$appModuleRootFolder"
}

The good thing to mention is that google does not generate two separat files when using e.g. the .debug suffix for buildTypes. If you add your release package name when generating your configuration file and later on do the same process for the .debug package name, you get one google-services.json file with both entries. That way you have one config-file for all buildTypes for one flavor.


How to copy events into the CalendarProvider

This one is short, but took some time to figure out because it is not documented :(

Copying an appointment to the device calendar looks like this (the calendarId has been selected through a list that I offer to the user to select the calendar he wants to use, appointmentItem is my own appointment-type):

ContentResolver cr = context.getContentResolver();
ContentValues values = new ContentValues();

Uri calendarUri = null;
final String tZone = TimeZone.getDefault().getID();

values.put(CalendarContract.Events.DTSTART, appointmentItem.getStartTime().getTimeInMillis());
values.put(CalendarContract.Events.DTEND, appointmentItem.getEndTime().getTimeInMillis());
values.put(CalendarContract.Events.TITLE, appointmentItem.getTitle());
values.put(CalendarContract.Events.DESCRIPTION, appointmentItem.getTherapistsLabel());
values.put(CalendarContract.Events.EVENT_LOCATION, appointmentItem.getLocation());
values.put(CalendarContract.Events.CALENDAR_ID, calendarId);
values.put(CalendarContract.Events.EVENT_TIMEZONE, tZone);
values.put(CalendarContract.Events.EVENT_END_TIMEZONE, tZone);


calendarUri = cr.insert(CalendarContract.Events.CONTENT_URI, values);
...

so

final String tZone = TimeZone.getDefault().getID();

values.put(CalendarContract.Events.EVENT_TIMEZONE, tZone);
values.put(CalendarContract.Events.EVENT_END_TIMEZONE, tZone);

is the important part, put that's exactly what the CalendarContract needs to get. It's a String, so it could have been everything. Even reading an appointment from the device calendar just returns the localized timezone String (which didn't help) and TimeZone.getDefault().getID() is an awkward method name for what will be returned.
I hope you saved some time having this info ;)


YouTubeAndroidPlayerApi is not available via maven repository

This is just some 'I want to complain about it' part. Google has been pushing Android Studio and the usage of gradle so hard and stopped the support for Eclipse, and on the other hand they still don't offer one of there own APIs via repository.

There's a bug in the YouTubeAndroidPlayerApi v1.0.0 crashing with an Exception only on Android L devices. That's how I ran into the 'no repository' issue. It has been fixed in 1.2.1. the current Version is 1.2.2 and you can get it here