Created
October 29, 2021 17:42
-
-
Save ncipollo/5d34ef0f533dbbad01fe6af932240bf6 to your computer and use it in GitHub Desktop.
Paparazzi + Compose
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Mock Hacks | |
internal object ContextSwizzle { | |
fun swizzle() { | |
ByteBuddyAgent.install() | |
val bridgeContextClass = contextClass() | |
val buddy = ByteBuddy() | |
buddy.redefine(bridgeContextClass) | |
.method( | |
ElementMatchers.named<MethodDescription>("getSystemService") | |
.and(ElementMatchers.takesArguments(String::class.java)) | |
) | |
.intercept(MethodDelegation.to(ContextMethodInterceptor::class.java)) | |
.make() | |
.load(bridgeContextClass.classLoader, ClassReloadingStrategy.fromInstalledAgent()) | |
} | |
fun unswizzle() { | |
val bridgeContextClass = contextClass() | |
ClassReloadingStrategy.fromInstalledAgent().reset(bridgeContextClass) | |
} | |
private fun contextClass() = Class.forName("com.android.layoutlib.bridge.android.BridgeContext") | |
} | |
internal object ContextMethodInterceptor { | |
@Suppress("unused") // Called via reflection | |
@JvmStatic | |
fun intercept(service: String, @This self: Any): Any { | |
return when (service) { | |
"layout_inflater" -> { | |
val fields = fieldMap(self) | |
fields["mBridgeInflater"]?.isAccessible = true | |
fields["mBridgeInflater"]!!.get(self) | |
} | |
"accessibility" -> mockClass("android.view.accessibility.AccessibilityManager") { | |
every { it["isEnabled"]() } returns false | |
} | |
"autofill" -> mockClass("android.view.autofill.AutofillManager") { | |
} | |
"clipboard" -> mockClass("android.content.ClipboardManager") | |
else -> error("No mock provided for service: $service") | |
} | |
} | |
private fun mockClass(className: String, block: (Any) -> Unit = {}) = | |
mockkClass(Class.forName(className).kotlin, relaxed = true).also(block) | |
private fun fieldMap(anyObject: Any): Map<String, Field> = | |
anyObject::class.java.declaredFields.map { it.name to it }.toMap() | |
} | |
// View Hacks | |
class SnapshotHostView @JvmOverloads constructor( | |
context: Context, | |
attrs: AttributeSet? = null | |
) : FrameLayout(context, attrs) { | |
override fun onAttachedToWindow() { | |
super.onAttachedToWindow() | |
val parentView = parent as? View | |
parentView?.let { | |
val treeLifecycle = SimulatedLifecycle(state = Lifecycle.State.CREATED, simulateObserver = true) | |
ViewTreeLifecycleOwner.set(it) { treeLifecycle } | |
ViewTreeSavedStateRegistryOwner.set(it, SimulatedSavedStateOwner(SimulatedLifecycle())) | |
} | |
} | |
} | |
internal class SimulatedSavedStateOwner(private val lifecycle: Lifecycle) : SavedStateRegistryOwner { | |
override fun getLifecycle() = lifecycle | |
override fun getSavedStateRegistry(): SavedStateRegistry = | |
SavedStateRegistryController.create(this) | |
.apply { performRestore(null) } | |
.savedStateRegistry | |
} | |
internal class SimulatedLifecycle( | |
private val state: State = State.INITIALIZED, | |
private val simulateObserver: Boolean = false | |
) : Lifecycle() { | |
override fun addObserver(observer: LifecycleObserver) { | |
if (simulateObserver) { | |
val eventObserver = observer as? LifecycleEventObserver | |
eventObserver?.onStateChanged({ this }, Event.ON_CREATE) | |
} | |
} | |
override fun removeObserver(observer: LifecycleObserver) = Unit | |
override fun getCurrentState() = state | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How to use this exactly with Paparazzi?
And what dependencies did you use?
Related to
cashapp/paparazzi@e71a024