Enable and apply Ktfmt to xplat/simplesql, xplat/sonar, and xplat/spectrum
Summary: As title. Reviewed By: zertosh Differential Revision: D30425160 fbshipit-source-id: c72d270d7cd3f30990aac55e33e8f72d60ed5fe2
This commit is contained in:
committed by
Facebook GitHub Bot
parent
8e2a839f9d
commit
1db39b8171
@@ -14,32 +14,34 @@ import shark.HeapAnalysis
|
|||||||
import shark.HeapAnalysisSuccess
|
import shark.HeapAnalysisSuccess
|
||||||
|
|
||||||
class FlipperLeakListener : OnHeapAnalyzedListener {
|
class FlipperLeakListener : OnHeapAnalyzedListener {
|
||||||
private val leaks: MutableList<Leak> = mutableListOf()
|
private val leaks: MutableList<Leak> = mutableListOf()
|
||||||
|
|
||||||
private val defaultListener = DefaultOnHeapAnalyzedListener.create()
|
private val defaultListener = DefaultOnHeapAnalyzedListener.create()
|
||||||
|
|
||||||
override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
|
override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
|
||||||
leaks.addAll(heapAnalysis.toLeakList())
|
leaks.addAll(heapAnalysis.toLeakList())
|
||||||
|
|
||||||
AndroidFlipperClient.getInstanceIfInitialized()?.let { client ->
|
AndroidFlipperClient.getInstanceIfInitialized()?.let { client ->
|
||||||
(client.getPlugin(LeakCanary2FlipperPlugin.ID) as? LeakCanary2FlipperPlugin)
|
(client.getPlugin(LeakCanary2FlipperPlugin.ID) as? LeakCanary2FlipperPlugin)?.reportLeaks(
|
||||||
?.reportLeaks(leaks)
|
leaks)
|
||||||
}
|
|
||||||
|
|
||||||
defaultListener.onHeapAnalyzed(heapAnalysis)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun HeapAnalysis.toLeakList(): List<Leak> {
|
defaultListener.onHeapAnalyzed(heapAnalysis)
|
||||||
return if (this is HeapAnalysisSuccess) {
|
}
|
||||||
allLeaks.mapNotNull {
|
|
||||||
if (it.leakTraces.isNotEmpty()) {
|
private fun HeapAnalysis.toLeakList(): List<Leak> {
|
||||||
it.leakTraces[0].toLeak(it.shortDescription)
|
return if (this is HeapAnalysisSuccess) {
|
||||||
} else {
|
allLeaks
|
||||||
null
|
.mapNotNull {
|
||||||
}
|
if (it.leakTraces.isNotEmpty()) {
|
||||||
}.toList()
|
it.leakTraces[0].toLeak(it.shortDescription)
|
||||||
} else {
|
} else {
|
||||||
emptyList()
|
null
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,40 +14,40 @@ private const val REPORT_LEAK_EVENT = "reportLeak2"
|
|||||||
private const val CLEAR_EVENT = "clear"
|
private const val CLEAR_EVENT = "clear"
|
||||||
|
|
||||||
class LeakCanary2FlipperPlugin : FlipperPlugin {
|
class LeakCanary2FlipperPlugin : FlipperPlugin {
|
||||||
private val leaks: MutableList<Leak> = mutableListOf()
|
private val leaks: MutableList<Leak> = mutableListOf()
|
||||||
private val alreadySeenLeakSignatures: MutableSet<String> = mutableSetOf()
|
private val alreadySeenLeakSignatures: MutableSet<String> = mutableSetOf()
|
||||||
private var connection: FlipperConnection? = null
|
private var connection: FlipperConnection? = null
|
||||||
|
|
||||||
override fun getId() = ID
|
override fun getId() = ID
|
||||||
|
|
||||||
override fun onConnect(connection: FlipperConnection?) {
|
override fun onConnect(connection: FlipperConnection?) {
|
||||||
this.connection = connection
|
this.connection = connection
|
||||||
connection?.receive(CLEAR_EVENT) { _, _ -> leaks.clear() }
|
connection?.receive(CLEAR_EVENT) { _, _ -> leaks.clear() }
|
||||||
sendLeakList()
|
sendLeakList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDisconnect() {
|
||||||
|
connection = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun runInBackground() = false
|
||||||
|
|
||||||
|
internal fun reportLeaks(leaks: List<Leak>) {
|
||||||
|
for (leak in leaks) {
|
||||||
|
if (leak.signature !in alreadySeenLeakSignatures) {
|
||||||
|
this.leaks.add(leak)
|
||||||
|
alreadySeenLeakSignatures.add(leak.signature)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDisconnect() {
|
sendLeakList()
|
||||||
connection = null
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun runInBackground() = false
|
private fun sendLeakList() {
|
||||||
|
connection?.send(REPORT_LEAK_EVENT, LeakCanary2Report(leaks).toFlipperObject())
|
||||||
|
}
|
||||||
|
|
||||||
internal fun reportLeaks(leaks: List<Leak>) {
|
companion object {
|
||||||
for (leak in leaks) {
|
const val ID = "LeakCanary"
|
||||||
if (leak.signature !in alreadySeenLeakSignatures) {
|
}
|
||||||
this.leaks.add(leak)
|
|
||||||
alreadySeenLeakSignatures.add(leak.signature)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendLeakList()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sendLeakList() {
|
|
||||||
connection?.send(REPORT_LEAK_EVENT, LeakCanary2Report(leaks).toFlipperObject())
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val ID = "LeakCanary"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,135 +10,133 @@ package com.facebook.flipper.plugins.leakcanary2
|
|||||||
import com.facebook.flipper.core.FlipperArray
|
import com.facebook.flipper.core.FlipperArray
|
||||||
import com.facebook.flipper.core.FlipperObject
|
import com.facebook.flipper.core.FlipperObject
|
||||||
import com.facebook.flipper.core.FlipperValue
|
import com.facebook.flipper.core.FlipperValue
|
||||||
|
import java.util.UUID
|
||||||
import shark.LeakTrace
|
import shark.LeakTrace
|
||||||
import shark.LeakTraceObject
|
import shark.LeakTraceObject
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
internal data class LeakCanary2Report(val leaks: List<Leak>) : FlipperValue {
|
internal data class LeakCanary2Report(val leaks: List<Leak>) : FlipperValue {
|
||||||
override fun toFlipperObject(): FlipperObject = FlipperObject.Builder()
|
override fun toFlipperObject(): FlipperObject =
|
||||||
.put("leaks", leaks.map { it.toFlipperObject() }.toFlipperArray())
|
FlipperObject.Builder()
|
||||||
.build()
|
.put("leaks", leaks.map { it.toFlipperObject() }.toFlipperArray())
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal data class Leak(
|
internal data class Leak(
|
||||||
val title: String,
|
val title: String,
|
||||||
val root: String,
|
val root: String,
|
||||||
val elements: Map<String, Element>,
|
val elements: Map<String, Element>,
|
||||||
val retainedSize: String,
|
val retainedSize: String,
|
||||||
val signature: String,
|
val signature: String,
|
||||||
val details: String
|
val details: String
|
||||||
) : FlipperValue {
|
) : FlipperValue {
|
||||||
override fun toFlipperObject(): FlipperObject {
|
override fun toFlipperObject(): FlipperObject {
|
||||||
return FlipperObject.Builder()
|
return FlipperObject.Builder()
|
||||||
.put("title", title)
|
.put("title", title)
|
||||||
.put("root", root)
|
.put("root", root)
|
||||||
.put("elements", elements.toFlipperObject())
|
.put("elements", elements.toFlipperObject())
|
||||||
.put("retainedSize", retainedSize)
|
.put("retainedSize", retainedSize)
|
||||||
.put("details", details)
|
.put("details", details)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Map<String, FlipperValue>.toFlipperObject(): FlipperObject =
|
private fun Map<String, FlipperValue>.toFlipperObject(): FlipperObject =
|
||||||
mapValues { it.value.toFlipperObject() }.toFlipperObject()
|
mapValues { it.value.toFlipperObject() }.toFlipperObject()
|
||||||
|
|
||||||
@JvmName("toFlipperObjectStringFlipperObject")
|
@JvmName("toFlipperObjectStringFlipperObject")
|
||||||
private fun Map<String, FlipperObject>.toFlipperObject(): FlipperObject =
|
private fun Map<String, FlipperObject>.toFlipperObject(): FlipperObject =
|
||||||
asIterable()
|
asIterable()
|
||||||
.fold(FlipperObject.Builder()) { builder, entry ->
|
.fold(FlipperObject.Builder()) { builder, entry -> builder.put(entry.key, entry.value) }
|
||||||
builder.put(entry.key, entry.value)
|
.build()
|
||||||
}
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun LeakTrace.toLeak(title: String): Leak {
|
internal fun LeakTrace.toLeak(title: String): Leak {
|
||||||
val elements = getElements()
|
val elements = getElements()
|
||||||
return Leak(
|
return Leak(
|
||||||
title = title,
|
title = title,
|
||||||
elements = elements.toMap(),
|
elements = elements.toMap(),
|
||||||
retainedSize = retainedHeapByteSize?.let { "$it bytes" } ?: "unknown size",
|
retainedSize = retainedHeapByteSize?.let { "$it bytes" } ?: "unknown size",
|
||||||
signature = signature,
|
signature = signature,
|
||||||
root = elements.first().first,
|
root = elements.first().first,
|
||||||
details = "$this"
|
details = "$this")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun LeakTrace.getElements(): List<Pair<String, Element>> {
|
private fun LeakTrace.getElements(): List<Pair<String, Element>> {
|
||||||
val referenceElements = referencePath.map { reference ->
|
val referenceElements =
|
||||||
val id = UUID.randomUUID().toString()
|
referencePath
|
||||||
id to Element(id, reference.originObject)
|
.map { reference ->
|
||||||
}.toMutableList()
|
val id = UUID.randomUUID().toString()
|
||||||
|
id to Element(id, reference.originObject)
|
||||||
|
}
|
||||||
|
.toMutableList()
|
||||||
|
|
||||||
val leakId = UUID.randomUUID().toString()
|
val leakId = UUID.randomUUID().toString()
|
||||||
referenceElements.add(leakId to Element(leakId, leakingObject))
|
referenceElements.add(leakId to Element(leakId, leakingObject))
|
||||||
|
|
||||||
return referenceElements.mapIndexed { index, pair ->
|
return referenceElements.mapIndexed { index, pair ->
|
||||||
pair.first to if (index == referenceElements.lastIndex) pair.second else pair.second.copy(
|
pair.first to
|
||||||
children = listOf(referenceElements[index + 1].second.id)
|
if (index == referenceElements.lastIndex) pair.second
|
||||||
)
|
else pair.second.copy(children = listOf(referenceElements[index + 1].second.id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal data class Element(
|
internal data class Element(
|
||||||
val id: String,
|
val id: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val expanded: Boolean = true,
|
val expanded: Boolean = true,
|
||||||
val children: List<String> = emptyList(),
|
val children: List<String> = emptyList(),
|
||||||
val attributes: List<ElementAttribute>,
|
val attributes: List<ElementAttribute>,
|
||||||
val decoration: String = ""
|
val decoration: String = ""
|
||||||
) : FlipperValue {
|
) : FlipperValue {
|
||||||
constructor(id: String, leakObject: LeakTraceObject) : this(
|
constructor(
|
||||||
id = id,
|
id: String,
|
||||||
name = "${leakObject.className} (${leakObject.typeName})",
|
leakObject: LeakTraceObject
|
||||||
attributes = listOf(
|
) : this(
|
||||||
ElementAttribute("leaking", leakObject.leakingStatus.shortName),
|
id = id,
|
||||||
ElementAttribute("retaining", leakObject.retaining)
|
name = "${leakObject.className} (${leakObject.typeName})",
|
||||||
)
|
attributes =
|
||||||
)
|
listOf(
|
||||||
|
ElementAttribute("leaking", leakObject.leakingStatus.shortName),
|
||||||
|
ElementAttribute("retaining", leakObject.retaining)))
|
||||||
|
|
||||||
override fun toFlipperObject(): FlipperObject {
|
override fun toFlipperObject(): FlipperObject {
|
||||||
return FlipperObject.Builder()
|
return FlipperObject.Builder()
|
||||||
.put("id", id)
|
.put("id", id)
|
||||||
.put("name", name)
|
.put("name", name)
|
||||||
.put("expanded", expanded)
|
.put("expanded", expanded)
|
||||||
.put("children", children.toFlipperArray())
|
.put("children", children.toFlipperArray())
|
||||||
.put("attributes", attributes.toFlipperArray())
|
.put("attributes", attributes.toFlipperArray())
|
||||||
.put("data", EMPTY_FLIPPER_OBJECT)
|
.put("data", EMPTY_FLIPPER_OBJECT)
|
||||||
.put("decoration", decoration)
|
.put("decoration", decoration)
|
||||||
.put("extraInfo", EMPTY_FLIPPER_OBJECT)
|
.put("extraInfo", EMPTY_FLIPPER_OBJECT)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmName("toFlipperArrayFlipperValue")
|
@JvmName("toFlipperArrayFlipperValue")
|
||||||
private fun Iterable<FlipperValue>.toFlipperArray(): FlipperArray =
|
private fun Iterable<FlipperValue>.toFlipperArray(): FlipperArray =
|
||||||
map { it.toFlipperObject() }.toFlipperArray()
|
map { it.toFlipperObject() }.toFlipperArray()
|
||||||
|
|
||||||
@JvmName("toFlipperArrayString")
|
@JvmName("toFlipperArrayString")
|
||||||
private fun Iterable<String>.toFlipperArray(): FlipperArray =
|
private fun Iterable<String>.toFlipperArray(): FlipperArray =
|
||||||
fold(FlipperArray.Builder()) { builder, row -> builder.put(row) }.build()
|
fold(FlipperArray.Builder()) { builder, row -> builder.put(row) }.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Iterable<FlipperObject>.toFlipperArray(): FlipperArray =
|
internal fun Iterable<FlipperObject>.toFlipperArray(): FlipperArray =
|
||||||
fold(FlipperArray.Builder()) { builder, row -> builder.put(row) }.build()
|
fold(FlipperArray.Builder()) { builder, row -> builder.put(row) }.build()
|
||||||
|
|
||||||
private val LeakTraceObject.LeakingStatus.shortName: String
|
private val LeakTraceObject.LeakingStatus.shortName: String
|
||||||
get() = when (this) {
|
get() =
|
||||||
|
when (this) {
|
||||||
LeakTraceObject.LeakingStatus.NOT_LEAKING -> "N"
|
LeakTraceObject.LeakingStatus.NOT_LEAKING -> "N"
|
||||||
LeakTraceObject.LeakingStatus.LEAKING -> "Y"
|
LeakTraceObject.LeakingStatus.LEAKING -> "Y"
|
||||||
LeakTraceObject.LeakingStatus.UNKNOWN -> "?"
|
LeakTraceObject.LeakingStatus.UNKNOWN -> "?"
|
||||||
}
|
}
|
||||||
private val LeakTraceObject.retaining: String
|
private val LeakTraceObject.retaining: String
|
||||||
get() = retainedHeapByteSize?.let { "$it bytes ($retainedObjectCount objects)" } ?: "unknown"
|
get() = retainedHeapByteSize?.let { "$it bytes ($retainedObjectCount objects)" } ?: "unknown"
|
||||||
|
|
||||||
private val EMPTY_FLIPPER_OBJECT = FlipperObject.Builder().build()
|
private val EMPTY_FLIPPER_OBJECT = FlipperObject.Builder().build()
|
||||||
|
|
||||||
data class ElementAttribute(
|
data class ElementAttribute(val name: String, val value: String) : FlipperValue {
|
||||||
val name: String,
|
override fun toFlipperObject(): FlipperObject {
|
||||||
val value: String
|
return FlipperObject.Builder().put("name", name).put("value", value).build()
|
||||||
) : FlipperValue {
|
}
|
||||||
override fun toFlipperObject(): FlipperObject {
|
|
||||||
return FlipperObject.Builder()
|
|
||||||
.put("name", name)
|
|
||||||
.put("value", value)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,35 +16,32 @@ import com.facebook.flipper.plugins.retrofit2protobuf.adapter.RetrofitServiceToG
|
|||||||
import com.facebook.flipper.plugins.retrofit2protobuf.model.CallNestedMessagesPayload
|
import com.facebook.flipper.plugins.retrofit2protobuf.model.CallNestedMessagesPayload
|
||||||
|
|
||||||
object SendProtobufToFlipperFromRetrofit {
|
object SendProtobufToFlipperFromRetrofit {
|
||||||
operator fun invoke(baseUrl: String, service: Class<*>) {
|
operator fun invoke(baseUrl: String, service: Class<*>) {
|
||||||
getNetworkPlugin()?.addProtobufDefinitions(
|
getNetworkPlugin()
|
||||||
baseUrl,
|
?.addProtobufDefinitions(baseUrl, generateProtobufDefinitions(service).toFlipperArray())
|
||||||
generateProtobufDefinitions(service).toFlipperArray()
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getNetworkPlugin(): NetworkFlipperPlugin? {
|
private fun getNetworkPlugin(): NetworkFlipperPlugin? {
|
||||||
return AndroidFlipperClient.getInstanceIfInitialized()?.let { client ->
|
return AndroidFlipperClient.getInstanceIfInitialized()?.let { client ->
|
||||||
client.getPlugin(NetworkFlipperPlugin.ID) as? NetworkFlipperPlugin
|
client.getPlugin(NetworkFlipperPlugin.ID) as? NetworkFlipperPlugin
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun generateProtobufDefinitions(service: Class<*>): List<CallNestedMessagesPayload> {
|
private fun generateProtobufDefinitions(service: Class<*>): List<CallNestedMessagesPayload> {
|
||||||
return RetrofitServiceToGenericCallDefinitions(service).let { definitions ->
|
return RetrofitServiceToGenericCallDefinitions(service)
|
||||||
GenericCallDefinitionsToMessageDefinitionsIfProtobuf(definitions)
|
.let { definitions -> GenericCallDefinitionsToMessageDefinitionsIfProtobuf(definitions) }
|
||||||
}.let { messages ->
|
.let { messages ->
|
||||||
messages.map {
|
messages.map {
|
||||||
CallNestedMessagesPayload(
|
CallNestedMessagesPayload(
|
||||||
path = it.path,
|
path = it.path,
|
||||||
method = it.method,
|
method = it.method,
|
||||||
requestMessageFullName = it.requestMessageFullName,
|
requestMessageFullName = it.requestMessageFullName,
|
||||||
requestDefinitions = it.requestModel,
|
requestDefinitions = it.requestModel,
|
||||||
responseMessageFullName = it.responseMessageFullName,
|
responseMessageFullName = it.responseMessageFullName,
|
||||||
responseDefinitions = it.responseModel
|
responseDefinitions = it.responseModel)
|
||||||
)
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Iterable<FlipperValue>.toFlipperArray(): FlipperArray =
|
private fun Iterable<FlipperValue>.toFlipperArray(): FlipperArray =
|
||||||
|
|||||||
@@ -12,23 +12,22 @@ import com.facebook.flipper.plugins.retrofit2protobuf.model.GenericCallDefinitio
|
|||||||
import me.haroldmartin.protobufjavatoprotobufjs.ProtobufGeneratedJavaToProtobufJs
|
import me.haroldmartin.protobufjavatoprotobufjs.ProtobufGeneratedJavaToProtobufJs
|
||||||
|
|
||||||
internal object GenericCallDefinitionsToMessageDefinitionsIfProtobuf {
|
internal object GenericCallDefinitionsToMessageDefinitionsIfProtobuf {
|
||||||
operator fun invoke(callDefinitions: List<GenericCallDefinition>): List<FullNamedMessagesCallDefinition> {
|
operator fun invoke(
|
||||||
return callDefinitions.mapNotNull { definition ->
|
callDefinitions: List<GenericCallDefinition>
|
||||||
val responseRootAndMessages = definition.responseType?.let {
|
): List<FullNamedMessagesCallDefinition> {
|
||||||
ProtobufGeneratedJavaToProtobufJs(it)
|
return callDefinitions.mapNotNull { definition ->
|
||||||
}
|
val responseRootAndMessages =
|
||||||
val requestRootAndMessages = definition.requestType?.let {
|
definition.responseType?.let { ProtobufGeneratedJavaToProtobufJs(it) }
|
||||||
ProtobufGeneratedJavaToProtobufJs(it)
|
val requestRootAndMessages =
|
||||||
}
|
definition.requestType?.let { ProtobufGeneratedJavaToProtobufJs(it) }
|
||||||
|
|
||||||
FullNamedMessagesCallDefinition(
|
FullNamedMessagesCallDefinition(
|
||||||
path = definition.path,
|
path = definition.path,
|
||||||
method = definition.method,
|
method = definition.method,
|
||||||
responseMessageFullName = responseRootAndMessages?.rootFullName,
|
responseMessageFullName = responseRootAndMessages?.rootFullName,
|
||||||
responseModel = responseRootAndMessages?.descriptors,
|
responseModel = responseRootAndMessages?.descriptors,
|
||||||
requestMessageFullName = requestRootAndMessages?.rootFullName,
|
requestMessageFullName = requestRootAndMessages?.rootFullName,
|
||||||
requestModel = requestRootAndMessages?.descriptors
|
requestModel = requestRootAndMessages?.descriptors)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,65 +13,64 @@ import java.lang.reflect.ParameterizedType
|
|||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
internal object RetrofitServiceToGenericCallDefinitions {
|
internal object RetrofitServiceToGenericCallDefinitions {
|
||||||
@Suppress("LoopWithTooManyJumpStatements")
|
@Suppress("LoopWithTooManyJumpStatements")
|
||||||
operator fun invoke(service: Class<*>): List<GenericCallDefinition> {
|
operator fun invoke(service: Class<*>): List<GenericCallDefinition> {
|
||||||
val methodToProtobufDefinition = mutableListOf<GenericCallDefinition>()
|
val methodToProtobufDefinition = mutableListOf<GenericCallDefinition>()
|
||||||
for (method in service.declaredMethods) {
|
for (method in service.declaredMethods) {
|
||||||
val responseType = method.innerGenericReturnClass ?: continue
|
val responseType = method.innerGenericReturnClass ?: continue
|
||||||
val (path, httpMethod) = method.annotations.urlPathAndMethod ?: continue
|
val (path, httpMethod) = method.annotations.urlPathAndMethod ?: continue
|
||||||
methodToProtobufDefinition.add(
|
methodToProtobufDefinition.add(
|
||||||
GenericCallDefinition(
|
GenericCallDefinition(
|
||||||
path = path,
|
path = path,
|
||||||
method = httpMethod,
|
method = httpMethod,
|
||||||
responseType = responseType,
|
responseType = responseType,
|
||||||
requestType = method.requestBodyType
|
requestType = method.requestBodyType))
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return methodToProtobufDefinition
|
|
||||||
}
|
}
|
||||||
|
return methodToProtobufDefinition
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val Array<Annotation>.urlPathAndMethod: Pair<String, String>?
|
private val Array<Annotation>.urlPathAndMethod: Pair<String, String>?
|
||||||
get() {
|
get() {
|
||||||
var path: Pair<String, String>? = null
|
var path: Pair<String, String>? = null
|
||||||
for (a in this) {
|
for (a in this) {
|
||||||
path = when (a.annotationClass) {
|
path =
|
||||||
retrofit2.http.DELETE::class -> (a as retrofit2.http.DELETE).value to "DELETE"
|
when (a.annotationClass) {
|
||||||
retrofit2.http.GET::class -> (a as retrofit2.http.GET).value to "GET"
|
retrofit2.http.DELETE::class -> (a as retrofit2.http.DELETE).value to "DELETE"
|
||||||
retrofit2.http.HEAD::class -> (a as retrofit2.http.HEAD).value to "HEAD"
|
retrofit2.http.GET::class -> (a as retrofit2.http.GET).value to "GET"
|
||||||
retrofit2.http.OPTIONS::class -> (a as retrofit2.http.OPTIONS).value to "OPTIONS"
|
retrofit2.http.HEAD::class -> (a as retrofit2.http.HEAD).value to "HEAD"
|
||||||
retrofit2.http.PATCH::class -> (a as retrofit2.http.PATCH).value to "PATCH"
|
retrofit2.http.OPTIONS::class -> (a as retrofit2.http.OPTIONS).value to "OPTIONS"
|
||||||
retrofit2.http.POST::class -> (a as retrofit2.http.POST).value to "POST"
|
retrofit2.http.PATCH::class -> (a as retrofit2.http.PATCH).value to "PATCH"
|
||||||
retrofit2.http.PUT::class -> (a as retrofit2.http.PUT).value to "PUT"
|
retrofit2.http.POST::class -> (a as retrofit2.http.POST).value to "POST"
|
||||||
else -> null
|
retrofit2.http.PUT::class -> (a as retrofit2.http.PUT).value to "PUT"
|
||||||
}
|
else -> null
|
||||||
if (path != null) break
|
}
|
||||||
}
|
if (path != null) break
|
||||||
return path
|
|
||||||
}
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
private val Method.requestBodyType: Class<*>?
|
private val Method.requestBodyType: Class<*>?
|
||||||
get() {
|
get() {
|
||||||
parameterAnnotations.forEachIndexed { index, annotations ->
|
parameterAnnotations.forEachIndexed { index, annotations ->
|
||||||
annotations.forEach { annotation ->
|
annotations.forEach { annotation ->
|
||||||
if (annotation.annotationClass == retrofit2.http.Body::class) {
|
if (annotation.annotationClass == retrofit2.http.Body::class) {
|
||||||
return parameterTypes[index]
|
return parameterTypes[index]
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null
|
}
|
||||||
}
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
private val Method.innerGenericReturnClass: Class<*>?
|
private val Method.innerGenericReturnClass: Class<*>?
|
||||||
get() = (genericReturnType as? ParameterizedType)?.innerGenericType as? Class<*>
|
get() = (genericReturnType as? ParameterizedType)?.innerGenericType as? Class<*>
|
||||||
|
|
||||||
private val ParameterizedType?.innerGenericType: Type?
|
private val ParameterizedType?.innerGenericType: Type?
|
||||||
get() {
|
get() {
|
||||||
val innerType = this?.actualTypeArguments?.get(0)
|
val innerType = this?.actualTypeArguments?.get(0)
|
||||||
return if (innerType is ParameterizedType) {
|
return if (innerType is ParameterizedType) {
|
||||||
innerType.innerGenericType
|
innerType.innerGenericType
|
||||||
} else {
|
} else {
|
||||||
innerType
|
innerType
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,29 +19,30 @@ internal data class CallNestedMessagesPayload(
|
|||||||
val responseMessageFullName: String?,
|
val responseMessageFullName: String?,
|
||||||
val responseDefinitions: Map<String, Any>?
|
val responseDefinitions: Map<String, Any>?
|
||||||
) : FlipperValue {
|
) : FlipperValue {
|
||||||
override fun toFlipperObject(): FlipperObject {
|
override fun toFlipperObject(): FlipperObject {
|
||||||
return FlipperObject.Builder()
|
return FlipperObject.Builder()
|
||||||
.put("path", path)
|
.put("path", path)
|
||||||
.put("method", method)
|
.put("method", method)
|
||||||
.put("requestMessageFullName", requestMessageFullName)
|
.put("requestMessageFullName", requestMessageFullName)
|
||||||
.put("requestDefinitions", requestDefinitions?.toFlipperObject())
|
.put("requestDefinitions", requestDefinitions?.toFlipperObject())
|
||||||
.put("responseMessageFullName", responseMessageFullName)
|
.put("responseMessageFullName", responseMessageFullName)
|
||||||
.put("responseDefinitions", responseDefinitions?.toFlipperObject())
|
.put("responseDefinitions", responseDefinitions?.toFlipperObject())
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Map<*, *>.toFlipperObject(): FlipperObject {
|
private fun Map<*, *>.toFlipperObject(): FlipperObject {
|
||||||
val builder = FlipperObject.Builder()
|
val builder = FlipperObject.Builder()
|
||||||
this.forEach { (key, value) ->
|
this.forEach { (key, value) ->
|
||||||
val castValue = when (value) {
|
val castValue =
|
||||||
is Map<*, *> -> value.toFlipperObject()
|
when (value) {
|
||||||
is Iterable<*> -> value.toFlipperArray()
|
is Map<*, *> -> value.toFlipperObject()
|
||||||
else -> value
|
is Iterable<*> -> value.toFlipperArray()
|
||||||
|
else -> value
|
||||||
}
|
}
|
||||||
builder.put(key as String, castValue)
|
builder.put(key as String, castValue)
|
||||||
}
|
}
|
||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Iterable<*>.toFlipperArray(): FlipperArray =
|
private fun Iterable<*>.toFlipperArray(): FlipperArray =
|
||||||
|
|||||||
@@ -7,21 +7,19 @@
|
|||||||
|
|
||||||
package com.facebook.flipper.sample.tutorial
|
package com.facebook.flipper.sample.tutorial
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.facebook.flipper.sample.tutorial.ui.RootComponent
|
import com.facebook.flipper.sample.tutorial.ui.RootComponent
|
||||||
import com.facebook.litho.LithoView
|
import com.facebook.litho.LithoView
|
||||||
import com.facebook.litho.sections.SectionContext
|
import com.facebook.litho.sections.SectionContext
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private val sectionContext: SectionContext by lazy { SectionContext(this) }
|
private val sectionContext: SectionContext by lazy { SectionContext(this) }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContentView(
|
setContentView(LithoView.create(this, RootComponent.create(sectionContext).build()))
|
||||||
LithoView.create(this, RootComponent.create(sectionContext).build())
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,18 +10,18 @@ package com.facebook.flipper.sample.tutorial
|
|||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
|
||||||
object MarineMammals {
|
object MarineMammals {
|
||||||
val list = listOf(
|
val list =
|
||||||
MarineMammal(
|
listOf(
|
||||||
"Polar Bear",
|
MarineMammal(
|
||||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/8/84/Ursus_maritimus_4_1996-08-04.jpg/190px-Ursus_maritimus_4_1996-08-04.jpg".toUri()),
|
"Polar Bear",
|
||||||
MarineMammal(
|
"https://upload.wikimedia.org/wikipedia/commons/thumb/8/84/Ursus_maritimus_4_1996-08-04.jpg/190px-Ursus_maritimus_4_1996-08-04.jpg".toUri()),
|
||||||
"Sea Otter",
|
MarineMammal(
|
||||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/1/15/Sea_otter_cropped.jpg/220px-Sea_otter_cropped.jpg".toUri()),
|
"Sea Otter",
|
||||||
MarineMammal(
|
"https://upload.wikimedia.org/wikipedia/commons/thumb/1/15/Sea_otter_cropped.jpg/220px-Sea_otter_cropped.jpg".toUri()),
|
||||||
"West Indian Manatee",
|
MarineMammal(
|
||||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/6/6f/FL_fig04.jpg/230px-FL_fig04.jpg".toUri()),
|
"West Indian Manatee",
|
||||||
MarineMammal(
|
"https://upload.wikimedia.org/wikipedia/commons/thumb/6/6f/FL_fig04.jpg/230px-FL_fig04.jpg".toUri()),
|
||||||
"Bottlenose Dolphin",
|
MarineMammal(
|
||||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/1/10/Tursiops_truncatus_01.jpg/220px-Tursiops_truncatus_01.jpg".toUri())
|
"Bottlenose Dolphin",
|
||||||
)
|
"https://upload.wikimedia.org/wikipedia/commons/thumb/1/10/Tursiops_truncatus_01.jpg/220px-Tursiops_truncatus_01.jpg".toUri()))
|
||||||
}
|
}
|
||||||
@@ -19,23 +19,23 @@ import com.facebook.litho.editor.flipper.LithoFlipperDescriptors
|
|||||||
import com.facebook.soloader.SoLoader
|
import com.facebook.soloader.SoLoader
|
||||||
|
|
||||||
class TutorialApplication : Application() {
|
class TutorialApplication : Application() {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
SoLoader.init(this, false)
|
SoLoader.init(this, false)
|
||||||
Fresco.initialize(this)
|
Fresco.initialize(this)
|
||||||
|
|
||||||
// Normally, you would want to make these dependent on BuildConfig.DEBUG.
|
// Normally, you would want to make these dependent on BuildConfig.DEBUG.
|
||||||
ComponentsConfiguration.isDebugModeEnabled = true
|
ComponentsConfiguration.isDebugModeEnabled = true
|
||||||
ComponentsConfiguration.enableRenderInfoDebugging = true
|
ComponentsConfiguration.enableRenderInfoDebugging = true
|
||||||
|
|
||||||
val flipperClient = AndroidFlipperClient.getInstance(this)
|
val flipperClient = AndroidFlipperClient.getInstance(this)
|
||||||
val descriptorMapping = DescriptorMapping.withDefaults()
|
val descriptorMapping = DescriptorMapping.withDefaults()
|
||||||
LithoFlipperDescriptors.addWithSections(descriptorMapping)
|
LithoFlipperDescriptors.addWithSections(descriptorMapping)
|
||||||
|
|
||||||
flipperClient.addPlugin(InspectorFlipperPlugin(this, descriptorMapping))
|
flipperClient.addPlugin(InspectorFlipperPlugin(this, descriptorMapping))
|
||||||
flipperClient.addPlugin(FrescoFlipperPlugin())
|
flipperClient.addPlugin(FrescoFlipperPlugin())
|
||||||
flipperClient.addPlugin(SeaMammalFlipperPlugin())
|
flipperClient.addPlugin(SeaMammalFlipperPlugin())
|
||||||
flipperClient.start()
|
flipperClient.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,28 +13,31 @@ import com.facebook.flipper.core.FlipperPlugin
|
|||||||
import com.facebook.flipper.sample.tutorial.MarineMammals
|
import com.facebook.flipper.sample.tutorial.MarineMammals
|
||||||
|
|
||||||
class SeaMammalFlipperPlugin : FlipperPlugin {
|
class SeaMammalFlipperPlugin : FlipperPlugin {
|
||||||
private var connection: FlipperConnection? = null
|
private var connection: FlipperConnection? = null
|
||||||
|
|
||||||
override fun getId(): String = "sea-mammals"
|
override fun getId(): String = "sea-mammals"
|
||||||
|
|
||||||
override fun onConnect(connection: FlipperConnection?) {
|
override fun onConnect(connection: FlipperConnection?) {
|
||||||
this.connection = connection
|
this.connection = connection
|
||||||
|
|
||||||
MarineMammals.list.mapIndexed { index, (title, picture_url) ->
|
MarineMammals.list
|
||||||
FlipperObject.Builder()
|
.mapIndexed { index, (title, picture_url) ->
|
||||||
.put("id", index)
|
FlipperObject.Builder()
|
||||||
.put("title", title)
|
.put("id", index)
|
||||||
.put("url", picture_url).build()
|
.put("title", title)
|
||||||
}.forEach(this::newRow)
|
.put("url", picture_url)
|
||||||
}
|
.build()
|
||||||
|
}
|
||||||
|
.forEach(this::newRow)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDisconnect() {
|
override fun onDisconnect() {
|
||||||
connection = null
|
connection = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun runInBackground(): Boolean = true
|
override fun runInBackground(): Boolean = true
|
||||||
|
|
||||||
private fun newRow(row: FlipperObject) {
|
private fun newRow(row: FlipperObject) {
|
||||||
connection?.send("newRow", row)
|
connection?.send("newRow", row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import com.facebook.litho.annotations.LayoutSpec
|
|||||||
import com.facebook.litho.annotations.OnCreateLayout
|
import com.facebook.litho.annotations.OnCreateLayout
|
||||||
import com.facebook.litho.annotations.Prop
|
import com.facebook.litho.annotations.Prop
|
||||||
import com.facebook.litho.widget.Card
|
import com.facebook.litho.widget.Card
|
||||||
|
|
||||||
import com.facebook.yoga.YogaEdge.HORIZONTAL
|
import com.facebook.yoga.YogaEdge.HORIZONTAL
|
||||||
import com.facebook.yoga.YogaEdge.VERTICAL
|
import com.facebook.yoga.YogaEdge.VERTICAL
|
||||||
|
|
||||||
@@ -23,17 +22,10 @@ import com.facebook.yoga.YogaEdge.VERTICAL
|
|||||||
object FeedItemCardSpec {
|
object FeedItemCardSpec {
|
||||||
|
|
||||||
@OnCreateLayout
|
@OnCreateLayout
|
||||||
fun onCreateLayout(
|
fun onCreateLayout(c: ComponentContext, @Prop mammal: MarineMammal): Component =
|
||||||
c: ComponentContext,
|
|
||||||
@Prop mammal: MarineMammal
|
|
||||||
): Component =
|
|
||||||
Column.create(c)
|
Column.create(c)
|
||||||
.paddingDip(VERTICAL, 8f)
|
.paddingDip(VERTICAL, 8f)
|
||||||
.paddingDip(HORIZONTAL, 16f)
|
.paddingDip(HORIZONTAL, 16f)
|
||||||
.child(
|
.child(Card.create(c).content(MarineMammelComponent.create(c).mammal(mammal)))
|
||||||
Card.create(c)
|
|
||||||
.content(
|
|
||||||
MarineMammelComponent.create(c)
|
|
||||||
.mammal(mammal)))
|
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
@@ -22,20 +22,16 @@ import com.facebook.litho.widget.RenderInfo
|
|||||||
|
|
||||||
@GroupSectionSpec
|
@GroupSectionSpec
|
||||||
object FeedSectionSpec {
|
object FeedSectionSpec {
|
||||||
@OnCreateChildren
|
@OnCreateChildren
|
||||||
fun onCreateChildren(c: SectionContext, @Prop data: List<MarineMammal>): Children =
|
fun onCreateChildren(c: SectionContext, @Prop data: List<MarineMammal>): Children =
|
||||||
Children.create()
|
Children.create()
|
||||||
.child(DataDiffSection.create<MarineMammal>(c)
|
.child(
|
||||||
.data(data)
|
DataDiffSection.create<MarineMammal>(c)
|
||||||
.renderEventHandler(FeedSection.render(c)))
|
.data(data)
|
||||||
.build()
|
.renderEventHandler(FeedSection.render(c)))
|
||||||
|
.build()
|
||||||
|
|
||||||
@OnEvent(RenderEvent::class)
|
@OnEvent(RenderEvent::class)
|
||||||
fun render(
|
fun render(c: SectionContext, @FromEvent model: MarineMammal): RenderInfo =
|
||||||
c: SectionContext,
|
ComponentRenderInfo.create().component(FeedItemCard.create(c).mammal(model).build()).build()
|
||||||
@FromEvent model: MarineMammal
|
|
||||||
): RenderInfo =
|
|
||||||
ComponentRenderInfo.create()
|
|
||||||
.component(FeedItemCard.create(c).mammal(model).build())
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
@@ -17,32 +17,25 @@ import com.facebook.litho.annotations.OnCreateLayout
|
|||||||
import com.facebook.litho.annotations.Prop
|
import com.facebook.litho.annotations.Prop
|
||||||
import com.facebook.litho.widget.Text
|
import com.facebook.litho.widget.Text
|
||||||
import com.facebook.yoga.YogaEdge.BOTTOM
|
import com.facebook.yoga.YogaEdge.BOTTOM
|
||||||
import com.facebook.yoga.YogaEdge.LEFT
|
|
||||||
import com.facebook.yoga.YogaEdge.HORIZONTAL
|
import com.facebook.yoga.YogaEdge.HORIZONTAL
|
||||||
|
import com.facebook.yoga.YogaEdge.LEFT
|
||||||
import com.facebook.yoga.YogaPositionType.ABSOLUTE
|
import com.facebook.yoga.YogaPositionType.ABSOLUTE
|
||||||
|
|
||||||
@LayoutSpec
|
@LayoutSpec
|
||||||
object MarineMammelComponentSpec {
|
object MarineMammelComponentSpec {
|
||||||
@OnCreateLayout
|
@OnCreateLayout
|
||||||
fun onCreateLayout(
|
fun onCreateLayout(c: ComponentContext, @Prop mammal: MarineMammal): Component =
|
||||||
c: ComponentContext,
|
Column.create(c)
|
||||||
@Prop mammal: MarineMammal
|
.child(SingleImageComponent.create(c).image(mammal.picture_url).build())
|
||||||
): Component =
|
.child(
|
||||||
Column.create(c)
|
Text.create(c)
|
||||||
.child(SingleImageComponent.create(c)
|
.text(mammal.title)
|
||||||
.image(mammal.picture_url)
|
.textStyle(Typeface.BOLD)
|
||||||
.build()
|
.textSizeDip(24f)
|
||||||
)
|
.backgroundColor(0xDDFFFFFF.toInt())
|
||||||
.child(
|
.positionType(ABSOLUTE)
|
||||||
Text.create(c)
|
.positionDip(BOTTOM, 4f)
|
||||||
.text(mammal.title)
|
.positionDip(LEFT, 4f)
|
||||||
.textStyle(Typeface.BOLD)
|
.paddingDip(HORIZONTAL, 6f))
|
||||||
.textSizeDip(24f)
|
.build()
|
||||||
.backgroundColor(0xDDFFFFFF.toInt())
|
|
||||||
.positionType(ABSOLUTE)
|
|
||||||
.positionDip(BOTTOM, 4f)
|
|
||||||
.positionDip(LEFT, 4f)
|
|
||||||
.paddingDip(HORIZONTAL, 6f))
|
|
||||||
.build()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ import com.facebook.yoga.YogaEdge
|
|||||||
|
|
||||||
@LayoutSpec
|
@LayoutSpec
|
||||||
object RootComponentSpec {
|
object RootComponentSpec {
|
||||||
@OnCreateLayout
|
@OnCreateLayout
|
||||||
fun onCreateLayout(c: ComponentContext): Component =
|
fun onCreateLayout(c: ComponentContext): Component =
|
||||||
RecyclerCollectionComponent.create(c)
|
RecyclerCollectionComponent.create(c)
|
||||||
.disablePTR(true)
|
.disablePTR(true)
|
||||||
.section(FeedSection.create(SectionContext(c)).data(MarineMammals.list).build())
|
.section(FeedSection.create(SectionContext(c)).data(MarineMammals.list).build())
|
||||||
.paddingDip(YogaEdge.TOP, 8f)
|
.paddingDip(YogaEdge.TOP, 8f)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
@@ -20,20 +20,15 @@ import com.facebook.litho.fresco.FrescoImage
|
|||||||
@LayoutSpec
|
@LayoutSpec
|
||||||
object SingleImageComponentSpec {
|
object SingleImageComponentSpec {
|
||||||
|
|
||||||
@PropDefault
|
@PropDefault val imageAspectRatio = 1f
|
||||||
val imageAspectRatio = 1f
|
|
||||||
|
|
||||||
@OnCreateLayout
|
@OnCreateLayout
|
||||||
fun onCreateLayout(
|
fun onCreateLayout(
|
||||||
c: ComponentContext,
|
c: ComponentContext,
|
||||||
@Prop image: Uri,
|
@Prop image: Uri,
|
||||||
@Prop(optional = true) imageAspectRatio: Float): Component =
|
@Prop(optional = true) imageAspectRatio: Float
|
||||||
Fresco.newDraweeControllerBuilder()
|
): Component =
|
||||||
.setUri(image)
|
Fresco.newDraweeControllerBuilder().setUri(image).build().let {
|
||||||
.build().let {
|
FrescoImage.create(c).controller(it).imageAspectRatio(imageAspectRatio).build()
|
||||||
FrescoImage.create(c)
|
|
||||||
.controller(it)
|
|
||||||
.imageAspectRatio(imageAspectRatio)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,9 +7,8 @@
|
|||||||
|
|
||||||
package com.facebook.flipper.sample.tutorial
|
package com.facebook.flipper.sample.tutorial
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
import org.junit.Assert.*
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
@@ -17,8 +16,8 @@ import org.junit.Assert.*
|
|||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
*/
|
*/
|
||||||
class ExampleUnitTest {
|
class ExampleUnitTest {
|
||||||
@Test
|
@Test
|
||||||
fun addition_isCorrect() {
|
fun addition_isCorrect() {
|
||||||
assertEquals(4, 2 + 2)
|
assertEquals(4, 2 + 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user