Fixing BroadcastReceiver Crashes and Metro Server Issues in Android 14

CodeCrafter
3 min readJul 20, 2024

--

Introduction

Many developers updating their React Native projects to target Android 14 (API level 34) are encountering crashes related to the dynamic registration of BroadcastReceiver. This problem is due to new security policies in Android 14, which require explicit declaration of whether a BroadcastReceiver is exported. This blog post will walk you through the issue, explain the new requirements, and provide a detailed solution to ensure your app runs smoothly on Android 14.

Problem

After updating compileSdkVersion and targetSdkVersion to 34 to comply with Google's latest policy, app started crashing and was unable to connect to the Metro server. Here is the updated configuration in android/app/build.gradle:

{
buildToolsVersion = "34.0.0"
minSdkVersion = 29
compileSdkVersion = 34
targetSdkVersion = 34
ndkVersion = "26.0.8775105"
}

Root Cause

Android 14 introduces stricter security measures to ensure better app isolation and security. One significant change is the need to specify whether a dynamically registered BroadcastReceiver is exported. If this is not specified, Android 14 throws a SecurityException, causing the app to crash.

Understanding BroadcastReceiver

What is BroadcastReceiver?

BroadcastReceiver is an Android component that allows your app to listen for system-wide broadcast announcements or broadcasts from other applications. For example, your app can listen for changes in network connectivity, battery status, or receive custom broadcasts from other apps.

Types of Broadcast Receivers

Static Registration (Manifest-Registered Receiver):

  • Registered in the AndroidManifest.xml file.
  • Always active, even if the app is not running.
  • Suitable for system-wide or persistent events.

Dynamic Registration (Context-Registered Receiver):

  • Registered at runtime using registerReceiver() in your app’s code.
  • Only active while the app or a specific component (like an activity) is running.
  • Suitable for events relevant only when the app is active.

Exported Receiver:

  • Can receive broadcasts from other apps and the system.
  • Necessary for system-wide events or messages from other apps.

Non-Exported Receiver:

  • Private to your app, only receiving broadcasts from within the app.
  • Suitable for handling internal app events.

Detailed Solution

To comply with the new requirements, we need to override the registerReceiver method in our application. Initially, the framework handled this internally without needing the Context.RECEIVER_EXPORTED flag.

Step-by-Step Implementation

1. Update MainApplication.java

Add the following code to override the registerReceiver method. This ensures that the Context.RECEIVER_EXPORTED flag is included when necessary.

import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import org.jetbrains.annotations.Nullable;

public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Other initialization code
}

@Override
public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter) {
if (Build.VERSION.SDK_INT >= 34 && getApplicationInfo().targetSdkVersion >= 34) {
return super.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED);
} else {
return super.registerReceiver(receiver, filter);
}
}
}

Explanation:

  • Imports: Import necessary classes for handling broadcast receivers, intents, and annotations.
  • Override registerReceiver Method: This method registers a broadcast receiver with the specified intent filter.
  • Condition Check: The condition if (Build.VERSION.SDK_INT >= 34 && getApplicationInfo().targetSdkVersion >= 34) ensures that the Context.RECEIVER_EXPORTED flag is only added when the app is running on Android 14 or higher and targets API level 34.
  • Context.RECEIVER_EXPORTED: This flag indicates that the receiver can receive broadcasts from other apps, complying with Android 14’s security requirements.
  • Backward Compatibility: If the conditions are not met, the receiver is registered without the flag, maintaining compatibility with older Android versions.

2. Update build.gradle

Add the org.jetbrains:annotations dependency to your project. This dependency helps with null-safety and static code analysis, ensuring our code is more robust and maintainable.

build.gradle:

dependencies {
// Other dependencies...
implementation 'org.jetbrains:annotations:16.0.2'
}

Explanation:

  • implementation 'org.jetbrains:annotations:16.0.2': This dependency provides annotations like @Nullable and @NotNull, which help with null-safety and static code analysis.

Why These Changes Work

Compliance with Android 14 Security Requirements:

  • Policy: Android 14 requires that dynamically registered BroadcastReceivers explicitly declare whether they are exported.
  • Impact: The Context.RECEIVER_EXPORTED flag ensures that our broadcast receivers are properly declared, preventing SecurityException.

Ensuring Backward Compatibility:

  • Impact: The conditional check ensures that the flag is only applied on devices running Android 14 and targeting API level 34, maintaining compatibility with older versions of Android.

Additional Resources

Conclusion

Updating the BroadcastReceiver registration to include the Context.RECEIVER_EXPORTED flag for Android 14 ensures compliance with the latest security requirements, preventing crashes and allowing the app to connect to the Metro server. This approach maintains backward compatibility while ensuring that your app functions correctly on the latest Android versions.

By understanding and implementing these changes, developers can avoid the common pitfalls introduced by Android 14’s new security measures and ensure their apps run smoothly on all supported Android versions.

--

--