Saturday, November 5, 2016

Eclipse: Integrate Firebase Analytics into your Android app

With official support moving away from Eclipse towards Android Studio and Gradle, new things like Firebase Analytics have become unavailable to developers wanting to continue using Eclipse for their app development. In this post, I will go through the libraries and the configuration required so you can integrate it in your Eclipse project.


Firebase Analytics libraries

 

dandar3/android-google-firebase-README project (GitHub) gives you an overview of the available Firebase libraires ready to use with your Eclipse project. Have a look at the two YouTube videos at the end covering the installation of the Subversive plugin and the steps to easily import an entire project set like Firebase Analytics into your workspace.

You will observe compile errors like Tag ... attribute name has invalid character '$' after importing libraries like android-google-firebase-iid or android-google-firebase-common. To resolve the issue you will have to manually replace ${applicationId} with your application id (Java package) in all AndroidManifest.xml files (use CTRL+H > File Search >  Replace...


Importing google-services.json

 

There is no Eclipse plugin to do the work that the Google Services Gradle plugin does for Android Studio (I have started working on one, but I can't say when this will be available), so you would have to read the Firebase documentation and follow the Processing the JSON File information to manually create your own values.xml.

If you're having trouble with getting this right you could use Android Studio to import firebase/quickstart-android GitHub project, deploy your google-services.json into it and then copy the generated values.xml out.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="default_web_client_id" translatable="false">{YOUR_CLIENT}/oauth_client/[first client_type 3]</string>
    <string name="gcm_defaultSenderId"   translatable="false">project_info/project_number</string>
    <string name="firebase_database_url" translatable="false">project_info/firebase_url</string>
    <string name="google_app_id"         translatable="false">{YOUR_CLIENT}/client_info/mobilesdk_app_id</string>
    <string name="google_api_key"        translatable="false">{YOUR_CLIENT}/services/api_key/current_key</string>
    <string name="google_storage_bucket" translatable="false">project_info/storage_bucket</string>
</resources>


AndroidManifest.xml changes


This is the undocumented part that can only be observed by looking at the AndroidManifest.xml generated by Google Services Gradle plugin (app\intermediates\manifests\full\debug\AndroidManifest.xml).
<?xml version="1.0" encoding="utf-8"?>
<manifest>
    [...]

    <!-- Required permission for App measurement to run. -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <permission
        android:name="${applicationId}.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />
    <uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" /> 
 
    <application>
        [...]
 
        <receiver
            android:name="com.google.android.gms.measurement.AppMeasurementReceiver"
            android:enabled="true"
            android:exported="false" >
        </receiver> 

        <receiver
            android:name="com.google.android.gms.measurement.AppMeasurementInstallReferrerReceiver"
            android:enabled="true"
            android:permission="android.permission.INSTALL_PACKAGES" >
            <intent-filter>
                <action android:name="com.android.vending.INSTALL_REFERRER" />
            </intent-filter>
        </receiver>

        <service
            android:name="com.google.android.gms.measurement.AppMeasurementService"
            android:enabled="true"
            android:exported="false" />

        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />

        <provider
            android:name="com.google.firebase.provider.FirebaseInitProvider"
            android:authorities="${applicationId}.firebaseinitprovider"
            android:exported="false"
            android:initOrder="100" />

        <receiver
            android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
            android:exported="true"
            android:permission="com.google.android.c2dm.permission.SEND" >
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <action android:name="com.google.android.c2dm.intent.REGISTRATION" />

                <category android:name="${applicationId}" />
            </intent-filter>
        </receiver>

        <!--
            Internal (not exported) receiver used by the app to start its own exported services
            without risk of being spoofed.
          -->
        <receiver
            android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver"
            android:exported="false" /> 

        <!-- 
            FirebaseInstanceIdService performs security checks at runtime,
            no need for explicit permissions despite exported="true"
          -->
        <service
            android:name="com.google.firebase.iid.FirebaseInstanceIdService"
            android:exported="true" >
            <intent-filter android:priority="-500" >
                <action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
            </intent-filter>
        </service>
    </application>
</manifest>

Without these changes you would get all sorts of errors in logcat when running your app, some of them listed  below:
11-04 15:56:22.970 E/FA      ( 1539): AppMeasurementReceiver not registered/enabled
11-04 15:56:22.971 E/FA      ( 1539): AppMeasurementService not registered/enabled
...
11-04 16:01:23.188 W/FA      ( 1694): Failed to retrieve Firebase Instance Id
...
11-04 16:01:36.391 E AndroidRuntime: Process: com.google.firebase.quickstart.analytics, PID: 1718
11-04 16:01:36.391 E AndroidRuntime: java.lang.RuntimeException: Unable to start receiver com.google.android.gms.measurement.AppMeasurementReceiver: 
                                     java.lang.SecurityException: Neither user 10067 nor current process has android.permission.WAKE_LOCK.


Testing

 

Enable verbose / debug logging for Firebase Analytics (docs):
adb logcat -c 
adb shell setprop log.tag.FA VERBOSE
adb shell setprop log.tag.FA-SVC VERBOSE
adb logcat -v time -s FA FA-SVC

If everything looks fine you may notice the upload scheduling is nothing close to Google Analytics (1h to 12h) - Steve Ganem, Product Manager at Firebase Analytics, confirms on StackOverflow he is aware of the need for quicker reporting.
11-05 21:01:21.560 V/FA      ( 1418): Upload scheduled in approximately ms: 3599998

One way to trigger the initial upload found by Yapaxi user on the same thread, is to Clear data for your app. That will not make the information immediately on the Firebase Console web app, it could take up to an hour or more before the reports are made available.
--------- beginning of system
--------- beginning of main
11-05 21:25:17.413 I/FA      ( 1761): App measurement is starting up, version: 9877
11-05 21:25:17.413 I/FA      ( 1761): To enable debug logging run: adb shell setprop log.tag.FA VERBOSE
11-05 21:25:17.413 D/FA      ( 1761): Debug-level message logging enabled
11-05 21:25:17.413 D/FA      ( 1761): AppMeasurement singleton hash: 96720111
11-05 21:25:17.418 V/FA      ( 1761): Collection enabled
11-05 21:25:17.418 V/FA      ( 1761): App package, google app id: com.google.firebase.quickstart.analytics, 1:348775509088:android:a9b2141120408d37
[...]
11-05 21:25:27.931 V/FA      ( 1761): Uploading data. app, uncompressed size, data: com.google.firebase.quickstart.analytics, 445,
11-05 21:25:27.931 V/FA      ( 1761): batch {
11-05 21:25:27.931 V/FA      ( 1761):   bundle {
11-05 21:25:27.931 V/FA      ( 1761):     protocol_version: 1
11-05 21:25:27.931 V/FA      ( 1761):     platform: android
11-05 21:25:27.931 V/FA      ( 1761):     gmp_version: 9877
11-05 21:25:27.931 V/FA      ( 1761):     uploading_gmp_version: 9877
11-05 21:25:27.931 V/FA      ( 1761):     config_version: 1477992761690000
11-05 21:25:27.931 V/FA      ( 1761):     gmp_app_id: 1:348775509088:android:a9b2141120408d37
11-05 21:25:27.931 V/FA      ( 1761):     app_id: com.google.firebase.quickstart.analytics
                                          [...]
11-05 21:25:27.931 V/FA      ( 1761):   }
11-05 21:25:27.931 V/FA      ( 1761): }
11-05 21:25:27.931 V/FA      ( 1761): Uploading data. size: 431
11-05 21:25:28.039 V/FA      ( 1761): Upload scheduled in approximately ms: 3599999
11-05 21:25:28.042 V/FA      ( 1761): Successful upload. Got network response. code, size: 204, 0

That's it - check your data up on Firebase Console an hour or so later.

34 comments :

  1. How to generate google-services.json to value.xml ?
    And where to put that value.xml files in Eclipse?
    Thanks

    ReplyDelete
    Replies
    1. You missed the "Processing the JSON File" link in the "Importing google-services.json" section - it should take you to Google's website where they explain what values goes into what. The same is hinted in the XML section up in the post here, but you need to open the .json file to see the resemblence.

      Being a string resouce file the convention is to save it in \res\values\ folder, it is also hinted in the title of the XML section up in the post.

      Hope it helps you.

      Delete
    2. Thank you very much.
      More clear now.

      Delete
  2. Thanks for your information, but may I use for Cloud Messaging? Is it have same way to use in Eclipse?
    many thanks.

    ReplyDelete
    Replies
    1. Pretty much the entire article should still apply, you just need the dandar3/android-google-firebase-messaging library (+ dependencies off course). Let us know how it works for you!

      Delete
    2. Thanks man, it works! Your article give me pretty much information that I need.
      :)

      Delete
    3. You're welcome! :-) I just made Cloud Messaging available following your question, glad to hear it works. I might write a new post soon specifically just for Cloud Messaging just so there is one, but I expect it not to be too different.

      Delete
    4. Hi! Is there are any instructions to send notification to android app using GCM? How to use it in eclipse?

      Delete
  3. I´m getting some errors.
    For example -> E/AndroidRuntime(29194): java.lang.RuntimeException: Unable to get provider com.google.firebase.provider.FirebaseInitProvider: java.lang.ClassNotFoundException: Didn't find class "com.google.firebase.provider.FirebaseInitProvider" on path: DexPathList[[zip file.....

    Any ideas? I copied everything into my manifest

    ReplyDelete
    Replies
    1. Hi Denis,

      You're getting the error because it found the entry in the manifest, but it didn't find the class - that class is in the firebase-common library.

      As I've pointed out in the above post in the "Firebase Analytics libraries" section, best to read the dandar3/android-google-firebase-README project on GitHub for details, especially the videos at the end that show you how to easily import the libraries you need into your project.

      Or maybe read this StackOverflow thread in case you've added the libraries to your project the Java way and not the Android way. http://stackoverflow.com/a/10965393

      Please contact me by email if your problem still doesn't resolve.

      Delete
    2. This comment has been removed by the author.

      Delete
    3. Hi Dan ,
      I have the same errors like Denis (class not found problem) , i did every thing was told in dandar3/android-google-firebase-README , i imported the project android-google-firebase-iid with team project set , the import passed good with no errors all is ok , i changed the ${applicationId} in the all project , but unfortunately while building the project the class com.google.firebase.provider.FirebaseInitProvider unable to found , help plzzzzzzzzzzz !!!

      Delete
    4. @Red Eye
      If you already added the android-google-firebase-analytics project as a dependat library to your project (Properties > Android > Library section), I would try next to add android-google-firebase-iid as well in there, clean your project, build and try again.

      Delete
  4. Thank you for your information! Is there are any instructions to send notification to android app using firebase cloud messaging? How to use it in eclipse?

    ReplyDelete
    Replies
    1. Currently working on that, hoping to have it published by the end of the weekend.

      Delete
    2. Sounds perfect, looking forward to it. Thanks a lot for your work

      Delete
  5. The libraries for Firebase Cloud Messaging have ${applicationId} given in their manifest file. However, eclipse is giving an error because of the "$" sign. Any suggestions on how to solve this issue? Thank you !

    ReplyDelete
    Replies
    1. Take a note on the library project with the errors and see the README.md for that project. It will tell you to replace ${applicationId} with the package of your application - that is a step that Android Studio does for you but Eclipse doesn't know anything about, you will have to manually change it. See https://github.com/dandar3/android-google-firebase-iid/ as a reference.

      Delete
    2. Thank you for your help !

      Delete
  6. Will the information about sending notifications to android app using firebase cloud messaging be published soon? Thank you so much for the help

    ReplyDelete
    Replies
    1. Just posted today, should not be much different than this one, but it's out there now.

      Delete
  7. When I add the permissions to android manifest I get an installation error : INSTALL_FAILED_CONFLICTING_PROVIDER. Any idea why that is happening?

    ReplyDelete
    Replies
    1. Probably because of conflicting provider authorities. See the updated AndroidManifest.xml template, you need to replace ${applicationId} with your application id (Java package). https://developer.android.com/studio/build/application-id.html

      Delete
  8. Am i supposed to replace the ${applicationId} with my package name? where do i get the package name from? I have read the https://github.com/dandar3/android-google-firebase-iid/ reference but i am still not sure where to get the package name from. Thank you for the help.

    ReplyDelete
    Replies
    1. You've defined a package name when you created your application, it's in the <manifest> element in AndroidManifest.xml, see https://developer.android.com/guide/topics/manifest/manifest-element.html Can you please contact me by email dan.dar33 at gmail.com for questions unrelated directly to this post please, it would be easier for everyone, thanks.

      Delete
  9. Hi, I follow the steps given, and my app immediately crash when starting.
    This is the log:

    FATAL EXCEPTION: main
    java.lang.NoClassDefFoundError: com.google.android.gms.R$string
    at com.google.android.gms.common.internal.zzam.(Unknown Source)
    at com.google.firebase.FirebaseOptions.fromResource(Unknown Source)
    at com.google.firebase.FirebaseApp.initializeApp(Unknown Source)
    at com.google.firebase.provider.FirebaseInitProvider.onCreate(Unknown Source)
    at android.content.ContentProvider.attachInfo(ContentProvider.java:1214)
    at android.content.ContentProvider.attachInfo(ContentProvider.java:1189)
    at com.google.firebase.provider.FirebaseInitProvider.attachInfo(Unknown Source)
    at android.app.ActivityThread.installProvider(ActivityThread.java:5119)
    at android.app.ActivityThread.installContentProviders(ActivityThread.java:4725)
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4665)
    at android.app.ActivityThread.access$1400(ActivityThread.java:159)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1376)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:176)
    at android.app.ActivityThread.main(ActivityThread.java:5419)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:525)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1046)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:862)
    at dalvik.system.NativeStart.main(Native Method)

    I think it missing parameters for initialisation, i think from google-services.json.
    But I already converted my google-services.json into values.xml and put the file into my res folder (res/values/values.xml)

    Any idea why the string cannot be found? Thank you very much for your attention.

    ReplyDelete
    Replies
    1. I believe you posted the same problem on the other thread, see my answer below.

      com.google.android.gms.R$string is in google-play-services-basement.

      If you go to dandar3/android-google-firebase-README project on GitHub (linked in article above) you can see all the libraries you need grouped by Firebase library.

      I would suggest using Eclipse to download an entire set easily (see the video explaining how to download an entire project set), then import in IntelliJ all projects from the Eclipse workspace.

      Delete
    2. Hi Dan, I saw similar question on FCM thread.
      I am a different person, just happen asking similar question on the same day. hehe.

      "com.google.android.gms.R$string is in google-play-services-basement." this really saved me from my problem.
      I has been wondering for quite some time.

      In my case, I directly include all *.jar for firebase & google sdk without their individual project.
      With this method I can initialise firebase manually using code,
      but not automatically using FirebaseInitProvider from manifest.

      After reading your reply, i tried importing google-play-services-basement as a project, complete with it res folder.
      Then everything works fine!

      Thank you very mush for your reply :D

      Delete
    3. You're welcome. glad you sorted it out! Yes, you need the entire projects, resources and all, not just the classes in the JARs. The libraries from GitHub are just the AAR packages exploded and reorganized so that they can be consumed by Eclipse ADT, which doesn't have AAR support. https://developer.android.com/studio/projects/android-library.html#aar-contents

      Delete
  10. How can i integrate with android studio (no gradle) ?

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Hi dan my app is crash this is my log
      Caused by: java.lang.ClassNotFoundException: Didn't find class "com.google.android.gms.R$string" on path: DexPathList[[zip file "/data/app/com.bisnews.bisnewscompanionmobile.dev-2/base.apk"],nativeLibraryDirectories=[/vendor/lib64, /system/lib64]]
      at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
      at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
      at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
      at com.google.android.gms.common.internal.zzam.(Unknown Source) 
      at com.google.firebase.FirebaseOptions.fromResource(Unknown Source) 
      at com.google.firebase.FirebaseApp.initializeApp(Unknown Source) 
      at com.google.firebase.provider.FirebaseInitProvider.onCreate(Unknown Source) 
      at android.content.ContentProvider.attachInfo(ContentProvider.java:1686) 
      at android.content.ContentProvider.attachInfo(ContentProvider.java:1655) 
      at com.google.firebase.provider.FirebaseInitProvider.attachInfo(Unknown Source) 
      at android.app.ActivityThread.installProvider(ActivityThread.java:4964) 
      at android.app.ActivityThread.installContentProviders(ActivityThread.java:4559) 
      at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4499) 
      at android.app.ActivityThread.access$1500(ActivityThread.java:144) 
      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1339) 
      at android.os.Handler.dispatchMessage(Handler.java:102) 
      at android.os.Looper.loop(Looper.java:135) 
      at android.app.ActivityThread.main(ActivityThread.java:5221) 
      at java.lang.reflect.Method.invoke(Native Method) 
      at java.lang.reflect.Method.invoke(Method.java:372) 
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899) 
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694) 
      Suppressed: java.lang.ClassNotFoundException: com.google.android.gms.R$string
      at java.lang.Class.classForName(Native Method)
      at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
      at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
      at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
      ... 20 more
      Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack available
      what should i do ? thanks :)

      Delete
    3. I've already answered this in a preview comment: com.google.android.gms.R$string is in google-play-services-basement.

      If you go to dandar3/android-google-firebase-README project on GitHub (linked in article above) you can see all the libraries you need grouped by Firebase library.

      As for Android Studio with no gradle, I see there is a File > New... > New Module > Import .JAR / .AAR Package. Run android-sdk\SDK Manager, install Extras > Google Repository, then import from androi-sdk\extras\google\m2repository\.

      Otherwise I guess you could try importing libraries I prepare for Eclipse with File > New... > New Module > Import Eclipse ADT Project.

      Delete