Skip to content

🎲 Kotlin Symbol Processor that auto-generates extensive sealed classes and interfaces for Android and Kotlin.

License

Notifications You must be signed in to change notification settings

skydoves/sealedx

Repository files navigation

SealedX


Google
License API Build Status Android Weekly Profile


🎲 Kotlin Symbol Processor to auto-generate extensive sealed classes and interfaces for Android and Kotlin.

Why SealedX?

SealedX generates extensive sealed classes & interfaces based on common sealed classes for each different model. You can reduce writing repeated sealed classes for every different model by auto-generating based on KSP (Kotlin Symbol Processor).

You can massively reduce writing repeated files such as _UiState sealed interfaces if your project is based on MVI architecture.

Use Cases

If you want to learn more about how to migrate and use cases, check out the repositories below:

Gradle Setup

To use KSP (Kotlin Symbol Processing) and SealedX library in your project, you need to follow steps below.

1. Enable KSP in your module

Add the KSP plugin below into your module's build.gradle file:

Kotlin (KTS)
plugins {
    id("com.google.devtools.ksp") version "1.9.0-1.0.12"
}
Groovy
plugins {
    id("com.google.devtools.ksp") version "1.9.0-1.0.12"
}

Note: Make sure your current Kotlin version and KSP version is the same.

2. Add SealedX dependencies

Maven Central

Add the dependency below into your module's build.gradle file:

dependencies {
    implementation("com.github.skydoves:sealedx-core:1.0.2")
    ksp("com.github.skydoves:sealedx-processor:1.0.2")
}

3. Add KSP source path

To access generated codes from KSP, you need to set up the source path like the below into your module's build.gradle file:

Android Kotlin (KTS)
kotlin {
  sourceSets.configureEach {
    kotlin.srcDir("$buildDir/generated/ksp/$name/kotlin/")
  }
}
Android Groovy
android {
    applicationVariants.all { variant ->
        kotlin.sourceSets {
            def name = variant.name
            getByName(name) {
                kotlin.srcDir("build/generated/ksp/$name/kotlin")
            }
        }
    }
}
Pure Kotlin (KTS)
kotlin {
    sourceSets.main {
        kotlin.srcDir("build/generated/ksp/main/kotlin")
    }
    sourceSets.test {
        kotlin.srcDir("build/generated/ksp/test/kotlin")
    }
}
Pure Kotlin Groovy
kotlin {
    sourceSets {
        main.kotlin.srcDirs += 'build/generated/ksp/main/kotlin'
        test.kotlin.srcDirs += 'build/generated/ksp/test/kotlin'
    }
}

Usage

ExtensiveSealed

@ExtensiveSealed annotation is the main trigger of the Kotlin Symbol Processor to run a sealed-extensive processor on compile time.

  • @ExtensiveSealed must be annotated to sealed classes or interfaces, which should be a common model to generate extensive sealed classes and interfaces.
  • @ExtensiveSealed receives an array of @ExtensiveModel annotations, which include the extensive model types.
  • If you build your project, extensive sealed classes or interfaces will be generated based on those extensive models.

Let's see a common UiState sealed interface below that is annotated with @ExtensiveSealed annotation:

@ExtensiveSealed(
  models = [
    ExtensiveModel(Poster::class),
    ExtensiveModel(PosterDetails::class)
  ]
)
sealed interface UiState {
  data class Success(val data: Extensive) : UiState
  object Loading : UiState
  object Error : UiState
}

The example codes above will generate PosterUiState and PosterDetailsUiState sealed interfaces below:

PosterUiState (generated):

public sealed interface PosterUiState {
  public object Error : PosterUiState

  public object Loading : PosterUiState

  public data class Success(
    public val `data`: Poster,
  ) : PosterUiState
}

PosterDetailsUiState (generated):

public sealed interface PosterDetailsUiState {
  public object Error : PosterDetailsUiState

  public object Loading : PosterDetailsUiState

  public data class Success(
    public val `data`: PosterDetails,
  ) : PosterDetailsUiState
}
See further sealed class examples

In the case of the sealed classes, it's not different fundamentally from sealed interface examples.

@ExtensiveSealed(
  models = [ ExtensiveModel(type = Poster::class) ]
)
sealed class UiState {
  data class Success(val data: Extensive) : UiState()
  object Loading : UiState()
  object Error : UiState()
}

The example codes above will generate the PosterUiState sealed class below:

PosterUiState (generated):

public sealed class PosterUiState {
  public object Error : PosterUiState()

  public object Loading : PosterUiState()

  public data class Success(
    public val `data`: Poster,
  ) : PosterUiState()
}

ExtensiveModel

@ExtensiveModel annotation class contains information on extensive models like model type and a custom name, which decides the name of generated classes. Basically, (the simple name of the type) + (the name of common sealed classes) will be used to name of generated classes, but you can modify the prefix with the name parameter like the example below:

@ExtensiveSealed(
  models = [ ExtensiveModel(type = PosterExtensive::class, name = "Movie") ]
)
sealed interface UiState {
  data class Success(val data: Extensive) : UiState
  object Loading : UiState
  object Error : UiState
}

The example codes above will generate MovieUiState file instead of PosterExtensiveUiState like the below:

MovieUiState (generated):

public sealed interface MovieUiState {
  public object Error : MovieUiState

  public object Loading : MovieUiState

  public data class Success(
    public val `data`: PosterExtensive,
  ) : MovieUiState
}

Collection type in @ExtensiveModel

Basically, you can't set a collection type like a List to the type parameter of the @ExtensiveModel annotation. So if you need to set a collection type as an extensive model, you need to write a wrapper class like the below:

data class PosterExtensive(
  val posters: List<Poster>
)

@ExtensiveSealed(
  models = [ ExtensiveModel(type = PosterExtensive::class) ]
)
sealed interface UiState {
    ..
}

Extensive

Extensive is an interface that is used to represent extensive model types of sealed classes and interfaces. When you need to use an extensive model type in your primary constructor of data class, you can use the Extensive extensive type on your common sealed classes and interfaces. It will be replaced by the extensive model type on compile time.

@ExtensiveSealed(
  models = [ ExtensiveModel(type = PosterExtensive::class) ]
)
sealed interface UiState {
  // You need to use the Extensive type if you want to use an extensive model type in the generated code.
  data class Success(val data: Extensive) : UiState
  ..
}

The example codes above will generate PosterExtensiveUiState sealed interface like the below:

PosterExtensiveUiState (generated):

public sealed interface PosterExtensiveUiState {
  public object Error : PosterExtensiveUiState

  public object Loading : PosterExtensiveUiState

  public data class Success(
    public val `data`: PosterExtensive,
  ) : PosterExtensiveUiState
}

As you can see from the example above, the Extensive interface type will be replaced with the extensive model by the SealedX processor on compile time.

Find this repository useful? ❀️

Support it by joining stargazers for this repository. ⭐
Also, follow me on GitHub for my next creations! 🀩

License

Designed and developed by 2022 skydoves (Jaewoong Eum)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.