How do you folks preview composable views stored u...
# multiplatform
s
How do you folks preview composable views stored under
commonMain/kotlin
in a Compose Multiplatform project? Neither IntelliJ nor AS seems to open a preview for them. Fleet requires
desktop
target and is extremely slow and limited in configuration options, so it's kind of painful and cannot compete with native AS dev experience. How do you do that?
1
w
Hey! Could you share for which reason you need a Desktop target when you try to preview in Fleet? Previews in Fleet without Desktop target are supposed to work as long as you don't call expects (we will have stubbed them) and all your dependencies support JVM. When it comes to performance, thanks for sharing your concern, please vote to keep track of our progress here. Also, we have Android preview support coming up under a feature flag, in some near future could opt in for more AS like preview performance.
👍 1
s
Could you share for which reason you need a Desktop target when you try to preview in Fleet? Previews in Fleet without Desktop target are supposed to work as long as you don't call expects (we will have stubbed them) and all your dependencies support JVM.
Initially I only had Android and iOS targets in
build.gradle.kts
file and Compose (Android) + Swift UI. The app could be built and worked for both platforms. Then I moved Compose UI to
commomMain/kotlin
and tried to preview some composable views. Preview pane failed with an error saying something like "some actual implementations are missing". It turned out that because of some reasons, preview pane expected "missing" actuals for
desktop
platform. After I added
jvm()
to
build.gradle.kts
and provided dummy
actual
implementations for it, preview started working. Fleet 1.37.84, Kotlin 2.0.0, AGP: 8.3.2, Compose plugin: 1.7.0-alpha01
When it comes to performance, thanks for sharing your concern, please vote to keep track of our progress here.
Also, we have Android preview support coming up under a feature flag, in some near future could opt in for more AS like preview performance.
Thanks for sharing the info, Wout! Voted for both issues and eager to have my hands on the update. For now, I'm juggling between Fleet and AS and copying views from one to another to be able to code views faster with a property preview.
w
Hmm, that's interesting! We practically do exactly what you did, but automatically. We should be able to generate the stubs for your expect declaration. Could you please share the expect declaration that was reported missing by the compiler? Also, just to make sure, this was a compiler error, and not a runtime error right? (If you use any expects in your preview, it is expected to throw a runtime exception and we will report this to the UI)
s
Given state: project has no
jvm()
platform, it builds and runs on Android. When opening a preview, "Compose preview" pane looks as in the screenshot. Compose Preview log:
Copy code
> Configure project :
>>>>>> Version: 5.743-jetpack-compose <<<<<<
Configuration '__COMPOSE_PREVIEW__RuntimeClasspath' was resolved during configuration time.
This is a build performance and scalability issue.
See <https://github.com/gradle/gradle/issues/2298>
Run with --info for a stacktrace.

Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to <https://docs.gradle.org/8.8/userguide/command_line_interface.html#sec:command_line_warnings> in the Gradle documentation.

