Getting Started with Android RUM Collection
Incident Management is now generally available! Incident Management is now generally available!

Getting Started with Android RUM Collection

Send Real User Monitoring data to Datadog from your Android applications with Datadog’s dd-sdk-android client-side RUM library and leverage the following features:

  • get a global idea about your app’s performance and demographics;
  • understand which resources are the slowest;
  • analyze errors by OS and device type.

Setup

  1. Add the Gradle dependency by declaring the library as a dependency in your build.gradle file:

    repositories {
        maven { url "https://dl.bintray.com/datadog/datadog-maven" }
    }
    
    dependencies {
        implementation "com.datadoghq:dd-sdk-android:x.x.x"
    }
    
  2. Initialize the library with your application context and your Datadog client token. For security reasons, you must use a client token: you cannot use Datadog API keys to configure the dd-sdk-android library as they would be exposed client-side in the Android application APK byte code. For more information about setting up a client token, see the client token documentation. You also need to provide an Application ID (see our RUM Getting Started page).

    class SampleApplication : Application() {
        override fun onCreate() {
            super.onCreate()
    
            val config = DatadogConfig.Builder("<CLIENT_TOKEN>", "<ENVIRONMENT_NAME>", "<APPLICATION_ID>")
                            .trackInteractions()
                            .useViewTrackingStrategy(strategy)
                            .build()
            Datadog.initialize(this, config)
        }
    }
    
    class SampleApplication : Application() {
        override fun onCreate() {
            super.onCreate()
    
            val config = DatadogConfig.Builder("<CLIENT_TOKEN>", "<ENVIRONMENT_NAME>", "<APPLICATION_ID>")
                            .trackInteractions()
                            .useViewTrackingStrategy(strategy)
                            .useEUEndpoints()
                            .build()
            Datadog.initialize(this, config)
        }
    }
    

Depending on your application’s architecture, you can choose one of several implementations of ViewTrackingStrategy:

  • ActivityViewTrackingStrategy: Every activity in your application is considered a distinct view.
  • FragmentViewTrackingStrategy: Every fragment in your application is considered a distinct view.
  • NavigationViewTrackingStrategy: If you use the Android Jetpack Navigation library, this is the recommended strategy. It automatically tracks the navigation destination as a distinct view.
  • MixedViewTrackingStrategy: Every activity or fragment in your application is considered a distinct view. This strategy is a mix between the ActivityViewTrackingStrategy and FragmentViewTrackingStrategy.

Note: For ActivityViewTrackingStrategy, FragmentViewTrackingStrategy, or MixedViewTrackingStrategy you can filter which Fragment or Activity is tracked as a RUM View by providing a ComponentPredicate implementation in the constructor.

Note: By default, the library won’t track any view. If you decide not to provide a view tracking strategy you will have to manually send the views by calling the startView and stopView methods yourself.

  1. Configure and register the RUM Monitor. You only need to do it once, usually in your application’s onCreate() method:

    val monitor = RumMonitor.Builder()
            // Optionally set a sampling between 0.0 and 100.0%
            // Here 75% of the RUM Sessions will be sent to Datadog
            .sampleRumSessions(75.0f)
            .build()
    GlobalRum.registerIfAbsent(monitor)
    
  2. If you want to track your OkHttp requests as resources, you can add the provided Interceptor as follows:

    val okHttpClient =  OkHttpClient.Builder()
        .addInterceptor(DatadogInterceptor())
        .build()
    

    This creates RUM Resource data around each request processed by the OkHttpClient, with all the relevant information automatically filled (URL, method, status code, error). Note that only network requests started when a view is active will be tracked. If you want to track requests when your application is in the background, you can create a view manually, as explained below.

    Note: If you also use multiple Interceptors, this one must be called first.

  3. (Optional) If you want to get timing information in Resources (such as time to first byte, DNS resolution, etc.), you can add the Event listener factory as follows:

    val okHttpClient =  OkHttpClient.Builder()
        .addInterceptor(DatadogInterceptor())
        .eventListenerFactory(DatadogEventListener.Factory())
        .build()
    
  4. (Optional) If you want to manually track RUM events, you can use the GlobalRum class.

To track views, call the RumMonitor#startView when the view becomes visible and interactive (equivalent with the lifecycle event onResume) followed by RumMonitor#stopView when the view is no longer visible(equivalent with the lifecycle event onPause) as follows:

   fun onResume(){
     GlobalRum.get().startView(viewKey, viewName, viewAttributes)        
   }
   
   fun onPause(){
     GlobalRum.get().stopView(viewKey, viewAttributes)        
   }

To track resources, call the RumMonitor#startResource when the resource starts being loaded, and RumMonitor#stopResource when it is fully loaded, or RumMonitor#stopResourceWithError if an error occurs while loading the resource, as follows:

   fun loadResource(){
     GlobalRum.get().startResource(resourceKey, method, url, resourceAttributes)
     try {
       // do load the resource
       GlobalRum.get().stopResource(resourceKey, resourceKind, additionalAttributes)
     } catch (e : Exception) {
       GlobalRum.get().stopResourceWithError(resourceKey, message, origin, e)
     }
   }

To track user actions, call the RumMonitor#addUserAction, or for continuous actions, call the RumMonitor#startUserAction and RumMonitor#stopUserAction, as follows:

   fun onUserInteraction(){
     GlobalRum.get().addUserAction(resourceKey, method, url, resourceAttributes)
   }
  1. (Optional) If you want to add custom information as attributes to all RUM events, you can use the GlobalRum class.

       // Adds an attribute to all future RUM events
       GlobalRum.addAttribute(key, value)
    
       // Removes an attribute to all future RUM events
       GlobalRum.removeAttribute(key)
    

