Getting the actual screen height in Flutter Android

Published 19.03.2022 • Last modified 01.03.2024

The app I’m currently building has a feature which displays a custom wallpaper on the home screen. It involves the user choosing an image from their gallery and cropping it to their device’s aspect ratio. The app then modifies the image and sets it as the background.

There’s just problem: How do I get the pixel dimensions of the screen?

Screen height in Flutter #

After a bit of searching, I found this property in the dart:ui package:

int getScreenHeight() {
	double height = window.physicalSize.height;
	return height.toInt();
}

print(getScreenHeight()); // 2208.0

This is actually wrong. I know my screen height is exactly 2340 pixels, but it appears physicalSize is 132 pixels short.

Where are the extra pixels? It turns out window.physicalSize just shows the rectangle that the view is drawn onto. It doesn’t include the bottom navigation bar.

print(window.padding.top); // 66.0
print(window.padding.bottom); // 0.0

Strangely enough, window.padding.bottom doesn’t display the padding caused by the bottom navigation bar like it should according to the documentation? Anyway, how do we get the actual height?

Seems like doing this isn’t trivial in native Android either. This is the best method I’ve found:

val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
// resourceId will be 0 if the resource wasn't found
if (resourceId > 0) {
 	return resources.getDimensionPixelSize(resourceId))
} else {
	 // navigation bar height not found, return null
	 return null
}

Usage in Flutter #

To call the native code from Flutter, we need to use a MethodChannel. For more information about it, check out the documentation.

Replace /android/app/src/main/kotlin/com/example/app/MainActivity.kt with the following:

package com.example.app

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel


class MainActivity : FlutterActivity() {
    private val CHANNEL: String = "com.example.app" // Remember to change this!

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
            .setMethodCallHandler { call, result ->
                if (call.method == "navHeight") {
                    // resourceId will be 0 if nothing found
                    val resourceId =
                        resources.getIdentifier("navigation_bar_height", "dimen", "android")
                    if (resourceId > 0) {
                        // result.success() will send the value back through the channel
                        result.success(resources.getDimensionPixelSize(resourceId))
                    } else {
                        // if resource not found, just return null
                        result.success(null)
                    }
                }
            }
    }
}

In Android Studio, open the Flutter project and navigate to the MainActivity.kt file and click “Open for editing in Android Studio” to get autocompletion, auto imports and refactoring.

Back in Dart, we simply create the MethodChannel and then invoke the method. If the method call is erroring out with MissingPluginException, just stop the application and run it again. Otherwise check if the channel name is the same on both platforms.

// Needs to match exactly with `CHANNEL` in Kotlin
const _channel = MethodChannel("com.example.app");

Future<int> getScreenHeight() async {
  final physicalSize = window.physicalSize.height.toInt();

  try {
    final navHeight = await _channel.invokeMethod<int?>("navHeight");

    return physicalSize + (navHeight ?? 0);
  } on PlatformException catch (e) {
    return physicalSize;
  }
}

Example with a StatefulWidget:

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  static const _channel = MethodChannel("com.example.app");

  int height = 0;

  Future<int> _getHeight() async {
    final physicalSize = window.physicalSize.height.toInt();

    try {
      final navHeight = await _channel.invokeMethod<int?>("navHeight");

      return physicalSize + (navHeight ?? 0);
    } on PlatformException {
      return physicalSize;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'Screen height:',
            ),
            Text(
              '$height',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          final _height = await _getHeight();

          setState(() {
            height = _height;
          });
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

Screen height: 2340

Now it’s showing the correct height! The only thing needed was some method calls to native code. I’d like to see this process become easier, but since it is such a niche use case, this workaround will do fine. Anyway, I hope this guide has been helpful.