Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inject dev machine IP on Android and improve error message when connection fails #49166

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import com.facebook.react.tasks.GenerateCodegenSchemaTask
import com.facebook.react.tasks.GeneratePackageListTask
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForApp
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForLibraries
import com.facebook.react.utils.AgpConfiguratorUtils.configureDevPorts
import com.facebook.react.utils.AgpConfiguratorUtils.configureDevServerLocation
import com.facebook.react.utils.AgpConfiguratorUtils.configureNamespaceForLibraries
import com.facebook.react.utils.BackwardCompatUtils.configureBackwardCompatibilityReactMap
import com.facebook.react.utils.DependencyUtils.configureDependencies
Expand Down Expand Up @@ -70,7 +70,7 @@ class ReactPlugin : Plugin<Project> {

configureReactNativeNdk(project, extension)
configureBuildConfigFieldsForApp(project, extension)
configureDevPorts(project)
configureDevServerLocation(project)
configureBackwardCompatibilityReactMap(project)
configureJavaToolChains(project)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import com.facebook.react.ReactExtension
import com.facebook.react.utils.ProjectUtils.isHermesEnabled
import com.facebook.react.utils.ProjectUtils.isNewArchEnabled
import java.io.File
import java.net.NetworkInterface
import java.net.Inet4Address
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import org.gradle.api.Action
Expand Down Expand Up @@ -50,13 +52,14 @@ internal object AgpConfiguratorUtils {
}
}

fun configureDevPorts(project: Project) {
fun configureDevServerLocation(project: Project) {
val devServerPort =
project.properties["reactNativeDevServerPort"]?.toString() ?: DEFAULT_DEV_SERVER_PORT

val action =
Action<AppliedPlugin> {
project.extensions.getByType(AndroidComponentsExtension::class.java).finalizeDsl { ext ->
ext.defaultConfig.resValue("string", "react_native_dev_server_ip", getHostIpAddress())
ext.defaultConfig.resValue("integer", "react_native_dev_server_port", devServerPort)
}
}
Expand Down Expand Up @@ -104,3 +107,11 @@ fun getPackageNameFromManifest(manifest: File): String? {
return null
}
}

internal fun getHostIpAddress(): String =
NetworkInterface.getNetworkInterfaces().asSequence()
.filter { it.isUp && !it.isLoopback }
.flatMap { it.inetAddresses.asSequence() }
.filter { it is Inet4Address && !it.isLoopbackAddress }
.map { it.hostAddress }
.firstOrNull() ?: "localhost"
1 change: 1 addition & 0 deletions packages/react-native/ReactAndroid/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ android {
buildConfigField("boolean", "UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE", "false")

resValue("integer", "react_native_dev_server_port", reactNativeDevServerPort())
resValue("string", "react_native_dev_server_ip", "localhost")

testApplicationId = "com.facebook.react.tests.gradle"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
import android.graphics.Typeface;
import android.hardware.SensorManager;
import android.os.Build;
import android.text.InputType;
import android.util.Pair;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
Expand Down Expand Up @@ -62,6 +64,8 @@
import com.facebook.react.devsupport.interfaces.StackFrame;
import com.facebook.react.modules.core.RCTNativeAppEventEmitter;
import com.facebook.react.modules.debug.interfaces.DeveloperSettings;
import com.facebook.react.modules.systeminfo.AndroidInfoHelpers;
import com.facebook.react.packagerconnection.PackagerConnectionSettings;
import com.facebook.react.packagerconnection.RequestHandler;
import java.io.File;
import java.net.MalformedURLException;
Expand Down Expand Up @@ -376,24 +380,91 @@ public void onOptionSelected() {
return;
}

final EditText input = new EditText(context);
input.setHint("localhost:8081");
PackagerConnectionSettings settings = mDevSettings.getPackagerConnectionSettings();
final String currentHost = settings.getDebugServerHost();
settings.setDebugServerHost("");
final String defaultHost = settings.getDebugServerHost();
settings.setDebugServerHost(currentHost);

LinearLayout layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL);
int paddingSmall = (int) (4 * context.getResources().getDisplayMetrics().density);
int paddingLarge = (int) (16 * context.getResources().getDisplayMetrics().density);
layout.setPadding(paddingLarge, paddingLarge, paddingLarge, paddingLarge);

AlertDialog bundleLocationDialog =
final TextView label = new TextView(context);
label.setText(mApplicationContext.getString(R.string.catalyst_change_bundle_location_input_label));
label.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));

final EditText input = new EditText(context);
// This makes it impossible to enter a newline in the input field
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.setHint(mApplicationContext.getString(R.string.catalyst_change_bundle_location_input_hint));
input.setBackgroundResource(android.R.drawable.edit_text);
input.setHintTextColor(0xFFCCCCCC);
input.setTextColor(0xFF000000);
input.setText(currentHost);

final Button defaultHostSuggestion = new Button(context);
defaultHostSuggestion.setText(defaultHost);
defaultHostSuggestion.setTextSize(12);
defaultHostSuggestion.setAllCaps(false);
defaultHostSuggestion.setOnClickListener(v -> input.setText(defaultHost));

final Button localhostSuggestion = new Button(context);
localhostSuggestion.setText("localhost:8081");
localhostSuggestion.setTextSize(12);
localhostSuggestion.setAllCaps(false);
localhostSuggestion.setOnClickListener(v -> input.setText("localhost:8081"));

LinearLayout suggestionRow = new LinearLayout(context);
suggestionRow.setOrientation(LinearLayout.HORIZONTAL);
suggestionRow.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
suggestionRow.addView(defaultHostSuggestion);
suggestionRow.addView(localhostSuggestion);

