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
Kotlin
version andKSP version
are 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 generationPreviewParameterProvider
for that class.@DowelList
: Same as@Dowel
, but generates aPreviewParameterProvider
of typeList<T>
whereT
is the class annotated with@DowelList
annotation. Rest of the behavior is same as the@Dowel
annotation.@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@ConsiderForDowel
annotation.
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 ofInt
andLong
propertiesandroidx.annotation.FloatRange
: Control the range ofFloat
andDouble
propertiesandroidx.annotation.Size
: Control the size ofString
,List
andMap
properties
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
Dowel
annotations (@Dowel
,@DowelList
or@ConsdierForDowel
) should be concrete (non-abstract) - Primary constructors of classes annotated with
@Dowel
annotation should not be private - Only classes extending
androidx.compose.ui.tooling.preview.PreviewParameterProvider
can be annotated with@ConsiderForDowel
- Only classes already annotated with
@Dowel
can be annotated with@DowelList
- All of the properties listed in the primary constructor of class annotated with
@Dowel
can 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.Color
kotlinx.coroutines.flow.Flow
,SharedFlow
,StateFlow
(and their mutable types)- Functional types (high-order functions, lambdas)
@Dowel
classes (@Dowel
classes can be nested. A@Dowel
annotated class can have properties of the type of classes which are again annotated with@Dowel
)- Types for which a user-defined
PreviewParameterProvider
exist (via the@ConsiderForDowel
annotation) - Types which have a no-args constructor, or all of the properties of at-least a single constructor have default values
Sealed
types- Kotlin Objects
Enum
List
,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
overrideDefaultValues
property of theDowel
annotation is toggled totrue
for that class) - Types in the above mentioned list having generic type parameters (like
List
andMap
) can only have@Dowel
supported 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.