Previews and the fastest way to iterate
Every change you have made so far meant a build, an install and a wait for the emulator, which on
an 8 GB laptop is long enough to lose your train of thought. For layout work there is a faster
loop: Android Studio renders composables directly in the editor, without a device, and re-renders
as you type. This lesson sets up previews for PriceCard that show every sample price and a large
font at once, and covers the one design rule that decides whether a screen can be previewed at
all.
A first preview
@Preview(showBackground = true)
@Composable
private fun PriceCardPreview() {
MaterialTheme {
PriceCard(price = SamplePrices.lasalgaonOnion)
}
}
Put it at the bottom of PriceCard.kt, with two new imports,
androidx.compose.ui.tooling.preview.Preview and com.riztechacademy.mandibhav.data.SamplePrices.
A preview is a composable with no parameters (with one
exception, below) and an @Preview annotation. It can be private: nothing calls it except
the tooling, and private keeps it out of autocomplete everywhere else.
Switch the editor to its split view (the view buttons sit at the top right of the editor) and the
card appears beside the code. Change titleLarge to headlineLarge and the preview updates
without a build. Larger changes, such as a new parameter, need a short rebuild that Android Studio
triggers itself; it is still far quicker than a run on the emulator.
Two dependencies make this work, and your starter project already has both:
ui-tooling-preview, which provides the annotation, and ui-tooling, which Android Studio uses to
render. The second is debugImplementation, so it is not packed into release builds.
Wrap previews in MaterialTheme. Without it, components still render, but with default colours
and text styles, so the preview stops matching the app.
Parameters worth knowing
| Parameter | Example | What it does |
|---|---|---|
name |
name = "Large text" |
Label above the preview |
group |
group = "Fonts" |
Lets you filter previews by group |
showBackground |
true |
White background instead of transparent |
backgroundColor |
0xFFFFF8E1 |
Background colour, an ARGB Long |
widthDp, heightDp |
widthDp = 320 |
Fixed size in dp; 320 is a small, cheap phone |
fontScale |
1.5f |
Simulates the user's font size setting |
locale |
locale = "mr" |
Uses string resources for that language |
uiMode |
Configuration.UI_MODE_NIGHT_YES |
Night mode, among others |
showSystemUi |
true |
Draws a status bar and navigation bar round it |
device |
device = "id:pixel_4" |
Renders at a specific device's size |
apiLevel |
apiLevel = 30 |
Renders with an older Android version |
Multiple previews of one composable
The large-text case is the one that catches layouts out. Many older users set the phone's font size up, and Mandi Bhav's users skew older than most apps'. Add a second preview:
@Preview(name = "Large text", showBackground = true, fontScale = 1.5f, widthDp = 320)
@Composable
private fun PriceCardLargeTextPreview() {
MaterialTheme {
PriceCard(price = SamplePrices.puneOnion)
}
}
At 1.5× on a 320 dp-wide screen, "Pune (Gultekdi) APMC · 7 Nov 2026" no longer fits on one line. It wraps, which is right, but it breaks between "7" and "Nov 2026", leaving the day stranded at the end of the first line. Nothing crashes and no test fails; you only find it by looking at a large font, and the preview lets you look without changing the emulator's settings. Practice 6 fixes it.
You can also stack @Preview annotations on one function, and Compose ships ready-made sets of
them, called multipreview annotations:
| Annotation | Renders |
|---|---|
@PreviewFontScale |
The same composable at a range of font scales |
@PreviewLightDark |
Light and dark mode |
@PreviewScreenSizes |
Phone, foldable, tablet and desktop sizes |
@PreviewDynamicColors |
Several wallpaper-based colour schemes |
The trap: a dark preview that looks light
Put @PreviewLightDark on PriceCardPreview and you get two previews that look the same. Nothing
is broken in the preview: MaterialTheme with no arguments always uses its default light colour
scheme, and Mandi Bhav has no dark theme yet. The night-mode setting reaches the app; the app
ignores it. Module 4 builds a theme that switches, and from then on this preview pair earns its
place. Until then, a dark preview is proof only that you have not built dark mode.
Every sample price at once
Writing one preview per price does not scale. A PreviewParameterProvider feeds a sequence of
values into a single preview function, which renders once per value. Create
ui/SamplePriceProvider.kt:
package com.riztechacademy.mandibhav.ui
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.riztechacademy.mandibhav.data.SamplePrices
import com.riztechacademy.mandibhav.model.CommodityPrice
/** Feeds every sample price into a preview, one preview per price. */
class SamplePriceProvider : PreviewParameterProvider<CommodityPrice> {
private val prices = SamplePrices.all
override val values: Sequence<CommodityPrice> = prices.asSequence()
override fun getDisplayName(index: Int): String? {
val price = prices.getOrNull(index) ?: return null
return "${price.commodity}, ${price.market}"
}
}
and the preview, in PriceCard.kt (import androidx.compose.ui.tooling.preview.PreviewParameter):
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun PriceCardSamplesPreview(
@PreviewParameter(SamplePriceProvider::class) price: CommodityPrice,
) {
MaterialTheme {
PriceCard(price = price)
}
}
That is the exception to "previews take no parameters": exactly one parameter, annotated with
@PreviewParameter and the provider class. Five previews appear, one per sample price.
getDisplayName labels each one "Onion, Lasalgaon APMC" instead of the default index-based name;
without it you get five previews you cannot tell apart by name. If a provider has hundreds of
values, @PreviewParameter(SamplePriceProvider::class, limit = 3) renders only the first three.
The provider must be a class with a constructor that takes no arguments, because the tooling
creates it by reflection. That is also why it reads from SamplePrices instead of receiving the
list.
The trap: previews that need a ViewModel or real data
Previews are rendered inside Android Studio by a cut-down copy of the Android framework. It has
no network, no file access, and only part of Context. So a composable that, inside itself,
creates a ViewModel, opens a database or calls an API, cannot be previewed. Android Studio shows
a render error instead, and the usual reaction is to give up on previews for that screen, which is
a big loss: screens are where previews pay most.
The fix is a design rule, and it is the same rule that made PriceCard straightforward to preview: a composable that
draws a screen takes the data it shows as parameters. Mandi Bhav's screen, coming in the next
lesson, is PricesScreen(prices: List<CommodityPrice>). It does not know where prices come from.
In the app, MainActivity hands it sample data now and real data later; in a preview, you hand it
SamplePrices.all. When the ViewModel arrives in module 5, it lives one level up, in a small
composable that reads from the ViewModel and calls PricesScreen with plain data. That outer
composable is not previewed; everything inside it is.
You will also meet LocalInspectionMode.current, which is true inside a preview, used to swap
real data for fake. It works, but it puts preview logic into production code, and it hides the
real problem, so reach for it only for third-party components you cannot restructure.
What previews cost. The sample data in SamplePrices is compiled into the app, and preview
functions are code you maintain. Both are small. The real cost is false confidence: a preview
runs on your laptop's fonts and a simplified framework, so before a release you still look at the
real screen on a real phone.
Check your work
What makes a function a preview. @Preview on a composable that takes no parameters, or one
@PreviewParameter parameter.
Why previews can be private. Only the tooling calls them.
Why the PriceCardLargeTextPreview matters for Mandi Bhav. Many users set a large font size;
it shows wrapping and clipping without changing device settings.
Why @PreviewLightDark shows two identical cards today. MaterialTheme() uses its default
light colour scheme; the app has no dark theme until module 4.
How many previews PriceCardSamplesPreview renders. Five, one per sample price.
Why a provider needs a no-argument constructor. The tooling instantiates it by reflection.
Why a composable that creates a ViewModel cannot be previewed. The preview renderer cannot
construct its dependencies (network, database, injection), so rendering fails.
The design rule that keeps screens previewable. Screens take the data they show as parameters.
Practice 2. Nothing clips. Every line wraps, including the range, and the card grows taller,
because nothing in PriceCard fixes a height. That is why lesson 4 warned against height(…) on
text containers.
Practice 4. The texts in PriceCard are Kotlin strings, not string resources, and there is no
values-mr folder. locale only changes which resources are loaded. Translating the card means
moving its words into strings.xml first.
Practice 6. Put the market and the date on separate lines (two Texts), or join the date's
parts with a non-breaking space, written in code as the escape "\u00A0", so the line can only
break before the date. A separate line is easier to read at large sizes; Mandi Bhav
keeps one line for now and module 4 revisits the card's layout.
Practice 7.
@Preview(name = "320 dp", widthDp = 320, showBackground = true)
@Preview(name = "320 dp, large text", widthDp = 320, fontScale = 1.5f, showBackground = true)
annotation class CheapPhonePreviews
Put @CheapPhonePreviews above @Composable on any preview function.
Practice
- Add the three previews to
PriceCard.ktand get all seven rendering (one, one large-text, five from the provider). - Change the large-text preview to
fontScale = 2fandwidthDp = 280. Does anything clip rather than wrap? - Add
@PreviewFontScaleabovePriceCardPreviewand compare the scales it renders. - Add a preview with
locale = "mr". Nothing changes. Explain why, using what you learnt about string resources in module 2. - Break it on purpose: give
SamplePriceProvidera constructor parameter. Read the render error Android Studio shows, then undo it. - Fix the stranded "7": make the date always stay on one line, without changing what it says.
- Harder: write your own multipreview annotation
@CheapPhonePreviewsthat renders a 320 dp-wide preview at font scale 1 and one at 1.5, and use it onPriceCardPreview.
Official documentation
- Preview your UI with composable previews — Every
@Previewparameter, multipreview, providers, and the limits of previews. - PreviewParameterProvider — The interface, including
countandgetDisplayName.
Next: the Material 3 components you will use daily, and Mandi Bhav's first real screen.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Selected students who finish a course can join our internship, with real-world projects and code review from a working engineer.
About the internship