Advanced logging

Library Initialization

The following methods in DatadogConfig.Builder can be used when creating the Datadog Configuration to initialize the library:

MethodDescription
setServiceName(<SERVICE_NAME>)Set <SERVICE_NAME> as default value for the service standard attribute attached to all logs sent to Datadog (this can be overriden in each Logger).
setRumEnabled(true)Set to true to enable sending RUM data to Datadog.
trackInteractions(Array<ViewAttributesProvider>)Enables tracking User interactions (such as Tap, Scroll or Swipe). The parameter allow you to add custom attributes to the RUM Action events based on the widget with which the user interacted.
useViewTrackingStrategy(strategy)Defines the strategy used to track Views. Depending on your application’s architecture, you can choose one of several implementations of ViewTrackingStrategy (see above) or implement your own.
addPlugin(DatadogPlugin, Feature)Adds a plugin implementation for a specific feature (CRASH, LOG, TRACE, RUM). The plugin will be registered once the feature is initialized and unregistered when the feature is stopped.

RumMonitor Initialization

The following methods in RumMonitor.Builder can be used when creating the RumMonitor to track RUM data:

MethodDescription
sampleRumSessions(float)Sets the sampling rate for RUM Sessions. This method expects a value between 0 and 100, and is used as a percentage of Session for which data will be sent to Datadog.

Manual Tracking

If you need to manually track events, you can do so by getting the active RumMonitor instance, and call one of the following methods:

MethodDescription
startView(<key>, <name>, <attributes>)Notifies the RumMonitor that a new View just started. Most often, this method should be called in the frontmost Activity or Fragment's onResume() method.
stopView(<key>, <attributes>)Notifies the RumMonitor that the current View just stopped. Most often, this method should be called in the frontmost Activity or Fragment's onPause() method.
addUserAction(<type>, <name>, <attributes>)Notifies the RumMonitor that a user action just happened.
startUserAction(<type>, <name>, <attributes>)Notifies the RumMonitor that a continuous user action just started (for example a user scrolling a list).
stopUserAction(<type>, <name>, <attributes>)Notifies the RumMonitor that a continuous user action just stopped.
startResource(<key>, <method>, <url>, <attributes>)Notifies the RumMonitor that the application started loading a resource with a given method (e.g.: GET or POST), at the given url.
stopResource(<key>, <status>, <size>, <kind> <attributes>)Notifies the RumMonitor that a resource finished being loaded, with a given status (usually an HTTP status code), size (in bytes) and kind.
stopResourceWithError(<key>, <status>, <message>, <source>, <throwable>)Notifies the RumMonitor that a resource couldn’t finished being loaded, because of an exception.
addError(<message>, <source>, <throwable>, <attributes>)Notifies the RumMonitor that an error occurred.

Tracking widgets

Most of the time, the widgets are displayed in the AppWidgetHostView provided by the HomeScreen application, and we are not able to provide auto-instrumentation for those components. To send UI interaction information from your widgets, manually call our API. See one example approach in this sample application: Tracking widgets

Batch collection

All the RUM events are first stored on the local device in batches. Each batch follows the intake specification. They are sent as soon as the network is available, and the battery is high enough to ensure the Datadog SDK does not impact the end user’s experience. If the network is not available while your application is in the foreground, or if an upload of data fails, the batch is kept until it can be sent successfully.

This means that even if users open your application while being offline, no data will be lost.

The data on the disk will automatically be discarded if it gets too old to ensure the SDK doesn’t use too much disk space.

Extensions

Coil

If you use Coil to load images in your application, take a look at Datadog’s dedicated library.

Fresco

If you use Fresco to load images in your application, take a look at Datadog’s dedicated library.

Glide

If you use Glide to load images in your application, take a look at our dedicated library.

Picasso

If you use Picasso, let it use your OkHttpClient, and you’ll get RUM and APM information about network requests made by Picasso.

        val picasso = Picasso.Builder(context)
                .downloader(OkHttp3Downloader(okHttpClient))
                // …
                .build()
        Picasso.setSingletonInstance(picasso)

Retrofit

If you use Retrofit, let it use your OkHttpClient, and you’ll get RUM and APM information about network requests made with Retrofit.

        val retrofitClient = Retrofit.Builder()
                .client(okHttpClient)
                // …
                .build()

SQLDelight

If you use SQLDelight, take a look at our dedicated library.

SQLite

Following SQLiteOpenHelper’s Generated API documentation, you only have to provide the implementation of the DatabaseErrorHandler -> DatadogDatabaseErrorHandler in the constructor.

Doing this detects whenever a database is corrupted and sends a relevant RUM error event for it.

   class <YourOwnSqliteOpenHelper>: SqliteOpenHelper(<Context>, 
                                                     <DATABASE_NAME>, 
                                                     <CursorFactory>, 
                                                     <DATABASE_VERSION>, 
                                                     DatadogDatabaseErrorHandler()) {
                                // …
   
   }

Apollo (GraphQL)

If you use Apollo, let it use your OkHttpClient, and you’ll get RUM and APM information about all the queries performed through Apollo client.

        val apolloClient =  ApolloClient.builder()
                 .okHttpClient(okHttpClient)
                 .serverUrl(<APOLLO_SERVER_URL>)
                 .build()

Further Reading

Additional helpful documentation, links, and articles: