sergio-sastre/ComposablePreviewScanner

Crash when Android-specific classes are used as top-level val

Closed this issue · 5 comments

We are trying to use ComposablePreviewScanner with Paparazzi.
We have some components that use Android-specific classes in it, and the scanner crashes when scanning these classes.

Example 1

private val SomePath = android.graphics.Path().apply { ... }

The scanner crashes with

java.lang.UnsatisfiedLinkError: 'long android.graphics.Path.nInit()'
	at android.graphics.Path.nInit(Native Method)
	at android.graphics.Path.<init>(Path.java:51)
	at mypackage.SomeComponentKt.<clinit>

Example 2

private val Typeface = android.graphics.Typeface.create("sans-serif-medium", Typeface.NORMAL)

The scanner crashes with

java.lang.NullPointerException: Cannot invoke "java.util.Map.get(Object)" because "android.graphics.Typeface.sSystemFontMap" is null
	at android.graphics.Typeface.getSystemDefaultTypeface(Typeface.java:1216)
	at android.graphics.Typeface.create_Original(Typeface.java:899)
	at android.graphics.Typeface_Delegate.create(Typeface_Delegate.java:109)
	at android.graphics.Typeface.create(Typeface.java:899)
	at mypackage.SomeComponentKt.<clinit>

After Paparazzi is initialized (when using Paparazzi as the test rule), the crash doesn't happen, but the preview list need to be generated before the test rule kicks in.

We can use something like lazy as a workaround, but I'm wondering if there's some other way that doesn't require code change.

@mxalbert1996
So I can reproduce it, and this happens only with Paparazzi: with Roborazzi this is not reproducible. I think the best to avoid this is to use some lazy initialization as you mentioned, or just make those vals compute those values every time

private val SomePath 
   get() = android.graphics.Path()

instead of this

private val SomePath = android.graphics.Path()

Another option would be to put your @Previews inside a class, which is supported since ComposablePreviewScanner 0.3.0.

Just let me know if it helps

I believe it might work without changing those global vals if you use ParameterizedRunner instead of the TestParameterInjector.

How to set it up is explained in the Roborazzi section. The setup the same, but use ParameterizedTestRunner instead of RobolectricParameterizedTest.

So only the test code would need changes

I’ll give it a try tonight and report back

I believe it might work without changing those global vals if you use ParameterizedRunner instead of the TestParameterInjector.

How to set it up is explained in the Roborazzi section. The setup the same, but use ParameterizedTestRunner instead of RobolectricParameterizedTest.

So only the test code would need changes

I’ll give it a try tonight and report back

Looks like using Parameterized instead of TestParameterInjector makes no difference.
The issue seems to be related to the way Paparazzi download android resources, which seems to interfere somehow with ComposablePreviewScanner, so my best bet is that you'll have to resort to one of the approaches I mentioned previously

@mxalbert1996
After checking ComposablePreviewScanner code I’ve understood where the problem lies (has to do with Class.forName()) and I have a couple of ideas on how to solve it without you needing to change the problematic vals that access android classes.

I’ll try to publish a new version ComposablePreviewScanner 0.3.1 with a fix for this issue next week

@mxalbert1996 should be fixed in 0.3.2 😊