This is the continuation of “how I built the Friends Roster app” saga. If you missed the previous parts in this series, do check them out — part 1part 2part 3part 4part 5part 6 and part 7. One of the nice features of Flutter is building responsive layouts is quite simple. It is certainly easier than building responsive layouts in HMTL or Android.


As you already know, the reason I chose Flutter is because I can build for both the web and android from one code base. That means I had to design the UI to adapt to large screens and small screens and everything in between.


Large screens

Take the case of a large screen, for example in a desktop browser. There is too much real estate. So limiting the app's UI to a maximum of 1400 pixels reduces the amount of white space in the app. Thankfully Flutter uses device independent pixels, so I don't have to worry about high DPI screens vs low DPI screens.


App running on a large screen


Medium screens

These devices could be tablets on landscape mode or small screen laptops etc. Here, the app fills up all available space.

On medium screens the app takes up all the available space


Small screens

On mobile devices we don't have a lot of horizontal space when the device is held in portrait. So in this case I hide the left navigation panel inside a navigation drawer. To navigate, you will need to click the hamburger menu. Notice also how the list items show the names and call info in a 2 line format laid out vertically. On larger screens they were positioned horizontally.


On mobile screens the left navigation is in the hamburger menu


Layout code

The logic that determines the layout is quite simple.

final screenWidth = MediaQuery.of(context).size.width;
if (screenWidth < 900) {
  return _buildDetails(context);
} else if (screenWidth < 1400) {
  return _buildDrawerAndDetails(context);
} else {
  return Center(
    child: ConstrainedBox(
      constraints: BoxConstraints(maxWidth: 1400),
      child: Card(
        color: Colors.grey.shade100,
        clipBehavior: Clip.antiAlias,
        elevation: 3,
        margin: EdgeInsets.all(32),
        child: _buildDrawerAndDetails(context),
      ),
    ),
  );
}


If the screen width is less than 900 pixels then show only the details content. If the screen is less than 1400 pixels, then show both the drawer and details side by side but take up all the screen space. Finally if none of the constraints apply then make a constrained box with max width of 1400 pixels, decorated with a Card. Then show both drawer and details content inside that box.


Changing density

Sometimes there is not enough horizontal space to show all the information. In that case we can hide some columns of data like friend phone number. Likewise on mobile screens the data is laid out vertically instead of horizontally. This is achieved by showing a ListTile on small screens and a Row on larger screens.


return LayoutBuilder(
  builder: (BuildContext context, BoxConstraints constraints) {
    if (constraints.minWidth < 500) {
      return ListTile(
        onTap: () => _showFriendDetails(),
        leading: _buildAvatar(),
        title: _buildName(),
        subtitle: _buildCallInfo(),
      );
    } else {
      return InkWell(
        onTap: () => _showFriendDetails(),
        child: Padding(
          padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
          child: Row(
            children: [
              _buildAvatar(),
              SizedBox(width: 16),
              _buildName(),
              if (constraints.minWidth > 700) _buildPhone(),
              Spacer(),
              _buildCallInfo(),
            ],
          ),
        ),
      );
    }
  },
);


As you will note above, if the screen is larger than 500 but less than 700 pixels then the phone number is not shown to reduce the density.


That is all for today. If I had seen the following video before embarking on my journey, it would have helped me more in building responsive design. Here is the video for future reference.