What Just Happened
Your app called startActivity(intent) with an implicit intent, and Android drew a blank โ no installed app declared an intent filter matching what you asked for. The crash shows up in logcat like this:
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=https://example.com }
at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2105)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1747)
at android.app.Activity.startActivity(Activity.java:5258)
This isn't a permissions error or a manifest misconfiguration on your side. No app on the device declared an intent filter for what you're asking. Still, there are a few specific reasons this happens even on devices where you'd expect it to just work.
Debug Process
1. Confirm the exact intent being fired
Log your intent before calling startActivity:
Log.d("IntentDebug", intent.toString());
Look closely at three things: Is the action correct? Is the URI scheme right โ https:// vs http:// vs a custom scheme like myapp://? Is a MIME type set when it shouldn't be, or missing when the handler requires it?
2. Check if any app can handle it
Call resolveActivity() before you ever touch startActivity():
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
} else {
Log.e("IntentDebug", "No handler found for: " + intent.toString());
Toast.makeText(this, "No app found to handle this action", Toast.LENGTH_SHORT).show();
}
A null result confirms the problem. On Android 11+, null can also mean the target app exists but isn't visible to your app โ more on that below.
3. Android 11+ package visibility (the sneaky one)
API 30 introduced package visibility filtering. Even if Chrome or Gmail is installed, your app can't query it without declaring the intent in a <queries> block in your manifest. Skip that block and resolveActivity() returns null โ even on a device that has a perfectly capable browser sitting right there. This is the most common cause on devices running Android 11 and up.
Solutions
Fix 1: Add to AndroidManifest.xml (Android 11+)
Declare the intent patterns your app needs to query. Here are the three you'll hit most often:
<!-- Open URLs in browser -->
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
</queries>
<!-- Send email -->
<queries>
<intent>
<action android:name="android.intent.action.SENDTO" />
<data android:scheme="mailto" />
</intent>
</queries>
<!-- Dial phone number -->
<queries>
<intent>
<action android:name="android.intent.action.DIAL" />
<data android:scheme="tel" />
</intent>
</queries>
You can stack multiple <intent> blocks inside a single <queries> element. One important placement detail: <queries> must be a direct child of <manifest>, not nested inside <application>.
Fix 2: Wrap startActivity in try/catch
Even with <queries> in place, a defensive try/catch saves you on edge cases. Budget Android devices โ especially those running stripped OEM ROMs from manufacturers like Lava or Tecno โ sometimes ship without any default browser or email client:
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"));
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, "No browser installed", Toast.LENGTH_SHORT).show();
}
In Kotlin:
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"))
try {
startActivity(intent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(this, "No app found to open this link", Toast.LENGTH_SHORT).show()
}
Fix 3: Use Intent.createChooser for sharing
For ACTION_SEND, the chooser usually shows an empty dialog instead of crashing. But if zero apps are visible due to the API 30 visibility rules, it still throws. Cover both bases:
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT, "Check this out!");
Intent chooser = Intent.createChooser(shareIntent, "Share via");
if (shareIntent.resolveActivity(getPackageManager()) != null) {
startActivity(chooser);
}
Add the matching query to your manifest:
<queries>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="text/plain" />
</intent>
</queries>
Fix 4: Custom URI scheme with no handler
Custom schemes like myapp://action fail hard when the target app isn't installed. Resolve it before firing, and have a web fallback ready:
Uri customUri = Uri.parse("myapp://open/profile/123");
Intent deepLink = new Intent(Intent.ACTION_VIEW, customUri);
if (deepLink.resolveActivity(getPackageManager()) != null) {
startActivity(deepLink);
} else {
// App not installed โ fall back to web
Intent fallback = new Intent(Intent.ACTION_VIEW, Uri.parse("https://myapp.com/profile/123"));
startActivity(fallback);
}
Verification
Run the app on a real device โ emulators can mask visibility issues that only surface on physical hardware. Confirm these four things:
- The intent resolves without crashing.
- Logcat shows no
ActivityNotFoundExceptionstack trace. - The flow works on a device running Android 11+ (API 30), where the visibility rules actually bite.
- Your fallback path triggers correctly on a minimal-apps device or a GApps-free emulator image.
Want to verify your <queries> block is working? Dump what your app can actually see at runtime:
// Kotlin โ list all apps that can handle ACTION_VIEW for https
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"))
val activities = packageManager.queryIntentActivities(intent, 0)
activities.forEach { Log.d("PM", it.activityInfo.packageName) }
Empty list despite adding <queries>? Double-check the scheme โ https and http are treated as separate entries and need to be declared separately.
Lessons Learned
- Implicit intents without a null check or try/catch are a ticking time bomb. Android device diversity is brutal โ you cannot assume any specific app is installed, even Chrome.
- Android 11+ made package visibility opt-in, not opt-out. Every implicit intent your app fires needs a matching
<queries>entry. Without it,resolveActivity()returns null even when the target app is sitting right there on the device. - Factory-reset and low-end devices expose holes in your fallback logic. Budget phones often lack a default browser, email client, or map app. If your fallback is just a comment in the code, it's not a fallback.
- One mental model that helps:
<queries>is not about permissions. You're not asking Android for access โ you're telling it which intent filters your app needs to be aware of at query time.

