Skip to content

Latest commit

History

History

trikot-viewmodels-declarative-compiler

Trikot.viewmodels.declarative.compiler

The Trikot ViewModels Declarative Compiler is a Gradle plugin that generates the required boilerplate code needed when using Trikot ViewModels Declarative, for both Streams and Flow versions.

Plugin Usage

Published Property

When a field in a ViewModel interface needs to be published, simply annotate it with the @Published annotation.

interface ExampleViewModel : VMDViewModel {

    @Published
    val publishedField: String

    val nonPublishedField: String
}

When the plugin detects the annotation on an interface, it will automatically generate a base class that your implementation can extend. This class will contain:

  • A constructor that requires the initial values of the published fields (nullable fields will have null default values)
  • The published property delegate
  • The overridden var based on the delegate
  • The method to update property mapping
  • Convenient methods to bind each field individually, or all of them in a single method
  • Convenient methods to get the Publisher/Flow of a field

The generated class will be abstract, only if needed. If it happens that it already implements everything required, it will be concrete (but still open). In that can you can instantiate and use the generated class without the need of subclassing it.

The conditions to be concrete are:

  • The ViewModel must have all its fields annotated as @Published (including fields inherited from any super interface, excluding VMDViewModel fields)
  • The ViewModel must not have any abstract methods defined.

Published Subclass

By default, the generated base class will extend the standard ViewModel base class, VMDViewModelImpl. However, if your View Model implementation needs it to extend something else, use the @PublishedSubClass annotation on your implementation.

@PublishedSubClass(superClass = VMDLifecycleViewModelImpl::class)
class ExampleViewModelImpl(
    coroutineScope: CoroutineScope
) : BaseExampleViewModelImpl(
    publishedFieldInitialValue = "initialValue",
    coroutineScope = CoroutineScope
)

In this case, BaseExampleViewModelImpl will extend VMDLifecycleViewModelImpl instead of VMDViewModelImpl.

Plugin installation

The compiler has 2 main components:

  • A Kotlin Multiplatform library containing the annotations, needed as a dependency of your kotlin multiplatform code.
  • A KSP plugin that handles annotation processing and code generation, only needed at compile time in your project.

Trikot ViewModels Annotations

Add a dependency to com.mirego.trikot:viewmodels-annotations:[trikot version] in your commonMain source set:

val commonMain by getting {
    dependencies {
        api("com.mirego.trikot:viewmodels-annotations:[trikot version]")
    }
}

And in your native exports:

framework {
    export("com.mirego.trikot:viewmodels-annotations:[trikot version]")
}

Trikot ViewModels Compiler

The compiler uses KSP (Kotlin Symbol Processing). If you don't have it setup in your project (for another 3rd party plugin), add it in the plugins section of your common module gradle script:

plugins {
    id("com.google.devtools.ksp") version "1.7.10-1.0.6"
}

The compiler is not a multi platform project, mainly because it's only needed in the common Koltin code (no need for target-specific code).

In order to add the compiler in your build pipeline, add the following dependency (at the root of your gradle script file).

dependencies {
    add("kspCommonMainMetadata", "com.mirego.trikot:viewmodels-declarative-compiler-[VMD flavor]:[trikot version]")
}

Where VMD flavor is either streams or flow, depending on which Trikot Viewmodels Declarative library you are using.

Then, add the source directory to your common source set:

val commonMain by getting {
    dependencies {
        api("com.mirego.trikot:viewmodels-annotations:[trikot version]")
    }
    kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin")
}

This will ensure that both Gradle and your IDE sees the generated code as actual source.

Finally, add the following snippet at the root of your Gradle script to hook the code generation task to any Kotlin compilation task:

tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>>().all {
    if (name != "kspCommonMainKotlinMetadata") {
        dependsOn("kspCommonMainKotlinMetadata")
    }
}