Upgrade to Pro — share decks privately, control downloads, hide ads and more …

A Snapshot Preview of Paparazzi 2.0 (droidcon N...

A Snapshot Preview of Paparazzi 2.0 (droidcon NYC 2024)

Since last Droidcon NYC, Paparazzi has seen continued increased adoption across the Android community!

In this session, we'll discuss:
* why pixel perfection is challenging af
* why Google's publishing of layoutlib is great for the community
* why we chose APNG for animations
* ...and more!

We'll also give a sneak peek to what's coming in 2.0, as well as why we're excited about it and you should be too!

Video: coming soon!

John Rodriguez

September 20, 2024
Tweet

More Decks by John Rodriguez

Other Decks in Technology

Transcript

  1. RFRR (src/main/res) RFRR (src/debug/res) RFRR (resValues) MRR (feature1:views) RFRR (src/main/res)

    RFRR (src/debug/res) RFRR (resValues) MRR (feature2:views) PRR (main app) AppRR (main repo) AarRR (./gradle/transforms/res/)
  2. • Initial pro fi ling shows heap usage drops from

    about ~52.9MB to ~1.7MB! • Now supports application and dynamic feature modules! • Various bugs around string resources also fi xed! • “Easier” to migrate to another build system
  3. • Initial pro fi ling shows heap usage drops from

    about ~52.9MB to ~1.7MB! • Now supports application and dynamic feature modules! • Various bugs around string resources also fi xed! • “Easier” to migrate to another build system
  4. class ValidateAccessibilityTest { @get:Rule val paparazzi = Paparazzi(validateAccessibility = true)

    @Test fun validateTextContrast() { paparazzi.snapshot( TextView(paparazzi.context).apply { textA= "Low Contrast" setTextColor(Color.WHITE) setBackgroundColor(Color.WHITE) } ) } } Accessibility issue of type LOW_CONTRAST on no-id: The item's text contrast ratio is 1.00. This ratio is based on a text color of #FFFFFF and background color of #FFFFFF. Consider increasing this item's text contrast ratio to 4.50 or greater.
  5. class ComposeTest { @get:Rule val paparazzi = Paparazzi( deviceConfig =

    DeviceConfig.WEAR_OS_SMALL_ROUND, ) @Test fun compose() { paparazzi.snapshot { HelloPaparazzi() } } }
  6. jar +--- META-INF | +--- services | \--- ... +---

    app | +--- cash | \--- Paparazzi.class | \--- ... ... aar +--- META-INF | +--- services | \--- ... +--- AndroidManifest.xml +--- assets | \--- ... +--- baseline-profile.txt +--- classes.jar +--- jni | \--- ... +--- lint.jar +--- proguard.txt +--- public.txt +--- R.txt \--- res +--- values \--- values.xml
  7. jar +--- META-INF | +--- services | \--- ... +---

    app | +--- cash | \--- Paparazzi.class | \--- ... ... aar +--- META-INF | +--- services | \--- ... +--- AndroidManifest.xml +--- assets | \--- ... +--- baseline-profile.txt +--- classes.jar +--- jni | \--- ... +--- lint.jar +--- proguard.txt +--- public.txt +--- R.txt \--- res +--- values \--- values.xml
  8. val aarAsJar = project.configurations .resolvable("implementatio n​ AarAsJar") a {a it.attributes.attribute(AndroidArtifacts.ARTIFACT_TYPE,

    "release") it.attributes.attribut e​​(​​​ Usage.USAGE_ATTRIBUTE, project.objects.named(Usage :: class.java, Usage.JAVA_API) ) } .incoming .artifactViewa{a it.attributes.attribute(AndroidArtifacts.ARTIFACT_TYPE, "aarAsJar") a }a .files
  9. project.configurations .getByName("implementatio n​ ") .dependencies .add(project.dependencies.create (​ aarAsJar)) // paparazzi/build.gradle

    apply plugin: 'aar2jar' dependencies { implementationAarAsJar libs.androidx.compose.ui.android implementationAarAsJar libs.androidx.activity }
  10. public fun snapshot( name: String? = null, composable: @Composable ()

    -> Unit ) { createFrameHandler(name).use { handler -> frameHandler = handler sdk.snapshot(composable) } } @Test fun compose() { paparazzi.snapshot { HelloPaparazzi() } }
  11. public fun snapshot( name: String? = null, composable: @Composable ()

    -> Unit ) { createFrameHandler(name).use { handler -> frameHandler = handler sdk.snapshot(composable) } } @Test fun compose() { paparazzi.snapshot { HelloPaparazzi() } }
  12. =

  13. package sun.java2d; public final class SunGraphics2D extends Graphics2D implements ConstrainableGraphics,

    Cloneable, DestSurfaceProvider { // cached state for various draw[String,Char,Byte] optimizations public FontInfo checkFontInfo(FontInfo info, Font font, FontRenderContext frc) { ... } }
  14. package sun.java2d; public final class SunGraphics2D extends Graphics2D implements ConstrainableGraphics,

    Cloneable, DestSurfaceProvider { // cached state for various draw[String,Char,Byte] optimizations public FontInfo checkFontInfo(FontInfo info, Font font, FontRenderContext frc) { ... if (FontUtilities.isMacOSX14 && (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_OFF)) { aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON; } ... } }
  15. package sun.java2d; public final class SunGraphics2D extends Graphics2D implements ConstrainableGraphics,

    Cloneable, DestSurfaceProvider { // cached state for various draw[String,Char,Byte] optimizations public FontInfo checkFontInfo(FontInfo info, Font font, FontRenderContext frc) { ... if (FontUtilities.isMacOSX14 && (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_OFF)) { aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON; } ... } }
  16. class LottieTest { @get:Rule val paparazzi = Paparazzi() @Test fun

    lottie() { val composition = LottieCompositionFactory.fromRawResSync( paparazzi.context, R.raw.lottie_logo ).value !! val view = LottieAnimationView(paparazzi.context) view.setComposition(composition) view.playAnimation() } }
  17. class LottieTest { @get:Rule val paparazzi = Paparazzi() @Test fun

    lottie() { val composition = LottieCompositionFactory.fromRawResSync( paparazzi.context, R.raw.lottie_logo ).value !! val view = LottieAnimationView(paparazzi.context) view.setComposition(composition) view.playAnimation() paparazzi.gif(view, "lottie", start = 0L, end = 5000L, fps = 60) } }
  18. RFRR (src/main/res) RFRR (src/debug/res) RFRR (resValues) MRR (feature1:views) RFRR (src/main/res)

    RFRR (src/debug/res) RFRR (resValues) MRR (feature2:views) PRR (main app) AppRR (main repo) AarRR (./gradle/transforms/res/)
  19. // com.android.ide.common.resources.ResourceTable Table< ResourceNamespace, ResourceType, ListMultimap<String, ResourceItem> > delegate: =

    Tables.newCustomTable( new HashMap <> (), () -> Maps.newEnumMap(ResourceType.class) ); R.string.title
  20. // com.android.ide.common.resources.ResourceTable Table< ResourceNamespace, ResourceType, ListMultimap<String, ResourceItem> > delegate: =

    Tables.newCustomTable( new HashMap <> (), () -> Maps.newEnumMap(ResourceType.class) ); ResourceNamespace = ResourceNamespace.RES_AUTO ResourceType = ResourceType.STRING key = "title" ResourceItem = module/src/main/res/values.xml@title="Hello world" R.string.title
  21. // com/sample/R.jar public final class R { class string {

    public static final int title = 0x22012; ... } ... }
  22. public fun main() { val me = "/Users/jrod" System.setProperty("paparazzi.project.dir", ".")

    System.setProperty("paparazzi.build.dir", "build") System.setProperty("paparazzi.artifacts.cache.dir", "$me/.gradle/caches") System.setProperty("paparazzi.test.resources", "$me/Development/paparazzi/sample/build/intermediates/paparazzi/debug/resources.json") System.setProperty("paparazzi.platform.data.root", "$me/.gradle/caches/transforms-4/ ... /layoutlib-native-macarm-2023.2.1-6c7316c") val output = File("test.png") val sdk = PaparazziSdk( onNewFrame = { image -> ImageIO.write(image, "PNG", output) } ) sdk.setup() sdk.prepare() sdk.snapshot(buildView(sdk.context)) sdk.teardown() println("Done!") }