In Jetpack Compose, we use something called Previews, which are Composable functions written specifically to preview (or interact with) the UI rendered by Compose in the editor itself without needing to run the app on a device.
And Composable methods generally have some parameters based on which the UI is rendered. More often than not, Composable methods have a significant amount of data inputs which are needed to be passed from the preview methods for the previews to render.
In cases like these PreviewParameterProvider (a class from the Compose tooling library) can be used to provide data for the previews.
PreviewParameterProvider makes the Preview methods less verbose and easy to read by abstracting away the input data construction logic. And these providers can be reused across various Preview methods to render different previews.
Due to the amount of sheer verbosity involved in writing PreviewParameterProvider by hand, it becomes tedious to write PreviewParameterProvider for each and every UI model. And as writing PreviewParameterProvider for every model becomes an uninteresting task it becomes a barrier to entry for writing Previews for all the Composables.
That is where Dowel comes in and takes care of generating all of the boilerplate PreviewParameterProvider logic for your UI models.
This makes writing Previews simple and hence encourages writing more Previews for Composables in general. Apart from that, with Dowel you can also Fuzz test your Composables with all of the random values of random length or range being generated for all of the properties of the inputs.
Note : These random lengths or ranges can also be regulated, read more at "4. How do I use Dowel?" section.
plugins {
id("com.google.devtools.ksp") version "1.7.0-1.0.6"
}Note : Make sure your project's
Kotlinversion andKSP versionare the same. Learn more about the available versions here
pluginManagement {
repositories {
// Other repos
maven { url 'https://jitpack.io' } // <----- This is the line to add
}
}
dependencyResolutionManagement {
repositories {
// Other repos
maven { url 'https://jitpack.io' } // <----- This is the line to add
}
}dependencies {
implementation("com.github.jayasuryat.dowel:dowel:0.8.0")
ksp("com.github.jayasuryat.dowel:dowel-processor:0.8.0")
}kotlin {
sourceSets.configureEach {
kotlin.srcDir("$buildDir/generated/ksp/$name/kotlin/")
}
}Dowel uses Kotlin Symbol Processing API under the hood to read, parse, and process source code to generate appropriate PreviewParameterProviders.
The primary entry point into Dowel is with @Dowel annotation.
Dowel goes through all the classes annotated with @Dowel annotation and generates PreviewParameterProvider for each class.
Dowel_demo.mp4
// File : NewsArticle.kt
import androidx.compose.runtime.State
import com.jayasuryat.dowel.annotation.Dowel
import kotlinx.coroutines.flow.Flow
@Dowel(count = 2)
data class NewsArticle(
val title: String,
val description: String,
val likes: Int,
val authors: List<String>,
val liveComments: Flow<List<String>>,
val isExpanded: State<Boolean>,
val status: Status,
val onArticleClicked: () -> Unit,
) {
enum class Status { Draft, Accepted, Posted }
}// File in generated sources : NewsArticlePreviewParamProvider.kt
package com.yourapp.module
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import kotlin.sequences.Sequence
import kotlinx.coroutines.flow.flowOf
public class NewsArticlePreviewParamProvider : PreviewParameterProvider<NewsArticle> {
public override val values: Sequence<NewsArticle> = sequenceOf(
NewsArticle(
title = "AdipiscingDuis ac porttitor et",
description = "Phasellusmassa suscipit iaculi",
likes = 94,
authors = listOf(
"Velamet ultricies malesuada co",
"Consequatmassa malesuada sapie",
"Ameta et at bibendum ut neque ",
"Mimollis ac consectetur Praese",
"Namimperdiet massa bibendum po",
),
liveComments = flowOf(listOf(
"Malesuadasit Duis dapibus cong",
"Metusluctus nec congue congue ",
"InPraesent est tempus ac ultri",
"Malesuadaquis est Lorem sapien",
"FinibusCras mattis imperdiet n",
)),
isExpanded = mutableStateOf(false),
status = NewsArticle.Status.values().random(),
onArticleClicked = {},
),
NewsArticle(
title = "Tempuspurus congue elit euismo",
description = "Conguemetus Duis enim tincidun",
likes = 3,
authors = listOf(
"Namcondimentum lobortis et ali",
"Congueeu ultrices lacinia sed ",
"Lectussuscipit nisi eu quis se",
"Utnisi sapien mi ex magna magn",
"Proinipsum malesuada enim sed ",
),
liveComments = flowOf(listOf(
"CongueProin nec metus metus ma",
"Antenisi consectetur ac purus ",
"Eualiquet malesuada turpis rho",
"LobortisDuis mollis ac a lacus",
"Magnaet Donec libero Lorem sap",
)),
isExpanded = mutableStateOf(false),
status = NewsArticle.Status.values().random(),
onArticleClicked = {},
),
)
}
There are only 3 Dowel annotations you need to know about:
@Dowel: The primary entry point intoDowel, triggers generationPreviewParameterProviderfor that class.@DowelList: Same as@Dowel, but generates aPreviewParameterProviderof typeList<T>whereTis the class annotated with@DowelListannotation. Rest of the behavior is same as the@Dowelannotation.@ConsiderForDowel: If you want to add support for an unsupported type, or override provider logic for a particular type, then you can do that with@ConsiderForDowelannotation.
Apart from that if you want to control range / length / size of the values being generated, you can do that with androidx.annotations. Currently these 3 are the only supported ones:
androidx.annotation.IntRange: Control the range ofIntandLongpropertiesandroidx.annotation.FloatRange: Control the range ofFloatandDoublepropertiesandroidx.annotation.Size: Control the size ofString,ListandMapproperties
Dowel is quite flexible with the types it already supports, but there are certain limits on what all types are supported, and in general how Dowel works :
- Classes annotated with any of the
Dowelannotations (@Dowel,@DowelListor@ConsdierForDowel) should be concrete (non-abstract) - Primary constructors of classes annotated with
@Dowelannotation should not be private - Only classes extending
androidx.compose.ui.tooling.preview.PreviewParameterProvidercan be annotated with@ConsiderForDowel - Only classes already annotated with
@Dowelcan be annotated with@DowelList - All of the properties listed in the primary constructor of class annotated with
@Dowelcan only be of the following types:- Primitives (
Int,Long,Float,Double,Char,Boolean,String) androidx.compose.runtime.State,androidx.compose.runtime.MutableState,androidx.compose.ui.graphics.Colorkotlinx.coroutines.flow.Flow,SharedFlow,StateFlow(and their mutable types)- Functional types (high-order functions, lambdas)
@Dowelclasses (@Dowelclasses can be nested. A@Dowelannotated class can have properties of the type of classes which are again annotated with@Dowel)- Types for which a user-defined
PreviewParameterProviderexist (via the@ConsiderForDowelannotation) - Types which have a no-args constructor, or all of the properties of at-least a single constructor have default values
Sealedtypes- Kotlin Objects
EnumList,MutableList,Set,MutableSet,Map,MutableMap- Kotlinx immutable collections (
ImmutableList,PersistentList,ImmutableSet,PersistentSet,ImmutableMap,PersistentMap) Pair- Nullable types
- Properties with unsupported types which are nullable are allowed, and the generated value would always be null
- Properties with default values can have any type, as they are not considered while generating code, (unless the
overrideDefaultValuesproperty of theDowelannotation is toggled totruefor that class) - Types in the above mentioned list having generic type parameters (like
ListandMap) can only have@Dowelsupported types as their type parameters. LikeList<String>,Map<String, @Dowel class>
- Primitives (
- As far as a type is in above mentioned supported list, there are no practical limitations on how many times they may be nested.
Like
List<Map<Set<String>, List<@Dowel class>>>
Dowel ships with lint rules which cover all of the basic validation scenarios, and it will warn you even before you might compile the code if any improper usage is detected.
And for the things that lint doesn't catch, like issues with unsupported types of properties, meaningful error messages will be logged from KSP to nudge you in the right direction.
Copyright 2022 Jaya Surya Thotapalli
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.