Responsive

Responsive Screen Guide in Flutter

Responsive design is an essential part of Flutter application development to ensure the app has a visually appealing interface across various screen sizes. This article will guide you on how to implement responsiveness using different methods in Flutter.

How to Adjust Screens with responsive_builder and Mason for Different Screen Types

  • Add the following to your pubspec.yaml:

    responsive_builder: ^latest_version

The responsive_builder library helps divide the interface into different screen types such as mobile, tablet, and desktop. We can use ScreenTypeLayout.builder and OrientationLayoutBuilder to handle the interface based on screen type and orientation.

Below is how I build screens that can adapt to primary devices such as tablets and mobiles. My idea is to create small fragments of the UI and then assemble them according to the screen type that needs to be displayed. Here, we have 4 types: mobile portrait, mobile landscape, tablet portrait, and tablet landscape.

In a simple case, I will have a single common UI section for all screen types, called the body. Then, I arrange it into the four different screen types mentioned above.

import 'package:flutter/material.dart';
import 'package:responsive_builder/responsive_builder.dart';

part 'responsive_screen_mobile.dart';
part 'responsive_screen_tablet.dart';
part 'part_screen/responsive_screen_body.dart';

class ResponsiveScreen extends StatelessWidget {
  const ResponsiveScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final body = const ResponsiveScreenBody();
    return ScreenTypeLayout.builder(
      mobile: (_) => OrientationLayoutBuilder(
        portrait: (context) => ResponsiveScreenMobilePortrait(body: body),
        landscape: (context) => ResponsiveScreenMobileLandscape(body: body),
      ),
      tablet: (_) => OrientationLayoutBuilder(
        portrait: (context) => ResponsiveScreenTabletPortrait(body: body),
        landscape: (context) => ResponsiveScreenTabletLandscape(body: body),
      ),
    );
  }
}

Mobile Layouts

This is what will appear inside the mobile screen. You can arrange it freely according to the interface you have previously designed.

part of 'responsive_screen.dart';

class ResponsiveScreenMobilePortrait extends StatelessWidget {
  const ResponsiveScreenMobilePortrait({super.key, required this.body});
  final Widget body;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 20),
      child: body,
    );
  }
}

class ResponsiveScreenMobileLandscape extends StatelessWidget {
  const ResponsiveScreenMobileLandscape({super.key, required this.body});
  final Widget body;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        const Spacer(flex: 130,),
        Expanded(
          flex: 1180,
          child: body,),
        const Spacer(flex: 130,),
      ],
    );
  }
}

Tablet Layouts

This is what will appear inside the tablet screen. You can freely arrange it based on the interface you have previously designed.

part of 'responsive_screen.dart';

class ResponsiveScreenTabletPortrait extends StatelessWidget {
  const ResponsiveScreenTabletPortrait({super.key, required this.body});
  final Widget body;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        const Spacer(flex: 130),
        Expanded(flex: 1180, child: body),
        const Spacer(flex: 130),
      ],
    );
  }
}

class ResponsiveScreenTabletLandscape extends StatelessWidget {
  const ResponsiveScreenTabletLandscape({super.key, required this.body});
  final Widget body;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        const Spacer(flex: 130),
        Expanded(flex: 1180, child: body),
        const Spacer(flex: 130),
      ],
    );
  }
}

Ensuring that mobile and tablet screens have different layouts enhances the user experience. However, one challenge is having to rewrite code every time a new screen is created. To save time, I use Mason to reduce my workload.

You can check out my dr_responsive brick here: https://brickhub.dev/bricks/dr_responsive. If you're unfamiliar with Mason, refer to my article on Mason. I'm confident it will save you a lot of time.

Mason

Responsive Design Using Screen Ratios

In reality, phone screen sizes are not uniform even with the same aspect ratio. Use MediaQuery to determine the screen ratio and adjust the UI accordingly.

My idea is to use this ratio combined with the proportions in Figma to ensure the UI doesn’t overflow or break and always maintains its proportions across different devices.

class ResponsiveWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    double screenWidth = MediaQuery.of(context).size.width;
    double screenHeight = MediaQuery.of(context).size.height;

    return Container(
      width: screenWidth * 0.8, // Occupies 80% of the screen's width
      height: screenHeight * 0.5, // Occupies 50% of the screen's height
      color: Colors.blue,
      child: const Center(child: Text("Responsive Container")),
    );
  }
}

You might find writing MediaQuery.of(context).size.width quite long and time-consuming, so let me share my approach with you:

My idea is to create a variable that can be used throughout the entire project. For instance, I calculate the ratio by dividing the device's width by the design width in Figma to ensure compatibility across devices in landscape orientation.

This ratio will have a maximum value of 1 and a minimum value of 0.2, based on the design dimensions in Figma.

extension MyBuildContext on BuildContext {
  
  double get myHeight => MediaQuery.of(this).size.height;
  double get myWidth => MediaQuery.of(this).size.width;
  /// Edit ratio of fontSize for responsive screen
  double get dsgRatio {
    final ratio = myWidth / 1920;
    return ratio < 1
        ? ratio < 0.2
            ? 0.2
            : ratio
        : 1;
  }
}

Then, it will be called as follows. For instance, let’s assume I have a SizedBox ratio of 260px in Figma. Here’s how I would implement it in the code, adjusted with the global variable for screen ratio. Let me know if you'd like me to generate a full code example!

class ButtonAddCouponPartScreen extends StatelessWidget {
  const ButtonAddCouponPartScreen({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final ratio = context.dsgRatio;
    return SizedBox(
      width: 260 * ratio,
    );
  }
}

Translation:

This method is often used for widgets when they require a width or height to display. If you'd like to learn more about responsive text sizing, check out my article on theming

Dark Mode

Using Expanded and Spacer

If you find the previous method resource-intensive as it involves determining the device width, calculating the ratio, and then arriving at the actual size of the widget, I have another solution for you:

Use Expanded and Spacer.

For example, the image below has the following design and size:

Now, I'll use Expanded and Spacer to create the interface, and the result will be similar to the one produced by the ratio-based method:

Expanded and Spacer

You can clearly see the advantage here: there’s no need to calculate any ratios while creating the interface, yet it still produces a responsive result. I often use this method when designing interfaces for screens.

Conclusion

Above, I've introduced three methods for making a Flutter interface responsive. In practice, I use all three methods. Each has its own advantages and disadvantages, so make the most of this knowledge to make your application function more efficiently. If you want more examples of responsive screen design in my style, visit the website I built at https://wongcoupon.com/en/doc/help/flutter to experience it.

Me a Coffee | Support Me on Ko-fi

Last updated