Flutter Tablet Exclusions: How to Target Phones Only on Android and Google Play
The Flutter news you actually need
No spam, ever. Unsubscribe in one click.
So you’ve built a Flutter app that’s designed for smartphones, but you’re finding it’s being installed on Android tablets where the experience isn’t great. Maybe your app relies on telephony features, or perhaps the UI just doesn’t scale well. You want to restrict it to phones only. Let’s walk through the most effective ways to achieve this, from store-level exclusions to runtime checks.
Why Target Phones Only?
Targeting phones only is often about user experience and functionality. Tablets often lack hardware like telephony modules. If your app is a dialer, SMS client, or uses call-related permissions, it might be useless on a Wi-Fi-only tablet. Alternatively, you might have a phone-specific UI that becomes awkward on a large screen. By restricting your app, you can avoid negative reviews from tablet users who can’t use your app as intended.
Method 1: The Store-Level Filter (Most Effective)
The most powerful approach is to tell the Google Play Store not to offer your app to tablets in the first place. This is done in your android/app/src/main/AndroidManifest.xml file by declaring a required hardware feature.
The most common and effective declaration is to require telephony hardware. Most tablets lack cellular capabilities, so this filter catches the vast majority of them.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<uses-feature android:name="android.hardware.telephony" android:required="true" />
<!-- ... rest of your manifest ... -->
</manifest>
What this does: The android:required="true" attribute tells the Play Store, “Do not list this app on devices that do not have a telephony subsystem.” Users browsing on a Wi-Fi tablet will not see your app available for installation.
Important nuance: This method is excellent for filtering out most tablets, but be aware: some larger phones (“phablets”) have telephony, and a small number of cellular-enabled tablets will slip through. If you need a 100% guarantee, you’ll need to combine this with a runtime check (Method 3).
Method 2: Using Screen Density Features (A More Subtle Approach)
Another AndroidManifest.xml tactic is to use the android.hardware.screen.portrait feature. The logic here is that while tablets often support both orientations, phones are typically held in portrait mode for most apps. You can suggest a preference.
<uses-feature android:name="android.hardware.screen.portrait" android:required="false" />
Setting required="false" here is key. It tells the Play Store, “My app works best in portrait, but it can technically run otherwise.” The store may use this as a secondary signal for filtering, but it’s not a hard block. This method is less reliable than the telephony requirement and is best used as a supplementary hint.
Method 3: The Runtime Check (For Granular Control)
Sometimes, you need more control. Perhaps your app can run on a tablet, but you want to show a tailored message or redirect users to a tablet-optimized version of your service. This is where a Flutter runtime check comes in.
The classic heuristic is to check the shortestSide of the device screen. The general rule of thumb is that a shortestSide less than 600 logical pixels indicates a phone-sized device.
Here’s a simple utility function you can place in your app’s startup logic (e.g., in main() or your initial screen’s initState):
import 'package:flutter/widgets.dart';
bool _isLikelyTablet(BuildContext context) {
final double shortestSide = MediaQuery.of(context).size.shortestSide;
// Using 600 logical pixels as the threshold.
return shortestSide >= 600;
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(
builder: (context) {
// Perform the check once the context is available.
if (_isLikelyTablet(context)) {
return const Scaffold(
body: Center(
child: Text(
'This app is optimized for smartphones. '
'Please use your phone to install and run this application.',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18),
),
),
);
}
// Otherwise, run the normal phone app.
return const PhoneHomeScreen();
},
),
);
}
}
Is shortestSide >= 600 reliable? It’s a good rule of thumb for Android, but it’s not perfect. Device pixel densities and aspect ratios vary. Some small tablets might dip below 600, and some very large phones might go above it. Use this as a soft filter in combination with the Manifest method, not as a sole solution.
Common Mistakes & Best Practices
- Over-reliance on runtime checks alone: If you only use a
MediaQuerycheck, your app will still be installable on tablets, leading to potential confusion and bad reviews. Always start with theAndroidManifest.xmluses-featurefilter. - Forgetting about cellular tablets: The telephony filter won’t block LTE/5G tablets. If this is a critical issue for your app, your runtime check must be robust and you should consider showing a clear, informative message to those users.
- Testing on emulators: When you add
<uses-feature android:name="android.hardware.telephony" android:required="true" />, your app might not install on a tablet or Wi-Fi-only emulator. Test your runtime logic on a phone emulator or a real device.
How This Affects Google Play Console
When you upload an APK or App Bundle with android.hardware.telephony set to required="true", the Play Console’s device catalog will automatically reflect this. Under “Device compatibility” in your release dashboard, you’ll see tablets largely excluded.
Final Recommendation
For the strongest phone-only targeting, use a two-tiered approach:
- Primary Gate: Add
<uses-feature android:name="android.hardware.telephony" android:required="true" />to yourAndroidManifest.xml. This handles the store-level exclusion for ~95% of cases. - Secondary Gate: Implement a runtime
shortestSidecheck in your Flutter code. For the small percentage of devices that get past the first filter (like cellular tablets), you can display a friendly, explanatory message guiding users to the appropriate experience.
This combination gives you control at both the distribution and runtime levels, ensuring a clear path for your intended phone users and a graceful handling of everyone else.
This blog is produced with the assistance of AI by a human editor. Learn more
Related Posts
Flutter App Size Optimization: Strategies to Shrink Your APK/IPA Below 10MB
Flutter apps are often criticized for their larger-than-native binary sizes. This post will dive into practical, actionable strategies to significantly reduce your Flutter app's footprint, including build configurations, asset optimization, and dependency management, demonstrating how to achieve app sizes under 10MB.
Mastering In-App Subscriptions in Flutter: A Guide to Avoiding Common Pitfalls
Implementing in-app subscriptions in Flutter can be surprisingly complex, especially when dealing with cross-platform nuances, receipt validation, and managing different store configurations. This post will delve into the common challenges developers face, compare solutions like RevenueCat with custom implementations, and provide best practices for setting up and maintaining a robust subscription system.
Taming Android Emulator RAM: A Developer's Guide to Reducing Memory Usage for Flutter
Android emulators are notorious for hogging RAM, often pushing developer machines into swap and slowing down Flutter development. This post will explore the reasons behind high emulator memory consumption and provide practical, actionable strategies and tools (including a custom-built solution) to significantly reduce RAM usage, ensuring a smoother development experience without sacrificing functionality.