diff --git a/android/plugins/retrofit2-protobuf/build.gradle b/android/plugins/retrofit2-protobuf/build.gradle
new file mode 100644
index 000000000..9b796865f
--- /dev/null
+++ b/android/plugins/retrofit2-protobuf/build.gradle
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-kapt'
+apply plugin: 'maven'
+
+android {
+ compileSdkVersion rootProject.compileSdkVersion
+ buildToolsVersion rootProject.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion rootProject.minSdkVersion
+ targetSdkVersion rootProject.targetSdkVersion
+ }
+
+ dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION"
+ implementation project(':android')
+ implementation project(':network-plugin')
+ implementation deps.protobuf
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.github.hbmartin:protobuf_java_to_protobufjs:0.0.1"
+ compileOnly deps.jsr305
+ }
+}
+
+apply plugin: 'com.vanniktech.maven.publish'
diff --git a/android/plugins/retrofit2-protobuf/gradle.properties b/android/plugins/retrofit2-protobuf/gradle.properties
new file mode 100644
index 000000000..bc64e1a34
--- /dev/null
+++ b/android/plugins/retrofit2-protobuf/gradle.properties
@@ -0,0 +1,12 @@
+#
+# Copyright (c) Facebook, Inc. and its affiliates.
+#
+# This source code is licensed under the MIT license found in the LICENSE
+# file in the root directory of this source tree.
+#
+
+POM_NAME=Retrofit2 Protobuf Adapter
+POM_DESCRIPTION=Flipper plugin to automate Retrofit2 sending Protobuf definitions
+POM_ARTIFACT_ID=flipper-retrofit2-protobuf-plugin
+POM_PACKAGING=aar
+
diff --git a/android/plugins/retrofit2-protobuf/src/main/AndroidManifest.xml b/android/plugins/retrofit2-protobuf/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..0fe1d3f1a
--- /dev/null
+++ b/android/plugins/retrofit2-protobuf/src/main/AndroidManifest.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/android/plugins/retrofit2-protobuf/src/main/java/com/facebook/flipper/plugins/retrofit2protobuf/SendProtobufToFlipperFromRetrofit.kt b/android/plugins/retrofit2-protobuf/src/main/java/com/facebook/flipper/plugins/retrofit2protobuf/SendProtobufToFlipperFromRetrofit.kt
new file mode 100644
index 000000000..db827dc01
--- /dev/null
+++ b/android/plugins/retrofit2-protobuf/src/main/java/com/facebook/flipper/plugins/retrofit2protobuf/SendProtobufToFlipperFromRetrofit.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) Facebook, Inc. and its 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.retrofit2protobuf
+
+import com.facebook.flipper.android.AndroidFlipperClient
+import com.facebook.flipper.core.FlipperArray
+import com.facebook.flipper.core.FlipperObject
+import com.facebook.flipper.core.FlipperValue
+import com.facebook.flipper.plugins.network.NetworkFlipperPlugin
+import com.facebook.flipper.plugins.retrofit2protobuf.adapter.GenericCallDefinitionsToMessageDefinitionsIfProtobuf
+import com.facebook.flipper.plugins.retrofit2protobuf.adapter.RetrofitServiceToGenericCallDefinitions
+import com.facebook.flipper.plugins.retrofit2protobuf.model.CallNestedMessagesPayload
+import me.haroldmartin.protobufjavatoprotobufjs.adapter.FullNamedMessagesToNestedMessages
+
+object SendProtobufToFlipperFromRetrofit {
+ operator fun invoke(baseUrl: String, service: Class<*>) {
+ AndroidFlipperClient.getInstanceIfInitialized()?.let { client ->
+ (client.getPlugin(NetworkFlipperPlugin.ID) as? NetworkFlipperPlugin)
+ ?.send(
+ "addProtobufDefinitions",
+ FlipperObject.Builder().put(
+ baseUrl, generateProtobufDefinitions(service).toFlipperArray()
+ ).build()
+ )
+ }
+ }
+
+ private fun generateProtobufDefinitions(service: Class<*>): List {
+ return RetrofitServiceToGenericCallDefinitions(service).let { definitions ->
+ GenericCallDefinitionsToMessageDefinitionsIfProtobuf(definitions)
+ }.let { messages ->
+ messages.map {
+ CallNestedMessagesPayload(
+ path = it.path,
+ method = it.method,
+ requestMessageFullName = it.requestMessageFullName,
+ requestDefinitions = FullNamedMessagesToNestedMessages(it.requestModel),
+ responseMessageFullName = it.responseMessageFullName,
+ responseDefinitions = FullNamedMessagesToNestedMessages(it.responseModel)
+ )
+ }
+ }
+ }
+}
+
+private fun Iterable.toFlipperArray(): FlipperArray =
+ fold(FlipperArray.Builder()) { builder, call -> builder.put(call) }.build()
diff --git a/android/plugins/retrofit2-protobuf/src/main/java/com/facebook/flipper/plugins/retrofit2protobuf/adapter/GenericCallDefinitionsToMessageDefinitionsIfProtobuf.kt b/android/plugins/retrofit2-protobuf/src/main/java/com/facebook/flipper/plugins/retrofit2protobuf/adapter/GenericCallDefinitionsToMessageDefinitionsIfProtobuf.kt
new file mode 100644
index 000000000..d3eb67328
--- /dev/null
+++ b/android/plugins/retrofit2-protobuf/src/main/java/com/facebook/flipper/plugins/retrofit2protobuf/adapter/GenericCallDefinitionsToMessageDefinitionsIfProtobuf.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) Facebook, Inc. and its 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.retrofit2protobuf.adapter
+
+import com.facebook.flipper.plugins.retrofit2protobuf.model.FullNamedMessagesCallDefinition
+import com.facebook.flipper.plugins.retrofit2protobuf.model.GenericCallDefinition
+import me.haroldmartin.protobufjavatoprotobufjs.ProtobufGeneratedJavaToProtobufJs
+
+internal object GenericCallDefinitionsToMessageDefinitionsIfProtobuf {
+ operator fun invoke(callDefinitions: List): List {
+ return callDefinitions.mapNotNull { definition ->
+ val responseRootAndMessages = definition.responseType?.let {
+ ProtobufGeneratedJavaToProtobufJs(it)
+ }
+ val requestRootAndMessages = definition.requestType?.let {
+ ProtobufGeneratedJavaToProtobufJs(it)
+ }
+
+ FullNamedMessagesCallDefinition(
+ path = definition.path,
+ method = definition.method,
+ responseMessageFullName = responseRootAndMessages?.rootFullName,
+ responseModel = responseRootAndMessages?.messages,
+ requestMessageFullName = requestRootAndMessages?.rootFullName,
+ requestModel = requestRootAndMessages?.messages
+ )
+ }
+ }
+}
diff --git a/android/plugins/retrofit2-protobuf/src/main/java/com/facebook/flipper/plugins/retrofit2protobuf/adapter/RetrofitServiceToGenericCallDefinitions.kt b/android/plugins/retrofit2-protobuf/src/main/java/com/facebook/flipper/plugins/retrofit2protobuf/adapter/RetrofitServiceToGenericCallDefinitions.kt
new file mode 100644
index 000000000..a0e863128
--- /dev/null
+++ b/android/plugins/retrofit2-protobuf/src/main/java/com/facebook/flipper/plugins/retrofit2protobuf/adapter/RetrofitServiceToGenericCallDefinitions.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) Facebook, Inc. and its 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.retrofit2protobuf.adapter
+
+import com.facebook.flipper.plugins.retrofit2protobuf.model.GenericCallDefinition
+import java.lang.reflect.Method
+import java.lang.reflect.ParameterizedType
+import java.lang.reflect.Type
+
+internal object RetrofitServiceToGenericCallDefinitions {
+ @Suppress("LoopWithTooManyJumpStatements")
+ operator fun invoke(service: Class<*>): List {
+ val methodToProtobufDefinition = mutableListOf()
+ for (method in service.declaredMethods) {
+ val responseType = method.innerGenericReturnClass ?: continue
+ val (path, httpMethod) = method.annotations.urlPathAndMethod ?: continue
+ methodToProtobufDefinition.add(
+ GenericCallDefinition(
+ path = path,
+ method = httpMethod,
+ responseType = responseType,
+ requestType = method.requestBodyType
+ )
+ )
+ }
+ return methodToProtobufDefinition
+ }
+}
+
+private val Array.urlPathAndMethod: Pair?
+ get() {
+ var path: Pair? = null
+ for (a in this) {
+ path = when (a.annotationClass) {
+ retrofit2.http.DELETE::class -> (a as retrofit2.http.DELETE).value to "DELETE"
+ retrofit2.http.GET::class -> (a as retrofit2.http.GET).value to "GET"
+ retrofit2.http.HEAD::class -> (a as retrofit2.http.HEAD).value to "HEAD"
+ retrofit2.http.OPTIONS::class -> (a as retrofit2.http.OPTIONS).value to "OPTIONS"
+ retrofit2.http.PATCH::class -> (a as retrofit2.http.PATCH).value to "PATCH"
+ retrofit2.http.POST::class -> (a as retrofit2.http.POST).value to "POST"
+ retrofit2.http.PUT::class -> (a as retrofit2.http.PUT).value to "PUT"
+ else -> null
+ }
+ if (path != null) break
+ }
+ return path
+ }
+
+private val Method.requestBodyType: Class<*>?
+ get() {
+ parameterAnnotations.forEachIndexed { index, parameters ->
+ parameters.forEach { annotation ->
+ if (annotation.annotationClass == retrofit2.http.Body::class) {
+ return parameterTypes[index]
+ }
+ }
+ }
+ return null
+ }
+
+private val Method.innerGenericReturnClass: Class<*>?
+ get() = (genericReturnType as? ParameterizedType)?.innerGenericType as? Class<*>
+
+private val ParameterizedType?.innerGenericType: Type?
+ get() {
+ val innerType = this?.actualTypeArguments?.get(0)
+ return if (innerType is ParameterizedType) {
+ innerType.innerGenericType
+ } else {
+ innerType
+ }
+ }
diff --git a/android/plugins/retrofit2-protobuf/src/main/java/com/facebook/flipper/plugins/retrofit2protobuf/model/CallNestedMessagesPayload.kt b/android/plugins/retrofit2-protobuf/src/main/java/com/facebook/flipper/plugins/retrofit2protobuf/model/CallNestedMessagesPayload.kt
new file mode 100644
index 000000000..ef8c5a57d
--- /dev/null
+++ b/android/plugins/retrofit2-protobuf/src/main/java/com/facebook/flipper/plugins/retrofit2protobuf/model/CallNestedMessagesPayload.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) Facebook, Inc. and its 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.retrofit2protobuf.model
+
+import com.facebook.flipper.core.FlipperObject
+import com.facebook.flipper.core.FlipperValue
+
+internal data class CallNestedMessagesPayload(
+ val path: String,
+ val method: String,
+ val requestMessageFullName: String?,
+ val requestDefinitions: Map,
+ val responseMessageFullName: String?,
+ val responseDefinitions: Map
+) : FlipperValue {
+ override fun toFlipperObject(): FlipperObject {
+ return FlipperObject.Builder()
+ .put("path", path)
+ .put("method", method)
+ .put("requestMessageFullName", requestMessageFullName)
+ .put("requestDefinitions", requestDefinitions.toFlipperObject())
+ .put("responseMessageFullName", responseMessageFullName)
+ .put("responseDefinitions", responseDefinitions.toFlipperObject())
+ .build()
+ }
+}
+
+private fun Map<*, *>.toFlipperObject(): FlipperObject {
+ val builder = FlipperObject.Builder()
+ this.forEach {
+ builder.put(
+ it.key as String,
+ if (it.value is Map<*, *>) (it.value as Map<*, *>).toFlipperObject() else it.value
+ )
+ }
+ return builder.build()
+}
diff --git a/android/plugins/retrofit2-protobuf/src/main/java/com/facebook/flipper/plugins/retrofit2protobuf/model/FullNamedMessagesCallDefinition.kt b/android/plugins/retrofit2-protobuf/src/main/java/com/facebook/flipper/plugins/retrofit2protobuf/model/FullNamedMessagesCallDefinition.kt
new file mode 100644
index 000000000..239e524ae
--- /dev/null
+++ b/android/plugins/retrofit2-protobuf/src/main/java/com/facebook/flipper/plugins/retrofit2protobuf/model/FullNamedMessagesCallDefinition.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) Facebook, Inc. and its 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.retrofit2protobuf.model
+
+import me.haroldmartin.protobufjavatoprotobufjs.model.FullNamedMessages
+
+internal data class FullNamedMessagesCallDefinition(
+ val path: String,
+ val method: String,
+ val requestMessageFullName: String?,
+ val responseMessageFullName: String?,
+ val responseModel: FullNamedMessages?,
+ val requestModel: FullNamedMessages?
+)
diff --git a/android/plugins/retrofit2-protobuf/src/main/java/com/facebook/flipper/plugins/retrofit2protobuf/model/GenericCallDefinition.kt b/android/plugins/retrofit2-protobuf/src/main/java/com/facebook/flipper/plugins/retrofit2protobuf/model/GenericCallDefinition.kt
new file mode 100644
index 000000000..de2a784d8
--- /dev/null
+++ b/android/plugins/retrofit2-protobuf/src/main/java/com/facebook/flipper/plugins/retrofit2protobuf/model/GenericCallDefinition.kt
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) Facebook, Inc. and its 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.retrofit2protobuf.model
+
+internal data class GenericCallDefinition(
+ val path: String,
+ val method: String,
+ val responseType: Class<*>? = null,
+ val requestType: Class<*>? = null
+)
diff --git a/build.gradle b/build.gradle
index ac2681893..62a892c9e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -102,6 +102,7 @@ ext.deps = [
okhttp3 : 'com.squareup.okhttp3:okhttp:4.9.1',
leakcanary : 'com.squareup.leakcanary:leakcanary-android:1.6.3',
leakcanary2 : 'com.squareup.leakcanary:leakcanary-android:2.6',
+ protobuf : 'com.google.protobuf:protobuf-java:3.15.6',
testCore : 'androidx.test:core:1.3.0',
testRules : 'androidx.test:rules:1.3.0',
// Plugin dependencies
diff --git a/docs/setup/protobuf-retrofit-plugin.mdx b/docs/setup/protobuf-retrofit-plugin.mdx
new file mode 100644
index 000000000..5bfeb95c1
--- /dev/null
+++ b/docs/setup/protobuf-retrofit-plugin.mdx
@@ -0,0 +1,37 @@
+---
+id: protobuf-retrofit-plugin
+title: Protobut + Retrofit Setup
+sidebar_label: Protobut + Retrofit
+---
+
+## Gradle Dependencies
+
+Ensure that you already have an explicit dependency in your application's
+`build.gradle` including the plugin dependency, e.g.
+
+```groovy
+dependencies {
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-protobuf:2.9.0"
+
+ // update version below to match latest Flipper client app
+ debugImplementation "com.facebook.flipper:flipper:0.84.0"
+ debugImplementation "com.facebook.flipper:flipper-network-plugin:0.84.0"
+ debugImplementation "com.facebook.flipper:flipper-retrofit2-protobuf-plugin:0.84.0"
+}
+```
+
+## Network Plugin Requirement
+
+Ensure that `NetworkFlipperPlugin` is added as shown in the [Network setup guide](https://fbflipper.com/docs/setup/network-plugin#android)
+
+## Sending Retrofit Service
+
+Suppose you have a Retrofit service interface `PersonService` which has Protobuf body or return types. At the time you create your implementation, call the plugin with your `baseUrl` and service class:
+
+```
+import com.facebook.flipper.plugins.retrofit2protobuf.SendProtobufToFlipperFromRetrofit
+...
+val personService = retrofit.create(PersonService::class.java)
+SendProtobufToFlipperFromRetrofit(baseUrl, PersonService::class.java)
+```
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 483a38d93..63a4fea11 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -44,3 +44,6 @@ project(':leakcanary-plugin').projectDir = file('android/plugins/leakcanary')
include ':leakcanary2-plugin'
project(':leakcanary2-plugin').projectDir = file('android/plugins/leakcanary2')
+
+include ':retrofit2-protobuf'
+project(':retrofit2-protobuf').projectDir = file('android/plugins/retrofit2-protobuf')