|
| 1 | +# Creating Responsive Apps With Flutter |
| 2 | + |
| 3 | +- [Creating Responsive Apps With Flutter](#creating-responsive-apps-with-flutter) |
| 4 | + - [1.1. Box Contraints](#11-box-contraints) |
| 5 | + - [1.2. Responsive Apps On Various Mobile Screen Sizes](#12-responsive-apps-on-various-mobile-screen-sizes) |
| 6 | + - [1.2.1. Columns](#121-columns) |
| 7 | + - [1.2.2. Handling Orientation](#122-handling-orientation) |
| 8 | + - [1.3. Handling Wider Screens](#13-handling-wider-screens) |
| 9 | + - [1.3.1. MediaQuery](#131-mediaquery) |
| 10 | + - [1.3.2. Layout Builder](#132-layout-builder) |
| 11 | + |
| 12 | +## 1.1. Box Contraints |
| 13 | + |
| 14 | +Flutter framework is constraint based framework, i.e, a widget bounds is constrained by the parent widget. If for example, you container has a dimension of height 900px and width 500px but when you run the code on your simulator, you see that its actually smaller than what you have intended to, this is due to the box constraints coming in from the parent widget which passes contraints of maybe 800px 400px. If the parent of the container is a scaffold, then the box constraint coming in from the scaffold to the container would be the screen dimensions. Box contraints are passed automatically by the parent. |
| 15 | + |
| 16 | +<details> |
| 17 | + <summary>Example 1</summary> |
| 18 | + |
| 19 | +<p> |
| 20 | + |
| 21 | +```dart |
| 22 | +import 'package:flutter/material.dart'; |
| 23 | +import 'package:flutter/widgets.dart'; |
| 24 | +
|
| 25 | +class Example1 extends StatelessWidget { |
| 26 | + const Example1({Key key}) : super(key: key); |
| 27 | +
|
| 28 | + @override |
| 29 | + Widget build(BuildContext context) { |
| 30 | + //Scaffold passes down screen dimensions as constraints |
| 31 | + return Scaffold( |
| 32 | + appBar: AppBar( |
| 33 | + title: Text('Example 1'), |
| 34 | + ), |
| 35 | + body: Container( |
| 36 | + color: Colors.green, |
| 37 | + width: double.infinity, |
| 38 | + height: double.infinity, |
| 39 | + ), |
| 40 | + ); |
| 41 | + } |
| 42 | +} |
| 43 | +``` |
| 44 | +</p> |
| 45 | +</details> |
| 46 | + |
| 47 | +<details> |
| 48 | + <summary>Example 1</summary> |
| 49 | + |
| 50 | +<p> |
| 51 | + |
| 52 | +```dart |
| 53 | +import 'package:flutter/material.dart'; |
| 54 | +import 'package:flutter/widgets.dart'; |
| 55 | +
|
| 56 | +class Example2 extends StatelessWidget { |
| 57 | + const Example1({Key key}) : super(key: key); |
| 58 | +
|
| 59 | + @override |
| 60 | + Widget build(BuildContext context) { |
| 61 | + //Scaffold passes down screen dimensions as constraints |
| 62 | + return Scaffold( |
| 63 | + appBar: AppBar( |
| 64 | + title: Text('Example 1'), |
| 65 | + ), |
| 66 | + body: Container( |
| 67 | + width: 200, |
| 68 | + height: 200, |
| 69 | + child: Container( |
| 70 | + color: Colors.green, |
| 71 | + width: double.infinity, |
| 72 | + height: double.infinity, |
| 73 | + ), |
| 74 | + ), |
| 75 | + ); |
| 76 | + } |
| 77 | +} |
| 78 | +``` |
| 79 | +</p> |
| 80 | +</details> |
| 81 | + |
| 82 | +<br/> |
| 83 | + |
| 84 | +*A widget can decide its own size only within the constraints given to it by its parent. This means a widget usually can’t have any size it wants.* |
| 85 | + |
| 86 | +## 1.2. Responsive Apps On Various Mobile Screen Sizes |
| 87 | +When creating an app across various mobile screen, we want to make sure they are responsive and expand or collapse based on the screen dimensions. So we will look at how to achieve that with Columns and Rows. |
| 88 | + |
| 89 | +### 1.2.1. Columns |
| 90 | + |
| 91 | +So, Columns, unlike Scaffold or Container don't pass constraint along the main axis and will build the height from the children. This is because Columns children are dynamic. |
| 92 | + |
| 93 | +<details> |
| 94 | + <summary>Example 1</summary> |
| 95 | + |
| 96 | +<p> |
| 97 | + |
| 98 | +```dart |
| 99 | +import 'package:flutter/material.dart'; |
| 100 | +import 'package:flutter/widgets.dart'; |
| 101 | +
|
| 102 | +class ColumnExample1 extends StatelessWidget { |
| 103 | + const ColumnExample1({Key key}) : super(key: key); |
| 104 | +
|
| 105 | + @override |
| 106 | + Widget build(BuildContext context) { |
| 107 | + return Scaffold( |
| 108 | + appBar: AppBar( |
| 109 | + title: Text('Example 1'), |
| 110 | + ), |
| 111 | + //if the containers height are infite here, the app will //crash |
| 112 | + body: Column( |
| 113 | + children: [ |
| 114 | + Container( |
| 115 | + color: Colors.green, |
| 116 | + height: 100, |
| 117 | + ), |
| 118 | + Container( |
| 119 | + color: Colors.blue, |
| 120 | + height: 300, |
| 121 | + ), |
| 122 | + Container( |
| 123 | + color: Colors.orange, |
| 124 | + height: 500, |
| 125 | + ), |
| 126 | + ], |
| 127 | + ), |
| 128 | + ); |
| 129 | + } |
| 130 | +} |
| 131 | +``` |
| 132 | +</p> |
| 133 | +</details> |
| 134 | + |
| 135 | +As you saw with the example, the overflow error generally happens with Columns and Rows because they don't pass constraint to the children along the main axis. |
| 136 | + |
| 137 | +So how do we solve the problem of widgets going out of bounds? We use a special widget called **Expanded** or **Flex**. |
| 138 | + |
| 139 | +**Expanded** will fill the remaining space with the child widget, which in our case is the orange widget. Note that when using Expanded, it will completely ignore the child's height. |
| 140 | + |
| 141 | +<details> |
| 142 | + <summary>Example 1</summary> |
| 143 | + |
| 144 | +<p> |
| 145 | + |
| 146 | +```dart |
| 147 | +import 'package:flutter/material.dart'; |
| 148 | +import 'package:flutter/widgets.dart'; |
| 149 | +
|
| 150 | +class ColumnExampleResponsive extends StatelessWidget { |
| 151 | + const ColumnExampleResponsive({Key key}) : super(key: key); |
| 152 | +
|
| 153 | + @override |
| 154 | + Widget build(BuildContext context) { |
| 155 | + return Scaffold( |
| 156 | + appBar: AppBar( |
| 157 | + title: Text('Example 1'), |
| 158 | + ), |
| 159 | + body: Column( |
| 160 | + children: [ |
| 161 | + Container( |
| 162 | + color: Colors.green, |
| 163 | + height: 100, |
| 164 | + ), |
| 165 | + Container( |
| 166 | + color: Colors.blue, |
| 167 | + height: 300, |
| 168 | + ), |
| 169 | + Expanded( |
| 170 | + child: Container( |
| 171 | + color: Colors.orange, |
| 172 | + height: 500, |
| 173 | + ), |
| 174 | + ), |
| 175 | + ], |
| 176 | + ), |
| 177 | + ); |
| 178 | + } |
| 179 | +} |
| 180 | +``` |
| 181 | +</p> |
| 182 | +</details> |
| 183 | + |
| 184 | +Additonal Notes: |
| 185 | +- **Flexible** is similar to Expanded but with more options on the Columns on how children should take up space. |
| 186 | +- Rows is pretty similar to Column, except that the main axis is controlled by width. |
| 187 | + |
| 188 | +### 1.2.2. Handling Orientation |
| 189 | + |
| 190 | +- Use OrientationBuilder to know what the current orientation and return the respective widget |
| 191 | +- Disable particular orientation |
| 192 | + |
| 193 | +## 1.3. Handling Wider Screens |
| 194 | + |
| 195 | +So for handling different mobile device screen, using Columns/Rows with Expanded is sufficient but to |
| 196 | +expand the repsonsiveness to wider screens like desktop apps and desktop browsers, we have to rely on |
| 197 | +either MediaQuery or LayoutBuilder. |
| 198 | + |
| 199 | +### 1.3.1. MediaQuery |
| 200 | + |
| 201 | +Using MediaQuery, you can get information like screen dimensions, accessibilty information which you can use to handle various screen sizes. |
| 202 | + |
| 203 | +<details> |
| 204 | + <summary>Media Query Example</summary> |
| 205 | + |
| 206 | +<p> |
| 207 | + |
| 208 | +```dart |
| 209 | +import 'package:flutter/material.dart'; |
| 210 | +import 'package:flutter/widgets.dart'; |
| 211 | +
|
| 212 | +import '../responsive_util.dart'; |
| 213 | +
|
| 214 | +class MediaQueryResponsive extends StatelessWidget { |
| 215 | + const MediaQueryResponsive({Key key}) : super(key: key); |
| 216 | +
|
| 217 | + @override |
| 218 | + Widget build(BuildContext context) { |
| 219 | + return Scaffold( |
| 220 | + appBar: ResponsiveUtil.isWideScreen(context) |
| 221 | + ? null |
| 222 | + : AppBar( |
| 223 | + title: Text('MediaQuery Responsive'), |
| 224 | + ), |
| 225 | + body: GridView.count( |
| 226 | + crossAxisCount: MediaQuery.of(context).size.width < 500 ? 2 : 4, |
| 227 | + children: List.generate(100, (index) { |
| 228 | + return Container( |
| 229 | + child: Center( |
| 230 | + child: Image.network( |
| 231 | + 'https://picsum.photos/id/${index + 100}/${MediaQuery.of(context).size.width < 500 ? (MediaQuery.of(context).size.width / 2).round() : (MediaQuery.of(context).size.width / 4).round()}', |
| 232 | + loadingBuilder: (BuildContext context, Widget child, |
| 233 | + ImageChunkEvent loadingProgress) { |
| 234 | + if (loadingProgress == null) return child; |
| 235 | + return Center( |
| 236 | + child: CircularProgressIndicator(), |
| 237 | + ); |
| 238 | + }, |
| 239 | + ), |
| 240 | + ), |
| 241 | + ); |
| 242 | + }), |
| 243 | + )); |
| 244 | + } |
| 245 | +} |
| 246 | +
|
| 247 | +``` |
| 248 | +</p> |
| 249 | +</details> |
| 250 | + |
| 251 | +### 1.3.2. Layout Builder |
| 252 | + |
| 253 | +Layout builder is similar to MediaQuery when it comes to screen sizes but it can used with any widget |
| 254 | +and get the parent constraints. |
| 255 | + |
| 256 | +<details> |
| 257 | + <summary>Media Query Example</summary> |
| 258 | + |
| 259 | +<p> |
| 260 | + |
| 261 | +```dart |
| 262 | +import 'package:flutter/material.dart'; |
| 263 | +import 'package:flutter/widgets.dart'; |
| 264 | +
|
| 265 | +import '../responsive_util.dart'; |
| 266 | +
|
| 267 | +class LayoutBuilderResponsive extends StatelessWidget { |
| 268 | + const LayoutBuilderResponsive({Key key}) : super(key: key); |
| 269 | +
|
| 270 | + @override |
| 271 | + Widget build(BuildContext context) { |
| 272 | + return Scaffold( |
| 273 | + appBar: ResponsiveUtil.isWideScreen(context) |
| 274 | + ? null |
| 275 | + : AppBar( |
| 276 | + title: Text('LayoutBuilder Responsive'), |
| 277 | + ), |
| 278 | + body: LayoutBuilder( |
| 279 | + builder: (context, constraints) { |
| 280 | + return GridView.count( |
| 281 | + crossAxisCount: constraints.maxWidth < 500 ? 2 : 4, |
| 282 | + children: List.generate(100, (index) { |
| 283 | + return Container( |
| 284 | + child: Center( |
| 285 | + child: Image.network( |
| 286 | + 'https://picsum.photos/id/${index + 400}/${constraints.maxWidth < 500 ? (constraints.maxWidth / 2).round() : (constraints.maxWidth / 4).round()}', |
| 287 | + loadingBuilder: (BuildContext context, Widget child, |
| 288 | + ImageChunkEvent loadingProgress) { |
| 289 | + if (loadingProgress == null) return child; |
| 290 | + return Center( |
| 291 | + child: CircularProgressIndicator(), |
| 292 | + ); |
| 293 | + }, |
| 294 | + ), |
| 295 | + ), |
| 296 | + ); |
| 297 | + }), |
| 298 | + ); |
| 299 | + }, |
| 300 | + ), |
| 301 | + ); |
| 302 | + } |
| 303 | +} |
| 304 | +``` |
| 305 | +</p> |
| 306 | +</details> |
0 commit comments