Best practices

Coding style#

This guide will go over some tips on how to standardize your code so everybody can contribute to it. It's not necessary to read this section of the docs if you have your own coding preferences.

Kotlin docs has its own coding conventions.

The DOs#

This section explains the preferred code style for achieving code readability.

Prefer descriptive names#

Choose names that convey the purpose of the variable, function, or class.

Use identations and spaces#

Please FFS, code like a sane person and use proper spaces and tabs when coding-it is more readable that way.

  • Bad example:

    val listOfApis = listOf("API1","API2","API3","API4")
    val listOfExtractors = listOf("EX1","EX2")
    // ❌
  • Good example:

    val listOfApis = listOf(
        "API1",
        "API2",
        "API3",
        "API4"
    )
    val listOfExtractors = listOf("EX1", "EX2")
    // ✅

Organize code by feature#

Always group related classes together by feature, not by type. This structure makes it easier to find everything related to a specific feature in one place.

└── com.example.testProvider/
    ├── api/
    │   ├── search/
    │   │   └── TestSearchApi.kt
    │   ├── catalog/
    │   │   └── TestCatalogApi.kt
    │   ├── metadata/
    │   │   └── TestMetadataApi.kt
    │   └── links/
    │       └── TestMediaLinkApi.kt
    ├── settings/
    │   ├── MySettingsScreen.kt
    │   └── ... other compose related files
    ├── common/
    │   └── ... helper classes/methods
    └── TestProviderPlugin.kt

Keep methods short and focused#

Aim for small methods that perform a single task, improving readability and maintainability.

  • Bad example:

    override fun getLinks(
        film: FilmMetadata,
        episode: Episode?,
    ): Flow<MediaLink> = flow {
        // Fetch API
        // Parse HTML
        // Execute extractor
        // Emit links
    }
    // ❌
  • Good example:

    override fun getLinks(
        film: FilmMetadata,
        episode: Episode?,
    ): Flow<MediaLink> = flow {
        val response = fetchApi(film, episode)
        val html = parseResponseAsHtml(response)
        val links = extractor.run(html)
     
        for (link in links) {
            emit(link)
        }
    }
    // ✅

Create reusable components#

When implementing your provider's settings UI, it's preferred to extract common settings UI patterns into composable functions:

This also applies to non-Compose code.

@Composable
fun SwitchPreference(
    title: String,
    checked: Boolean,
    onCheckedChange: (Boolean) -> Unit,
) {
    // ...
}
 
@Composable
fun TextPreference(
    title: String,
    value: Boolean,
    onValueChange: (String) -> Unit,
) {
    // ...
}
 
@Composable
fun SettingsCategory(
    title: String,
    content: @Composable () -> Unit
) {
    // ...
}
 
@Composable
fun SettingsItem(
    title: String,
    description: String? = null,
    icon: @Composable (() -> Unit)? = null,
    content: @Composable () -> Unit
) {
    // ...
}

The DONTs#

This section explains the pitfalls to avoid to when achieving code readability.

Avoid the elvis operator#

The ?: operator does make development faster but new people might get confused to it. This can easily be replaced by a simple conditional if (x != null).

Avoid premature abstraction#

It's the need to create an interface that only has one implementation. This only obfuscates the code and makes code reading more difficult and slow.

Avoid overly complex expressions#

When creating providers, the tendency to combine too many operations in a single line is difficult to avoid. This can make code difficult to read and maintain. Prioritize readability by breaking complex expressions into multiple, simpler statements.

  • Bad example:

    val catalogNames = catalogs
        .filter { it.isMovie }
        .map { it.name }
        .sortedByDescending { it.name }
        .take(5)
    // ❌
  • Good example:

    val movieCatalogs = catalogs.filter { it.isMovie }
    val catalogNames = movieCatalogs.map { it.name }
    val topFiveCatalogNames = catalogNames.sortedDescending().take(5)
    // ✅

Avoid excessive comments#

Always aim to write self-explanatory code with meaningful variable so there'd be no need to add extra comments. Only add comments to explain why a code does it and not what it does.

Avoid overly long naming conventions#

While descriptive names are important for code readability, excessively long names can make code cumbersome and difficult to follow.

  • Bad example:

    fun getSubtitlesAndStreamsFromApi(): List<MediaLink>
        = // ...
    // ❌
  • Good example:

    fun getMediaLinks(): List<MediaLink>
        = // ...
    // ✅