final TextView instructions = new TextView(context);
instructions.setText(mApplicationContext.getString(R.string.catalyst_change_bundle_location_instructions, AndroidInfoHelpers.getAdbReverseTcpCommand(mApplicationContext)));
final LinearLayout.LayoutParams instructionsParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
instructionsParams.setMargins(0, paddingSmall, 0, paddingLarge);
instructions.setLayoutParams(instructionsParams);

final Button applyChangesButton = new Button(context);
applyChangesButton.setText(mApplicationContext.getString(R.string.catalyst_change_bundle_location_apply));

final Button cancelButton = new Button(context);
cancelButton.setText(mApplicationContext.getString(R.string.catalyst_change_bundle_location_cancel));

layout.addView(label);
layout.addView(input);
layout.addView(suggestionRow);
layout.addView(instructions);
layout.addView(applyChangesButton);
layout.addView(cancelButton);

final AlertDialog bundleLocationDialog =
new AlertDialog.Builder(context)
.setTitle(mApplicationContext.getString(R.string.catalyst_change_bundle_location))
.setView(input)
.setPositiveButton(
android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String host = input.getText().toString();
mDevSettings.getPackagerConnectionSettings().setDebugServerHost(host);
handleReloadJS();
}
})
.setView(layout)
.create();

applyChangesButton.setOnClickListener(v -> {
String host = input.getText().toString();
mDevSettings.getPackagerConnectionSettings().setDebugServerHost(host);
handleReloadJS();
bundleLocationDialog.dismiss();
});
cancelButton.setOnClickListener(v -> {
bundleLocationDialog.dismiss();
});

bundleLocationDialog.show();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ public object AndroidInfoHelpers {
private fun isRunningOnStockEmulator(): Boolean =
Build.FINGERPRINT.contains("generic") || Build.FINGERPRINT.startsWith("google/sdk_gphone")

@JvmStatic public fun getServerHost(port: Int): String = getServerIpAddress(port)
@JvmStatic public fun getServerHost(port: Int): String = getServerIpAddress(null, port)

@JvmStatic
public fun getServerHost(context: Context): String = getServerIpAddress(getDevServerPort(context))
public fun getServerHost(context: Context): String = getServerIpAddress(context, getDevServerPort(context))

@JvmStatic public fun getServerHost(context: Context, port: Int): String = getServerIpAddress(context, port)

@JvmStatic
public fun getAdbReverseTcpCommand(port: Int): String = "adb reverse tcp:$port tcp:$port"
Expand Down Expand Up @@ -86,13 +88,14 @@ public object AndroidInfoHelpers {
private fun getDevServerPort(context: Context): Int =
context.resources.getInteger(R.integer.react_native_dev_server_port)

private fun getServerIpAddress(port: Int): String {
private fun getServerIpAddress(context: Context?, port: Int): String {
val ipAddress: String =
when {
getMetroHostPropValue().isNotEmpty() -> getMetroHostPropValue()
isRunningOnGenymotion() -> GENYMOTION_LOCALHOST
isRunningOnStockEmulator() -> EMULATOR_LOCALHOST
else -> DEVICE_LOCALHOST
context == null -> DEVICE_LOCALHOST
else -> context.resources.getString(R.string.react_native_dev_server_ip)
}
return String.format(Locale.US, "%s:%d", ipAddress, port)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,13 @@ loadScriptFromAssets(AAssetManager* manager, const std::string& assetName) {
}

throw std::runtime_error(
"Unable to load script. Make sure you're "
"either running Metro (run 'npx react-native start') or that your bundle '" +
assetName + "' is packaged correctly for release.");
"Unable to load script.\n\n"
"Make sure you're running Metro or that your "
"bundle '" + assetName + "' is packaged correctly for release.\n\n"
"The device must be on the same Wi-Fi network as your computer to connect to Metro.\n\n"
"To use USB instead, shake the device to open the Dev Menu and set "
"the bundler location to \"localhost:8081\" and run:\n"
" adb reverse tcp:8081 tcp:8081");
}

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
<string name="catalyst_reload" project="catalyst" translatable="false">Reload</string>
<string name="catalyst_reload_error" project="catalyst" translatable="false">Failed to load bundle. Try restarting the bundler or reconnecting your device.</string>
<string name="catalyst_change_bundle_location" project="catalyst" translatable="false">Change Bundle Location</string>
<string name="catalyst_change_bundle_location_input_label" project="catalyst" translatable="false">Provide a custom bundler address and port:</string>
<string name="catalyst_change_bundle_location_input_hint" project="catalyst" translatable="false">127.0.0.1:8081</string>
<string name="catalyst_change_bundle_location_instructions" project="catalyst" translatable="false">If you experience slow reloads or unstable network connection, you can route data via USB cable instead of WiFi:\n 1. Connect your device via USB\n 2. Set the bundle location to `localhost:8081`\n 3. Run this command in your terminal:\n&#160;&#160;&#160;&#160;&#160;&#160;`%1$s`</string>
<string name="catalyst_change_bundle_location_apply" project="catalyst" translatable="false">Apply Changes</string>
<string name="catalyst_change_bundle_location_cancel" project="catalyst" translatable="false">Cancel</string>
<string name="catalyst_open_debugger_error" project="catalyst" translatable="false">Failed to open DevTools. Please check that the dev server is running and reload the app.</string>
<string name="catalyst_debug_open" project="catalyst" translatable="false">Open DevTools</string>
<string name="catalyst_debug_open_disabled" project="catalyst" translatable="false">Connect to the bundler to debug JavaScript</string>
Expand Down
Loading