BUILD SUCCESSFUL in 802ms
11 actionable tasks: 1 executed, 10 up-to-date
PREVIEW_HOST:SENT COMMAND 'ATTACH 2
PREVIEW_HOST:GOT COMMAND 'PREVIEW_CLASSPATH
PREVIEW_HOST:GOT [9961
PREVIEW_HOST:GOT COMMAND 'FRAME_REQUEST de.halfbit.gamineforagents.widgets.EmptyListWidgetKt.PreviewEmptyListWidget 21 478 986 4611686018427387904
PREVIEW_HOST:RENDERING 'de.halfbit.gamineforagents.widgets.EmptyListWidgetKt.PreviewEmptyListWidget' 956x1972@2.
PreviewLogger: Failed to invoke Composable Method 'de.halfbit.gamineforagents.widgets.EmptyListWidgetKt.PreviewEmptyListWidget'
nul
PREVIEW_HOST:SENT COMMAND 'ERROR
PREVIEW_HOST:SENT DATA [6067
No build errors at this stage. After adding jvm() as described above preview works.
Expected:
@Composable expect fun StatusBarTheme(darkTheme: Boolean)
expect fun createHttpClient(baseUrl: String): HttpClient
(ktor)
expect fun generateUuidV4(): Uuid
(Uuid - type in the project)
expect fun regexDotMatchAll(regex: String): Regex
(kotlin.text)
Copy code
@Composable
expect fun ScannerWidget(
    modifier: Modifier = Modifier,
    surfaceColor: Color,
    onTextScanned: (String) -> Unit,
)
w
Oh I see, so we successfully generated the stubs in your case. The issue here is that you are calling expect functions in your preview. We generate stubs that throw exceptions, and this exception is encountered during the preview. Any preview that does not call expects should work. Sadly there is not a lot we can do in this case, but Android Previews should solve your problem, since you have an Android target, Feel free to share if you think that there is something we could have done better here. I feel like the error message might not have made it clear that we could render a preview that does not call expects perhaps?
z
Would these preview capabilities in fleet make their way back to intellij idea?
s
@Wout Werkman Ah, you're right. Now it makes sense.
StatusBarTheme
is used in every preview and causes the issue. I commented the function out and not calling it. Now I'm getting another error (see the screenshot and attached file). I also added a simple snipped as below and it causes the same exception. Mayme it can say something to you.
Copy code
@Preview
@Composable
fun PreviewText() {
    Text("Hello there!")
}
‼️ UPDATE: After calling
./gradlew clean
and reopening the project in Fleet, it started to show previews. Please ignore the error.
I thought maybe in the case like mine, when one of generated stubs is called and crashes, Fleet could print out a list of stubs (all of them or only involved into exception staacktrace) saying that they must not be used in the preview. This would be a more clear statement which will save time and let devs fix the issue quicker. WDYT?
Another thought would about enhancements of the generated stubs. Since proper composable functions tend to not return any value, there is no strict necessity for throwing an exception from them. Can the stubs be just empty stubs doing nothing? I see the following cases: 1. A composable function has
content: @Composable () -> Unit
as the last parameter. Then the stub for this function could simply call the content(). 2. A composable function has no
content
parameter - such function can be just skipped. 3. For any other composable function (with many
content
- like parameters) , the stubs will throw. For 1. and 2. the Preview Pane can show a warning saying that some functions are not implemented and will do nothing. Then devs will be informed and know why something maybe is not working. This behavoir of stubs could be an option, in addition to always throwing stubs. Just an idea.
Is there a way of knowing if a composable runs inside a tool or as a real app? I thought I could use such a check to avoid calling
expect
functions in code.
w
Is there a way of knowing if a composable runs inside a tool or as a real app? I thought I could use such a check to avoid calling
expect
functions in code.
You could catch the exception that is thrown in the generate actual stub 😅 It throws
kotlin.NotImplementedError("__COMPOSE__PREVIEW__This_preview_uses_expects__COMPOSE__PREVIEW__")
You didn't ask for a pretty solution right? ;)
😜 1
Yeah, we have considered your suggestion for inserting non-throwing stubs such as empty body for
Unit
or
null
for nullables, empty string, list,
0
etc. But any such stubs might really confuse users. For all we know a user thinks that their code is wrong because the preview shows something weird, and after hours of debugging they realize that it's because I stubbed some default value. But actually, for `@Composable`s, this is a really interesting idea! We could make it a lot more clear to users. We could show purple-black 404 texture with a some text informing about the situation for example... I'll make sure it get's discussed! (Just calling
content()
seems a bit dangerous again, since we might get to the same situation I explained before :/ )
🙌 1
s
You didn't ask for a pretty solution right? 😉
That would work in my case. Having a separate function would be nice to have in the future 🙂
> But actually, for `@Composable`s, this is a really interesting idea! ... Yeah, discuss it internally. You might come to some interesting results 🙃
Thank you for your help @Wout Werkman and best regards from Mannheim to München!
🔥 1
🇩🇪 2