Sample code in Kotlin modules

Background

Public API can (and should!) have small corresponding code snippets that demonstrate functionality and usage of a particular API. These are often exposed inline in the documentation for the function / class - this causes consistency and correctness issues as this code is not compiled against, and the underlying implementation can easily change.

KDoc (JavaDoc for Kotlin) supports a @sample tag, which allows referencing the body of a function from documentation. This means that code samples can be just written as a normal function, compiled and linted against, and reused from other modules such as tests! This allows for some guarantees on the correctness of a sample, and ensuring that it is always kept up to date.

Enforcement

There are still some visibility issues here - it can be hard to tell if a function is a sample, and is used from public documentation - so as a result we have lint checks to ensure sample correctness.

Primarily, there are three requirements when using sample links:

  1. All functions linked to from a @sample KDoc tag must be annotated with @Sampled
  2. All sample functions annotated with @Sampled must be linked to from a @sample KDoc tag
  3. All sample functions must live inside a separate samples library submodule - see the section on module configuration below for more information.

This enforces visibility guarantees, and make it easier to know that a sample is a sample. This also prevents orphaned samples that aren't used, and remain unmaintained and outdated.

Sample usage

The follow demonstrates how to reference sample functions from public API. It is also recommended to reuse these samples in unit tests / integration tests / test apps / library demos where possible to help ensure that the samples work as intended.

Public API:

/*
 * Fancy prints the given [string]
 *
 * @sample androidx.printer.samples.fancySample
 */
fun fancyPrint(str: String) ...

Sample function:

package androidx.printer.samples

import androidx.printer.fancyPrint

@Sampled
fun fancySample() {
   fancyPrint("Fancy!")
}

Generated documentation visible on d.android.com / within Android Studio

fun fancyPrint(str: String)

Fancy prints the given [string]

<code>
 import androidx.printer.fancyPrint

 fancyPrint("Fancy!")
<code>

Warning: Only the body of the function is used in generated documentation, so any other references to elements defined outside the body of the function (such as variables defined within the sample file) will not be visible. To ensure that samples can be easily copy and pasted without errors, make sure that any references are defined within the body of the function.

Module configuration

The following module setups should be used for sample functions:

Per-module samples

For library groups with relatively independent sub-libraries. This is the recommended project setup, and should be used in most cases.

Gradle project name: :foo-library:foo-module:foo-module-samples

foo-library/
  foo-module/
    samples/

Group-level samples

For library groups with strongly related samples that want to share code and be reused across a library group, a singular shared samples library can be created. In most cases this is discouraged - samples should be small and show the usage of a particular API / small set of APIs, instead of more complicated usage combining multiple APIs from across libraries. For these cases a sample application is more appropriate.

Gradle project name: :foo-library:foo-library-samples

foo-library/
  foo-module/
  bar-module/
  samples/

Samples module configuration

Samples modules are published to GMaven so that they are available to Android Studio, which displays referenced samples as hover-over pop-ups.

To achieve this, samples modules must declare the same MavenGroup and publish as the library(s) they are samples for.