Routing 1: Pages on the Fly

There are a couple different ways to add more pages to your Flutter app.

For all pages you know will exist, you can use declared routes.

But this app just builds pages on the fly for each individual dog. This is a good case for route builders.

1. Create a Dog Detail Page:

Create a new file called dog_detail_page.dart

This is going to be a StatefulWidget, so the user of your app can rate dogs later. But for now, there will be no state to manage.

For now this is what you'll be building:

dog detail screenshot

  1. // dog_detail_page.dart
  2. import 'package:flutter/material.dart';
  3. import 'dog_model.dart';
  4. class DogDetailPage extends StatefulWidget {
  5. final Dog dog;
  6. DogDetailPage(this.dog);
  7. @override
  8. _DogDetailPageState createState() => _DogDetailPageState();
  9. }
  10. class _DogDetailPageState extends State<DogDetailPage> {
  11. // Arbitrary size choice for styles
  12. final double dogAvatarSize = 150.0;
  13. Widget get dogImage {
  14. // Containers define the size of its children.
  15. return Container(
  16. height: dogAvatarSize,
  17. width: dogAvatarSize,
  18. // Use Box Decoration to make the image a circle
  19. // and add an arbitrary shadow for styling.
  20. decoration: BoxDecoration(
  21. shape: BoxShape.circle,
  22. // Like in CSS you often want to add multiple
  23. // BoxShadows for the right look so the
  24. // boxShadow property takes a list of BoxShadows.
  25. boxShadow: [
  26. const BoxShadow(
  27. // just like CSS:
  28. // it takes the same 4 properties
  29. offset: const Offset(1.0, 2.0),
  30. blurRadius: 2.0,
  31. spreadRadius: -1.0,
  32. color: const Color(0x33000000)),
  33. const BoxShadow(
  34. offset: const Offset(2.0, 1.0),
  35. blurRadius: 3.0,
  36. spreadRadius: 0.0,
  37. color: const Color(0x24000000)),
  38. const BoxShadow(
  39. offset: const Offset(3.0, 1.0),
  40. blurRadius: 4.0,
  41. spreadRadius: 2.0,
  42. color: const Color(0x1F000000)),
  43. ],
  44. // This is how you add an image to a Container's background.
  45. image: DecorationImage(
  46. fit: BoxFit.cover,
  47. image: NetworkImage(widget.dog.imageUrl),
  48. ),
  49. ),
  50. );
  51. }
  52. // The rating section that says ★ 10/10.
  53. Widget get rating {
  54. // Use a row to lay out widgets horizontally.
  55. return Row(
  56. // Center the widgets on the main-axis
  57. // which is the horizontal axis in a row.
  58. mainAxisAlignment: MainAxisAlignment.center,
  59. children: <Widget>[
  60. Icon(
  61. Icons.star,
  62. size: 40.0,
  63. ),
  64. Text(' ${widget.dog.rating} / 10',
  65. style: Theme.of(context).textTheme.display2),
  66. ],
  67. );
  68. }
  69. // The widget that displays the image, rating and dog info.
  70. Widget get dogProfile {
  71. return Container(
  72. padding: EdgeInsets.symmetric(vertical: 32.0),
  73. decoration: BoxDecoration(
  74. // This would be a great opportunity to create a custom LinearGradient widget
  75. // that could be shared throughout the app but I'll leave that to you.
  76. gradient: LinearGradient(
  77. begin: Alignment.topRight,
  78. end: Alignment.bottomLeft,
  79. stops: [0.1, 0.5, 0.7, 0.9],
  80. colors: [
  81. Colors.indigo[800],
  82. Colors.indigo[700],
  83. Colors.indigo[600],
  84. Colors.indigo[400],
  85. ],
  86. ),
  87. ),
  88. // The Dog Profile information.
  89. child: Column(
  90. crossAxisAlignment: CrossAxisAlignment.center,
  91. children: <Widget>[
  92. dogImage,
  93. Text(
  94. '${widget.dog.name} 🎾',
  95. style: TextStyle(fontSize: 32.0),
  96. ),
  97. Text(
  98. widget.dog.location,
  99. style: TextStyle(fontSize: 20.0),
  100. ),
  101. Padding(
  102. padding:
  103. const EdgeInsets.symmetric(horizontal: 32.0, vertical: 16.0),
  104. child: Text(widget.dog.description),
  105. ),
  106. rating
  107. ],
  108. ),
  109. );
  110. }
  111. //Finally, the build method:
  112. //
  113. // Aside:
  114. // It's often much easier to build UI if you break up your widgets the way I
  115. // have in this file rather than trying to have one massive build method
  116. @override
  117. Widget build(BuildContext context) {
  118. // This is a new page, so you need a new Scaffold!
  119. return Scaffold(
  120. backgroundColor: Colors.black87,
  121. appBar: AppBar(
  122. backgroundColor: Colors.black87,
  123. title: Text('Meet ${widget.dog.name}'),
  124. ),
  125. body: dogProfile,
  126. );
  127. }
  128. }

2. Add the Routing mechanism:

Now you've added the DogDetailPage but you can't get to it. Let's add some routing.

On your main page that lists all the dogs, each card will be a button that when tapped brings up that dog's detail page.

Import DogDetailPage into dog_card.dart:

  1. // dog_card.dart
  2. import 'package:flutter/material.dart';
  3. import 'dog_detail_page.dart';
  4. import 'dog_model.dart';

In your _DogCardState class, have build wrap everything in a button:

  1. // dog_card.dart
  2. @override
  3. Widget build(BuildContext context) {
  4. // InkWell is a special Material widget that makes its children tappable
  5. // and adds Material Design ink ripple when tapped.
  6. return InkWell(
  7. // onTap is a callback that will be triggered when tapped.
  8. onTap: showDogDetailPage,
  9. child: Padding(
  10. padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
  11. child: Container(
  12. height: 115.0,
  13. child: Stack(
  14. children: <Widget>[
  15. Positioned(
  16. left: 50.0,
  17. child: dogCard,
  18. ),
  19. Positioned(top: 7.5, child: dogImage),
  20. ],
  21. ),
  22. ),
  23. ),
  24. );
  25. }
  26. // This is the builder method that creates a new page.
  27. showDogDetailPage() {
  28. // Navigator.of(context) accesses the current app's navigator.
  29. // Navigators can 'push' new routes onto the stack,
  30. // as well as pop routes off the stack.
  31. //
  32. // This is the easiest way to build a new page on the fly
  33. // and pass that page some state from the current page.
  34. Navigator.of(context).push(
  35. MaterialPageRoute(
  36. // builder methods always take context!
  37. builder: (context) {
  38. return DogDetailPage(dog);
  39. },
  40. ),
  41. );
  42. }

Your app now has pages for each and every dog.

And you may have noticed that there's a back button on the dog detail page, but there's no code for it.

Flutter automatically adds a leading button to an AppBar, which pops a route off. You can override it in the AppBar widget if you ever need to.