Skip to content
This repository has been archived by the owner on Nov 22, 2024. It is now read-only.

Commit

Permalink
Descriptors
Browse files Browse the repository at this point in the history
Summary:
Introducing descriptors. Taken from the existing inspector, refreshed with a few things from Stetho, and translated to Kotlin.

Note: doesn't use FlipperObject or FlipperArray.

This is a very rough draft for them all, but can be used as basis for experimentation.

Reviewed By: LukeDefeo

Differential Revision: D38860763

fbshipit-source-id: 9f6bf4ad0e61fc40b0a773dfb8bfa80b1f397b3a
  • Loading branch information
lblasa authored and facebook-github-bot committed Aug 19, 2022
1 parent b5bdd56 commit 6adf1d6
Show file tree
Hide file tree
Showing 13 changed files with 794 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.flipper.plugins.uidebugger.descriptors

/**
* This class derives from Descriptor and provides a canonical implementation of ChainedDescriptor}.
*/
abstract class AbstractChainedDescriptor<T> : Descriptor<T>(), ChainedDescriptor<T> {
private var mSuper: Descriptor<T>? = null

override fun setSuper(superDescriptor: Descriptor<T>) {
if (superDescriptor !== mSuper) {
check(mSuper == null)
mSuper = superDescriptor
}
}

fun getSuper(): Descriptor<T>? {
return mSuper
}

/** Initialize a descriptor. */
override fun init() {
mSuper?.init()
onInit()
}

open fun onInit() {}

/**
* A globally unique ID used to identify a node in a hierarchy. If your node does not have a
* globally unique ID it is fine to rely on [System.identityHashCode].
*/
override fun getId(node: T): String? {
return onGetId(node)
}

abstract fun onGetId(node: T): String

/**
* The name used to identify this node in the inspector. Does not need to be unique. A good
* default is to use the class name of the node.
*/
override fun getName(node: T): String? {
return onGetName(node)
}

abstract fun onGetName(node: T): String

/** The children this node exposes in the inspector. */
override fun getChildren(node: T, children: MutableList<Any>) {
mSuper?.getChildren(node, children)
onGetChildren(node, children)
}

open fun onGetChildren(node: T, children: MutableList<Any>) {}

/**
* Get the data to show for this node in the sidebar of the inspector. The object will be showen
* in order and with a header matching the given name.
*/
override fun getData(node: T, builder: MutableMap<String, Any?>) {
mSuper?.getData(node, builder)
onGetData(node, builder)
}

open fun onGetData(node: T, builder: MutableMap<String, Any?>) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.flipper.plugins.uidebugger.descriptors

import android.app.Activity
import com.facebook.flipper.plugins.uidebugger.stetho.FragmentCompat

class ActivityDescriptor : AbstractChainedDescriptor<Activity>() {
override fun onInit() {}

override fun onGetId(activity: Activity): String {
return Integer.toString(System.identityHashCode(activity))
}

override fun onGetName(activity: Activity): String {
return activity.javaClass.simpleName
}

override fun onGetChildren(activity: Activity, children: MutableList<Any>) {
activity.window?.let { window -> children.add(activity.window) }

var fragments = getFragments(FragmentCompat.supportInstance, activity)
for (fragment in fragments) {
children.add(fragment)
}

fragments = getFragments(FragmentCompat.frameworkInstance, activity)
for (fragment in fragments) {
children.add(fragment)
}
}

override fun onGetData(activity: Activity, builder: MutableMap<String, Any?>) {}

private fun getFragments(compat: FragmentCompat<*, *, *, *>?, activity: Activity): List<Any> {
if (compat == null) {
return emptyList()
}

return compat?.getFragments(activity)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.flipper.plugins.uidebugger.descriptors

import android.app.Activity
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
import com.facebook.flipper.plugins.uidebugger.core.RootViewResolver

class ApplicationDescriptor : AbstractChainedDescriptor<ApplicationRef>() {
val rootResolver = RootViewResolver()

override fun onInit() {}

override fun onGetId(applicationRef: ApplicationRef): String {
return applicationRef.application.packageName
}

override fun onGetName(applicationRef: ApplicationRef): String {
val applicationInfo = applicationRef.application.getApplicationInfo()
val stringId = applicationInfo.labelRes
return if (stringId == 0) applicationInfo.nonLocalizedLabel.toString()
else applicationRef.application.getString(stringId)
}

override fun onGetChildren(applicationRef: ApplicationRef, children: MutableList<Any>) {
val activeRoots = rootResolver.listActiveRootViews()

activeRoots?.let { roots ->
for (root: RootViewResolver.RootView in roots) {
var added = false
for (activity: Activity in applicationRef.activitiesStack) {
if (activity.window.decorView == root.view) {
children.add(activity)
added = true

break
}
}
if (!added) {
children.add(root.view)
}
}
}
}

override fun onGetData(applicationRef: ApplicationRef, builder: MutableMap<String, Any?>) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.flipper.plugins.uidebugger.descriptors

import android.widget.Button

class ButtonDescriptor : AbstractChainedDescriptor<Button>() {
override fun init() {}

override fun onGetId(button: Button): String {
return Integer.toString(System.identityHashCode(button))
}

override fun onGetName(button: Button): String {
return button.javaClass.simpleName
}

override fun onGetData(button: Button, builder: MutableMap<String, Any?>) {}

override fun onGetChildren(button: Button, children: MutableList<Any>) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.flipper.plugins.uidebugger.descriptors

/**
* This interface marks a Descriptor in a way that is specially understood by the register. When
* registered for a particular class 'T', a Descriptor that implements this interface will be
* chained (via ChainedDescriptor.setSuper) to the Descriptor that is registered for the super class
* of 'T'. If the super class of 'T' doesn't have a registration, then the super-super class will be
* used (and so on). This allows you to implement Descriptor for any class in an inheritance
* hierarchy without having to couple it (via direct inheritance) to the super-class' Descriptor.
*
* To understand why this is useful, let's say you wanted to write a Descriptor for ListView. You
* have three options:
*
* The first option is to derive directly from Descriptor and write code to describe everything
* about instances of ListView, including details that are exposed by super classes such as
* ViewGroup, View, and even Object. This isn't generally a very good choice because it would
* require a lot of duplicated code amongst many descriptor implementations.
*
* The second option is to derive your ListViewDescriptor from ViewGroupDescriptor and only
* implement code to describe how ListView differs from ViewGroup. This will result in a class
* hierarchy that is parallel to the one that you are describing, but is also not a good choice for
* two reasons (let's assume for the moment that ViewGroupDescriptor is deriving from
* ViewDescriptor). The first problem is that you will need to write code for aggregating results
* from the super-class in methods such as Descriptor.getChildren and Descriptor.getAttributes. The
* second problem is that you'd end up with a log of fragility if you ever want to implement a
* descriptor for classes that are in-between ViewGroup and ListView, e.g. AbsListView. Any
* descriptor that derived from ViewGroupDescriptor and described a class deriving from AbsListView
* would have to be modified to now derive from AbsListViewDescriptor.
*
* The third option is to implement ChainedDescriptor (e.g. by deriving from
* AbstractChainedDescriptor) which solves all of these issues for you.
*/
interface ChainedDescriptor<T> {
fun setSuper(superDescriptor: Descriptor<T>)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.flipper.plugins.uidebugger.descriptors

abstract class Descriptor<T> : NodeDescriptor<T> {
var descritorRegister: DescriptorRegister? = null
fun setDescriptorRegister(descriptorMapping: DescriptorRegister) {
this.descritorRegister = descriptorMapping
}

protected fun descriptorForClass(clazz: Class<*>): Descriptor<*>? {
descritorRegister?.let { register ->
return register.descriptorForClass(clazz)
}
return null
}

protected fun descriptorForObject(obj: Any): Descriptor<*>? {
descritorRegister?.let { register ->
return register.descriptorForClass(obj::class.java)
}
return null
}

// override fun inspect(obj: Any): FlipperObject {
// val descriptor = descriptorForObject(obj)
// descriptor?.let { descriptor ->
// return (descriptor as Descriptor<Any>).get(obj)
// }
//
// return FlipperObject.Builder().build()
// }
//
// override fun get(node: T): FlipperObject {
// val builder = FlipperObject.Builder()
//
// val propsBuilder = FlipperObject.Builder()
// getData(node, propsBuilder)
//
// val childrenBuilder = FlipperArray.Builder()
// getChildren(node, childrenBuilder)
//
// builder
// .put("key", getId(node))
// .put("title", getName(node))
// .put("data", propsBuilder)
// .put("children", childrenBuilder)
//
// return builder.build()
// }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.flipper.plugins.uidebugger.descriptors

import android.app.Activity
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.widget.Button
import android.widget.TextView
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef

class DescriptorRegister {
private val register: MutableMap<Class<*>, Descriptor<*>> = HashMap()

companion object {

fun withDefaults(): DescriptorRegister {
val mapping = DescriptorRegister()
mapping.register(Any::class.java, ObjectDescriptor())
mapping.register(ApplicationRef::class.java, ApplicationDescriptor())
mapping.register(Activity::class.java, ActivityDescriptor())
mapping.register(Window::class.java, WindowDescriptor())
mapping.register(ViewGroup::class.java, ViewGroupDescriptor())
mapping.register(View::class.java, ViewDescriptor())
mapping.register(TextView::class.java, TextViewDescriptor())
mapping.register(Button::class.java, ButtonDescriptor())

for (clazz in mapping.register.keys) {
val descriptor: Descriptor<*>? = mapping.register[clazz]
descriptor?.let { descriptor ->
if (descriptor is ChainedDescriptor<*>) {
val chainedDescriptor = descriptor as ChainedDescriptor<Any>
val superClass: Class<*> = clazz.getSuperclass()
val superDescriptor: Descriptor<*>? = mapping.descriptorForClass(superClass)
superDescriptor?.let { superDescriptor ->
chainedDescriptor.setSuper(superDescriptor as Descriptor<Any>)
}
}
}
}

for (descriptor in mapping.register.values) {
descriptor.setDescriptorRegister(mapping)
}

return mapping
}
}

fun <T> register(clazz: Class<T>, descriptor: Descriptor<T>) {
register[clazz] = descriptor
}

fun descriptorForClass(clazz: Class<*>): Descriptor<*>? {
var clazz = clazz
while (!register.containsKey(clazz)) {
clazz = clazz.superclass
}
return register[clazz]
}
}
Loading

0 comments on commit 6adf1d6

Please sign in to comment.