Blog Infos
Author
Published
Topics
, , ,
Published
Iceberg image from Shtumf on goodfon.com

Jetpack Compose Preview are great, I’ve blogged about them before and also delivered talks on the subject. But sometimes you can’t, or don’t want to, use them for specific UI components. That does not mean you have to miss out on the usefulness of Previews!

One of the reasons why you might not be able to create a Preview for a composable is when you have an image that is downloaded from the internet. If you haven’t set up your image error handling correctly you’ll just see a blank space (or perhaps even a missing space) when your Preview renders.

Another occasion might be when you are including an API that requires initialisation somewhere in the rest of the app (which doesn’t happen when Previews are rendered). One of the times I have experienced this is when my Composable includes Firebase.analytics.logEvent and you will get a Render problem when displaying your Preview:

If you check what the problem is, you may see something like:

Failed to instantiate a Composition Local

This preview was unable to find a CompositionLocal. You might need to define it so it can render correctly.

Failed to instantiate a Composition Local
This preview was unable to find a CompositionLocal. 
You might need to define it so it can render correctly.

If you check the exception you will see:

java.lang.IllegalStateException: Default FirebaseApp is not initialized 
in this process null. Make sure to call FirebaseApp.initializeApp(Context) 
first.
 at com.google.firebase.FirebaseApp.getInstance(FirebaseApp.java:179)

Removing the Firebase call will fix the issue and the Preview will display.

Ideally, our Composables should be stateless and have all the information passed into them so Previews will not require internet or API access but often, this is just not practical especially if you want to initiate analytics calls.

And sometimes, you just want to show something different in your Preview than what you want to show in the live version because it makes more sense (although, this shouldn’t take the place of using well-crafted PreviewParameters and may cause bloat in your composables — so only use it when you have a good reason).

Enter LocalInspectionMode.

LocalInspectionMode provides a CompositionLocal (part of the data that is passed into the composable) which you can query to determine if this composable instance is locally inspectable — which is true if this composable is in a Preview.

Then, to check if your composable is in a preview you can use:

LocalInspectionMode.current

You can then use this to exclude code from being run if the composable is being run inside a Preview or add extra code if needed.

A simple example of conditionally including some text:

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Migrating to Jetpack Compose – an interop love story

Most of you are familiar with Jetpack Compose and its benefits. If you’re able to start anew and create a Compose-only app, you’re on the right track. But this talk might not be for you…
Watch Video

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer for Jetpack Compose
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engin ...
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer f ...
Google

Jobs

@Composable
fun TextComponent() {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(16.dp)
) {
Text(
if (LocalInspectionMode.current) {
"This is only displayed in a preview"
} else {
"This is only displayed in live code"
}
)
Text("This should always be displayed")
}
}
@Preview(showBackground = true)
@Composable
fun TextComponentPreview() {
TextComponent()
}

When we look at the Preview versus the emulator:

Preview on the left, emulator on the right.

 

We can see that text is different based on where it is displayed.

Some other examples:

@Composable
fun ImageComponent() {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(16.dp)
) {
val imageUrl = "https://img.goodfon.com/original/800x600/e/12/voda-more-aysberg-nebo.jpg"
if (LocalInspectionMode.current) {
// Show this image from the resources rather than loading an image from the internet
Image(
painter = painterResource(id = R.drawable.iceberg_preview),
contentDescription = null,
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth()
)
} else {
// Show this image in the live version
AsyncImage(
model = imageUrl,
contentDescription = null,
contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth()
)
}
}
}
@Composable
fun AnalyticsComponent() {
if (!LocalInspectionMode.current) {
// Firebase is not initialised in Previews and will cause an error
Firebase.analytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW, Bundle())
}
Column(modifier = Modifier.padding(16.dp)) {
Text("This is a component that contains an analytics call that runs on display")
}
}
@Composable
fun ViewModelComponent() {
val viewModel: MainViewModel = hiltViewModel()
val list: List<String> = if (!LocalInspectionMode.current) {
viewModel.someViewModelFlow.collectAsState(initial = emptyList()).value
} else {
// For Previews, we want to show the flow in a different state
// In this case, it would be better to create a composable from the actual content
// (the Row below) and pass in a list
listOf("d", "e", "f")
}
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(16.dp)) {
Text("From the view model flow: ")
list.forEach {
Text(it)
}
}
}
@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
val someViewModelFlow = MutableStateFlow(listOf("a", "b", "c"))
}

As we can see, the previews are showing the alternate content:

Pretty easy!

You can find all the code I have shown above on my Github here:

https://github.com/KatieBarnett/Experiments/tree/main/jc-previews?source=post_page—–afa9143ebda8——————————–

This article is previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu