{"componentChunkName":"component---src-templates-lesson-post-js","path":"/lesson/sliders-and-buttons","result":{"data":{"strapiLesson":{"id":"Lesson_78","author":{"email":"eric@ericwindmill.com","username":"Eric Windmill","twitter":"ericwindmill"},"content":"The time has come to add the most important feature: the ability to rate to a dog.\n\n### 1. Add the Form\n\nStart by adding the form UI to `dog_detail_page.dart`.\n\nThis is what the page will look like:\n\n![dog rating page](https://res.cloudinary.com/ericwindmill/image/upload/c_scale,r_5,w_300/v1521395106/flutter_by_example/Simulator_Screen_Shot_-_iPhone_X_-_2018-03-18_at_10.44.53.png)\n\nThe interface will consist of two main widgets:\n  1. a `Slider` to change the rating.\n  2. a `RaisedButton` to submit the slider value.\n\nAdd them both to the `_DogDetailPageState`:\n\n```dart\n// dog_detail_page.dart\n\nclass _DogDetailPageState extends State<DogDetailPage> {\n  final double dogAvatarSize = 150.0;\n  // This is the starting value of the slider.\n  double _sliderValue = 10.0;\n\n  // ...\n\n  Widget get addYourRating {\n    return Column(\n      children: <Widget>[\n        Container(\n          padding: EdgeInsets.symmetric(\n            vertical: 16.0,\n            horizontal: 16.0,\n          ),\n          child: Row(\n            mainAxisAlignment: MainAxisAlignment.spaceBetween,\n            children: <Widget>[\n              // In a row, column, listview, etc., a Flexible widget is a wrapper\n              // that works much like CSS's flex-grow property.\n              //\n              // Any room left over in the main axis after\n              // the widgets are given their width\n              // will be distributed to all the flexible widgets\n              // at a ratio based on the flex property you pass in.\n              // Because this is the only Flexible widget,\n              // it will take up all the extra space.\n              //\n              // In other words, it will expand as much as it can until\n              // the all the space is taken up.\n              Flexible(\n                flex: 1,\n                // A slider, like many form elements, needs to know its\n                // own value and how to update that value.\n                //\n                // The slider will call onChanged whenever the value\n                // changes. But it will only repaint when its value property\n                // changes in the state using setState.\n                //\n                // The workflow is:\n                // 1. User drags the slider.\n                // 2. onChanged is called.\n                // 3. The callback in onChanged sets the sliderValue state.\n                // 4. Flutter repaints everything that relies on sliderValue,\n                // in this case, just the slider at its new value.\n                child: Slider(\n                  activeColor: Colors.indigoAccent,\n                  min: 0.0,\n                  max: 15.0,\n                  onChanged: (newRating) {\n                    setState(() => _sliderValue = newRating);\n                  },\n                  value: _sliderValue,\n                ),\n              ),\n\n              // This is the part that displays the value of the slider.\n              Container(\n                width: 50.0,\n                alignment: Alignment.center,\n                child: Text('${_sliderValue.toInt()}',\n                    style: Theme.of(context).textTheme.display1),\n              ),\n            ],\n          ),\n        ),\n        submitRatingButton,\n      ],\n    );\n  }\n\n  // A simple Raised Button that as of now doesn't do anything yet.\n  Widget get submitRatingButton {\n    return RaisedButton(\n      onPressed: () => print('pressed!'),\n      child: Text('Submit'),\n      color: Colors.indigoAccent,\n    );\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      backgroundColor: Colors.black87,\n      appBar: AppBar(\n        backgroundColor: Colors.black87,\n        title: Text('Meet ${widget.dog.name}'),\n      ),\n      // Make the body a ListView that displays\n      // both the profile and the rating form.\n      body: ListView(\n        children: <Widget>[dogProfile, addYourRating],\n      ),\n    );\n  }\n}\n```\n\nIf you hot reload your app, you should have a working slider.\n\n### 2. Wire up the Submit button\n\nThe `submitRatingButton` does what could *technically*  be done in the `Slider` `onChanged` callback.\n\nIt updates the rating in the dog class itself. That way, throughout the app the new rating is shown, because Flutter will rebuild *everything* that includes the Dog's rating.\n\nThis is as simple as adding this function to your `_DogDetailPageState` class, and then calling it when the submit button is pressed:\n\n```dart\n// dog_detail_page.dart\n\n// In the next section you'll add error handling.\n// For now this is all it does.\nvoid updateRating() {\n  setState(() => widget.dog.rating = _sliderValue.toInt());\n}\n```\n\nAnd then in your `submitRatingButton` widget:\n\n```dart\n// dog_detail_page.dart\n\nWidget get submitRatingButton {\n  return RaisedButton(\n    onPressed: updateRating,\n    child: Text('Submit'),\n    color: Colors.indigoAccent,\n  );\n}\n```\n\nAfter adding this, you can move the slider, press submit, and then travel back to the main page. You should see the dog's rating updated.\n","updated_at":"Friday, 24th of July, 2020","slug":"sliders-and-buttons","strapiId":78,"title":"Sliders and Buttons","tutorial":{"category":"Flutter","title":"Intro Flutter App"}},"strapiTutorial":{"lessons":[{"author":1,"content":"At this point, your app has all the functionality that you'll implement.\n\nThere are some easy animations Flutter gives you that will enhance the UX by a mile.\n\n1. Add placeholder for images, so they fade in on load.\n2. Add a [hero animation](https://ericwindmill.com/ux-transitions-in-flutter-fade-in-image-animated-cross-fade-and-hero-transitions) that connects your `DogCard` to the `dog_detail_page`.\n\n### 1. Add an AnimatedCrossFade to load Widgets on state change\n\nThis all happens in your `_DogCardState` class.\n\n```dart\n// dog_card.dart\n\nWidget get dogImage {\n  var dogAvatar = Container(\n    width: 100.0,\n    height: 100.0,\n    decoration: BoxDecoration(\n      shape: BoxShape.circle,\n      image: DecorationImage(\n        fit: BoxFit.cover,\n        image: NetworkImage(renderUrl ?? ''),\n      ),\n    ),\n  );\n\n  // Placeholder is a static container the same size as the dog image.\n  var placeholder = Container(\n    width: 100.0,\n    height: 100.0,\n    decoration: BoxDecoration(\n      shape: BoxShape.circle,\n      gradient: LinearGradient(\n        begin: Alignment.topLeft,\n        end: Alignment.bottomRight,\n        colors: [Colors.black54, Colors.black, Colors.blueGrey[600]],\n      ),\n    ),\n    alignment: Alignment.center,\n    child: Text(\n      'DOGGO',\n      textAlign: TextAlign.center,\n    ),\n  );\n\n  // This is an animated widget built into flutter.\n  return AnimatedCrossFade(\n    // You pass it the starting widget and the ending widget.\n    firstChild: placeholder,\n    secondChild: dogAvatar,\n      // Then, you pass it a ternary that should be based on your state\n      //\n      // If renderUrl is null tell the widget to use the placeholder,\n      // otherwise use the dogAvatar.\n    crossFadeState: renderUrl == null\n        ? CrossFadeState.showFirst\n        : CrossFadeState.showSecond,\n     // Finally, pass in the amount of time the fade should take.\n    duration: Duration(milliseconds: 1000),\n  );\n}\n```\n\nGive your app a full restart and you should see it fade in (slowly) when the image is loaded.\n","created_at":"2020-07-21T16:27:54.626Z","id":80,"slug":"built-in-animation-animated-cross-fade","title":"Built-in Animation: AnimatedCrossFade","tutorial":8,"updated_at":"2020-07-24T16:43:06.524Z"},{"author":1,"content":"\n### 1. Get to a Clean Slate\n\nAll Flutter apps start with `main.dart`. Get rid of all the Counter app stuff, and you'll end up with this:\n\n```dart\n// main.dart\nimport 'package:flutter/material.dart';\n\nvoid main() => runApp(MyApp());\n\nclass MyApp extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    /// MaterialApp is the base Widget for your Flutter Application\n    /// Gives us access to routing, context, and meta info functionality.\n    return MaterialApp(\n      title: 'We Rate Dogs',\n      // Make all our text default to white\n      // and backgrounds default to dark\n      theme: ThemeData(brightness: Brightness.dark),\n      home: MyHomePage(title: 'We Rate Dogs'),\n    );\n  }\n}\n\nclass MyHomePage extends StatefulWidget {\n  MyHomePage({Key key, this.title}) : super(key: key);\n\n  final String title;\n\n  @override\n  _MyHomePageState createState() => _MyHomePageState();\n}\n\nclass _MyHomePageState extends State<MyHomePage> {\n    @override\n    Widget build(BuildContext context) {\n        /// Scaffold is the base for a page.\n        /// It gives an AppBar for the top,\n        /// Space for the main body, bottom navigation, and more.\n        return Scaffold(\n            /// App bar has a ton of functionality, but for now lets\n            /// just give it a color and a title.\n            appBar: AppBar(\n                /// Access this widgets properties with 'widget'\n                title: Text(widget.title),\n                backgroundColor: Colors.black87,\n            ),\n            /// Container is a convenience widget that lets us style it's\n            /// children. It doesn't take up any space itself, so it\n            /// can be used as a placeholder in your code.\n            body: Container(),\n        );\n    }\n}\n\n```\n\n### 2. Create A Dog Model Class\n\nWe'll create a plain Dart class called Dog for our data model.\n\nFirst, create a new file called `dog_model.dart` in the `lib` directory.\n\n```\n- lib\n  -dog_model.dart\n  -main.dart\n```\n\nIn that file, we'll just create super basic class with a couple properties:\n\n```dart\nclass Dog {\n  final String name;\n  final String location;\n  final String description;\n  String imageUrl;\n\n  // All dogs start out at 10, because they're good dogs.\n  int rating = 10;\n\n  Dog(this.name, this.location, this.description);\n}\n```\n\n### 3. Get Dog Pics\n\nThis is our one slight detour. We're going to use a super simple API to generate the dog images. This API doesn't require an API key or anything.\n\nYou can find it at [dog.ceo](https://dog.ceo). All it does is give us random images of dogs.\n\nIn your `Dog` class, add this method:\n\n```dart\n// dog_model.dart\n\nFuture getImageUrl() async {\n  // Null check so our app isn't doing extra work.\n  // If there's already an image, we don't need to get one.\n  if (imageUrl != null) {\n    return;\n  }\n\n  // This is how http calls are done in flutter:\n  HttpClient http = HttpClient();\n  try {\n    // Use darts Uri builder\n    var uri = Uri.http('dog.ceo', '/api/breeds/image/random');\n    var request = await http.getUrl(uri);\n    var response = await request.close();\n    var responseBody = await response.transform(utf8.decoder).join();\n    // The dog.ceo API returns a JSON object with a property\n    // called 'message', which actually is the URL.\n    imageUrl = json.decode(responseBody)['message'];\n  } catch (exception) {\n    print(exception);\n  }\n}\n```\n\n**NB:** This will also require the import of two dart packages:\n\n```dart\nimport 'dart:convert';\nimport 'dart:io';\n```\n\n### 4. Create some sample data with the new Dog class.\n\nIn `main.dart` let's create a handful of dogs so we have something to work with.\n\nFirst import `dog_model.dart`:\n\n```dart\n// main.dart\n\nimport 'package:flutter/material.dart';\n\nimport 'dog_model.dart';\n```\n\nThen add some doggos:\n\n```dart\n// main.dart in the State class\n\nclass _MyHomePageState extends State<MyHomePage> {\n  List<Dog> initialDoggos = []\n    ..add(Dog('Ruby', 'Portland, OR, USA',\n        'Ruby is a very good girl. Yes: Fetch, loungin\\'. No: Dogs who get on furniture.'))\n    ..add(Dog('Rex', 'Seattle, WA, USA', 'Best in Show 1999'))\n    ..add(Dog('Rod Stewart', 'Prague, CZ',\n        'Star good boy on international snooze team.'))\n    ..add(Dog('Herbert', 'Dallas, TX, USA', 'A Very Good Boy'))\n    ..add(Dog('Buddy', 'North Pole, Earth', 'Self proclaimed human lover.'));\n}\n```\n","created_at":"2020-07-21T16:22:06.384Z","id":71,"slug":"data-model-and-http","title":"Data Model and HTTP","tutorial":8,"updated_at":"2020-07-24T16:43:06.524Z"},{"author":1,"content":"Right now you just have a card for your dog. It would be more useful to render all of them as a list.\n\nOne of the most important concepts in Flutter UI is rendering UI lists, which is often done in builder methods.\n\nBuilder methods essentially create a widget once for each piece of data in a Dart `List`.\n\nFirst, create a new file called `dog_list.dart`.\n\n## 1. DogList Class\n\n```dart\n// dog_list.dart\n\nimport 'package:flutter/material.dart';\n\nimport 'dog_card.dart';\nimport 'dog_model.dart';\n\nclass DogList extends StatelessWidget {\n  // Builder methods rely on a set of data, such as a list.\n  final List<Dog> doggos;\n  DogList(this.doggos);\n\n  // First, make your build method like normal.\n  // Instead of returning Widgets, return a method that returns widgets.\n  // Don't forget to pass in the context!\n  @override\n  Widget build(BuildContext context) {\n    return _buildList(context);\n  }\n\n  // A builder method almost always returns a ListView.\n  // A ListView is a widget similar to Column or Row.\n  // It knows whether it needs to be scrollable or not.\n  // It has a constructor called builder, which it knows will\n  // work with a List.\n\n  ListView _buildList(context) {\n    return ListView.builder(\n      // Must have an item count equal to the number of items!\n      itemCount: doggos.length,\n      // A callback that will return a widget.\n      itemBuilder: (context, int) {\n        // In our case, a DogCard for each doggo.\n        return DogCard(doggos[int]);\n      },\n    );\n  }\n}\n```\n\nThe only thing left to do is to actually **use** the `DogList`. Replace the `DogCard` in main with the `DogList` of Dog Cards.\n\nFirst, import `DogList` to `main.dart`. Note that the `dog_card.dart` import is no longer needed.\n\n```dart\n// main.dart\n\nimport 'package:flutter/material.dart';\n\nimport 'dog_list.dart';\nimport 'dog_model.dart';\n```\n\nThen modify the build method in `_MyHomePageState`:\n\n```dart\n// main.dart\n\n@override\nWidget build(BuildContext context) {\n  return Scaffold(\n    appBar: AppBar(\n      title: Text(widget.title),\n      backgroundColor: Colors.black87,\n    ),\n    body: Container(\n      // Remove the DogCard Widget.\n      // Instead, use your new DogList Class,\n      // Pass in the mock data from the list above.\n      child: Center( // Changed code\n        child: DogList(initialDoggos), // Changed code\n      ),\n    ),\n  );\n}\n```\n\nThis is your app at this point with random doggos photos:\n\n![sample app](https://res.cloudinary.com/ericwindmill/image/upload/c_scale,w_300/v1521385666/flutter_by_example/Simulator_Screen_Shot_-_iPhone_X_-_2018-03-18_at_08.07.33.png)\n","created_at":"2020-07-21T16:23:03.276Z","id":73,"slug":"list-view-and-builder-pattern","title":"ListView and builder pattern","tutorial":8,"updated_at":"2020-07-25T15:28:45.281Z"},{"author":1,"content":"The time has come to add the most important feature: the ability to rate to a dog.\n\n### 1. Add the Form\n\nStart by adding the form UI to `dog_detail_page.dart`.\n\nThis is what the page will look like:\n\n![dog rating page](https://res.cloudinary.com/ericwindmill/image/upload/c_scale,r_5,w_300/v1521395106/flutter_by_example/Simulator_Screen_Shot_-_iPhone_X_-_2018-03-18_at_10.44.53.png)\n\nThe interface will consist of two main widgets:\n  1. a `Slider` to change the rating.\n  2. a `RaisedButton` to submit the slider value.\n\nAdd them both to the `_DogDetailPageState`:\n\n```dart\n// dog_detail_page.dart\n\nclass _DogDetailPageState extends State<DogDetailPage> {\n  final double dogAvatarSize = 150.0;\n  // This is the starting value of the slider.\n  double _sliderValue = 10.0;\n\n  // ...\n\n  Widget get addYourRating {\n    return Column(\n      children: <Widget>[\n        Container(\n          padding: EdgeInsets.symmetric(\n            vertical: 16.0,\n            horizontal: 16.0,\n          ),\n          child: Row(\n            mainAxisAlignment: MainAxisAlignment.spaceBetween,\n            children: <Widget>[\n              // In a row, column, listview, etc., a Flexible widget is a wrapper\n              // that works much like CSS's flex-grow property.\n              //\n              // Any room left over in the main axis after\n              // the widgets are given their width\n              // will be distributed to all the flexible widgets\n              // at a ratio based on the flex property you pass in.\n              // Because this is the only Flexible widget,\n              // it will take up all the extra space.\n              //\n              // In other words, it will expand as much as it can until\n              // the all the space is taken up.\n              Flexible(\n                flex: 1,\n                // A slider, like many form elements, needs to know its\n                // own value and how to update that value.\n                //\n                // The slider will call onChanged whenever the value\n                // changes. But it will only repaint when its value property\n                // changes in the state using setState.\n                //\n                // The workflow is:\n                // 1. User drags the slider.\n                // 2. onChanged is called.\n                // 3. The callback in onChanged sets the sliderValue state.\n                // 4. Flutter repaints everything that relies on sliderValue,\n                // in this case, just the slider at its new value.\n                child: Slider(\n                  activeColor: Colors.indigoAccent,\n                  min: 0.0,\n                  max: 15.0,\n                  onChanged: (newRating) {\n                    setState(() => _sliderValue = newRating);\n                  },\n                  value: _sliderValue,\n                ),\n              ),\n\n              // This is the part that displays the value of the slider.\n              Container(\n                width: 50.0,\n                alignment: Alignment.center,\n                child: Text('${_sliderValue.toInt()}',\n                    style: Theme.of(context).textTheme.display1),\n              ),\n            ],\n          ),\n        ),\n        submitRatingButton,\n      ],\n    );\n  }\n\n  // A simple Raised Button that as of now doesn't do anything yet.\n  Widget get submitRatingButton {\n    return RaisedButton(\n      onPressed: () => print('pressed!'),\n      child: Text('Submit'),\n      color: Colors.indigoAccent,\n    );\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      backgroundColor: Colors.black87,\n      appBar: AppBar(\n        backgroundColor: Colors.black87,\n        title: Text('Meet ${widget.dog.name}'),\n      ),\n      // Make the body a ListView that displays\n      // both the profile and the rating form.\n      body: ListView(\n        children: <Widget>[dogProfile, addYourRating],\n      ),\n    );\n  }\n}\n```\n\nIf you hot reload your app, you should have a working slider.\n\n### 2. Wire up the Submit button\n\nThe `submitRatingButton` does what could *technically*  be done in the `Slider` `onChanged` callback.\n\nIt updates the rating in the dog class itself. That way, throughout the app the new rating is shown, because Flutter will rebuild *everything* that includes the Dog's rating.\n\nThis is as simple as adding this function to your `_DogDetailPageState` class, and then calling it when the submit button is pressed:\n\n```dart\n// dog_detail_page.dart\n\n// In the next section you'll add error handling.\n// For now this is all it does.\nvoid updateRating() {\n  setState(() => widget.dog.rating = _sliderValue.toInt());\n}\n```\n\nAnd then in your `submitRatingButton` widget:\n\n```dart\n// dog_detail_page.dart\n\nWidget get submitRatingButton {\n  return RaisedButton(\n    onPressed: updateRating,\n    child: Text('Submit'),\n    color: Colors.indigoAccent,\n  );\n}\n```\n\nAfter adding this, you can move the slider, press submit, and then travel back to the main page. You should see the dog's rating updated.\n","created_at":"2020-07-21T16:26:14.188Z","id":78,"slug":"sliders-and-buttons","title":"Sliders and Buttons","tutorial":8,"updated_at":"2020-07-24T16:43:06.524Z"},{"author":1,"content":"The only other page we will create is to add dogs.\n\nThe next section will show you how to handle user input, but you might as well add that route now, while we're on the subject.\n\n### 1. Add NewDogPage\n\nCreate a new file in the `lib` folder called `new_dog_form.dart`.\n\nThe UI of this page is simple:\n\n![form page screen shot](https://res.cloudinary.com/ericwindmill/image/upload/c_scale,w_300/v1521390457/flutter_by_example/Simulator_Screen_Shot_-_iPhone_X_-_2018-03-18_at_09.27.27.png)\n\nHere's the code with no functionality (again, you'll add the user input functionality in the next section):\n\n```dart\n// new_dog_form.dart\n\nimport 'package:flutter/material.dart';\n\nclass AddDogFormPage extends StatefulWidget {\n  @override\n  _AddDogFormPageState createState() => _AddDogFormPageState();\n}\n\nclass _AddDogFormPageState extends State<AddDogFormPage> {\n  @override\n  Widget build(BuildContext context) {\n    // new page needs scaffolding!\n    return Scaffold(\n      appBar: AppBar(\n        title: Text('Add a new Dog'),\n        backgroundColor: Colors.black87,\n      ),\n      body: Container(\n        color: Colors.black54,\n        child: Padding(\n          padding: const EdgeInsets.symmetric(\n            vertical: 8.0,\n            horizontal: 32.0,\n          ),\n          child: Column(\n            children: [\n              Padding(\n                padding: const EdgeInsets.only(bottom: 8.0),\n                // Text Field is the basic input widget for Flutter.\n                // It comes built in with a ton of great UI and\n                // functionality, such as the labelText field you see below.\n                child: TextField(\n                    decoration: InputDecoration(\n                  labelText: 'Name the Pup',\n                )),\n              ),\n              Padding(\n                padding: const EdgeInsets.only(bottom: 8.0),\n                child: TextField(\n                    decoration: InputDecoration(\n                  labelText: \"Pup's location\",\n                )),\n              ),\n              Padding(\n                padding: const EdgeInsets.only(bottom: 8.0),\n                child: TextField(\n                  decoration: InputDecoration(\n                    labelText: 'All about the pup',\n                  ),\n                ),\n              ),\n              // A Strange situation.\n              // A piece of the app that you'll add in the next\n              // section *needs* to know its context,\n              // and the easiest way to pass a context is to\n              // use a builder method. So I've wrapped\n              // this button in a Builder as a sort of 'hack'.\n              Padding(\n                padding: const EdgeInsets.all(16.0),\n                child: Builder(\n                  builder: (context) {\n                    // The basic Material Design action button.\n                    return RaisedButton(\n                      // If onPressed is null, the button is disabled\n                      // this is my goto temporary callback.\n                      onPressed: () => print('PRESSED'),\n                      color: Colors.indigoAccent,\n                      child: Text('Submit Pup'),\n                    );\n                  },\n                ),\n              ),\n            ],\n          ),\n        ),\n      ),\n    );\n  }\n}\n```\n\n### 2. Add the Routing\n\nLike the last section, you now have a page that you can't access. Add the button and routing information to the `_MyHomePageState` class.\n\n```dart\n// main.dart\n\n@override\nWidget build(BuildContext context) {\n  return Scaffold(\n    appBar: AppBar(\n      title: Text(widget.title),\n      backgroundColor: Colors.black87,\n      // This is how you add new buttons to the top right of a material appBar.\n      // You can add as many as you'd like.\n      actions: [\n         IconButton(\n          icon: Icon(Icons.add),\n          onPressed: _showNewDogForm,\n        ),\n      ],\n    ),\n  ...\n```\n\nThat will add a plus-sign looking button to the top right corner of your app, and finally you can add the method that builds a new route.\n\nImport `new_dog_form.dart` in `main.dart`:\n\n```dart\n// main.dart\n\nimport 'package:flutter/material.dart';\n\nimport 'dog_list.dart';\nimport 'dog_model.dart';\nimport 'new_dog_form.dart';\n```\n\nAdd this anywhere in your `_MyHomePageState` class:\n\n```dart\n// Any time you're pushing a new route and expect that route\n// to return something back to you,\n// you need to use an async function.\n// In this case, the function will create a form page\n// which the user can fill out and submit.\n// On submission, the information in that form page\n// will be passed back to this function.\nFuture _showNewDogForm() async {\n  // push a new route like you did in the last section\n  Dog newDog = await Navigator.of(context).push(\n    MaterialPageRoute(\n      builder: (BuildContext context) {\n        return AddDogFormPage();\n      },\n    ),\n  );\n  // A null check, to make sure that the user didn't abandon the form.\n  if (newDog != null) {\n    // Add a newDog to our mock dog array.\n    initialDoggos.add(newDog);\n  }\n}\n```\n","created_at":"2020-07-21T16:25:20.996Z","id":76,"slug":"routing-2-add-a-form-page","title":"Routing 2: Add a form page","tutorial":8,"updated_at":"2020-07-24T16:43:06.524Z"},{"author":1,"content":"Adding functionality to your `AddDogForm` is a pretty easy feat.\n\nEssentially, you need to add a couple built in Flutter classes that keep track of form input, and a function that returns the data to the main page through the router.\n\n### 1. TextEditingController class\n\nThere are a couple ways to go about tracking text input form elements. You can use `Form` widgets, or you can track each text input separately.\n\nIn this example, I will show you how to do the latter. `TextEditingController` is an important and fundamental thing in Flutter.\n\nA `TextEditingController` is basically a class that listens to its assigned `TextField`, and updates it's own internal state every time the text in the `TextField` changes.\n\nIn your `_AddDogFormPageState` class, add a `controller` and `onChanged` property to each `TextField`:\n\n```dart\n// new_dog_form.dart\n\nclass _AddDogFormPageState extends State<AddDogFormPage> {\n  // One TextEditingController for each form input:\n  TextEditingController nameController = TextEditingController();\n  TextEditingController locationController = TextEditingController();\n  TextEditingController descriptionController = TextEditingController();\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(\n        title: Text('Add a new Dog'),\n        backgroundColor: Colors.black87,\n      ),\n      body: Container(\n        color: Colors.black54,\n        child: Padding(\n          padding: const EdgeInsets.symmetric(\n            vertical: 8.0,\n            horizontal: 32.0,\n          ),\n          child: Column(\n            children: [\n              Padding(\n                padding: const EdgeInsets.only(bottom: 8.0),\n                child: TextField(\n                    // Tell your textfield which controller it owns\n                    controller: nameController,\n                    // Every single time the text changes in a\n                    // TextField, this onChanged callback is called\n                    // and it passes in the value.\n                    //\n                    // Set the text of your controller to\n                    // to the next value.\n                    onChanged: (v) => nameController.text = v,\n                    decoration: InputDecoration(\n                      labelText: 'Name the Pup',\n                    )),\n              ),\n              Padding(\n                padding: const EdgeInsets.only(bottom: 8.0),\n                child: TextField(\n                    controller: locationController,\n                    onChanged: (v) => locationController.text = v,\n                    decoration: InputDecoration(\n                      labelText: \"Pups location\",\n                    )),\n              ),\n              Padding(\n                padding: const EdgeInsets.only(bottom: 8.0),\n                child: TextField(\n                    controller: descriptionController,\n                    onChanged: (v) => descriptionController.text = v,\n                    decoration: InputDecoration(\n                      labelText: 'All about the pup',\n                    )),\n              ),\n              Padding(\n                padding: const EdgeInsets.all(16.0),\n                child: Builder(\n                  builder: (context) {\n                    return RaisedButton(\n                      onPressed: () => print('PRESSED'),\n                      color: Colors.indigoAccent,\n                      child: Text('Submit Pup'),\n                    );\n                  },\n                ),\n              ),\n            ],\n          ),\n        ),\n      ),\n    );\n  }\n}\n```\n\nNow, even though it doesn't look like anything new is happening, the `TextEditingControllers` are keeping track of your form.\n\n### 2. Submit The Form\n\nImport `dog_model.dart` into `new_dog_form.dart`:\n\n```dart\n// new_dog_form.dart\n\nimport 'package:flutter/material.dart';\n\nimport 'dog_model.dart';\n```\n\nIn the same `_AddDogFormPageState` class, add the `submitPup` function, which will pass the form information back via the `Navigator`:\n\n```dart\n// new_dog_form.dart\n\n// You'll need the context in order for the Navigator to work.\nvoid submitPup(BuildContext context) {\n  // First make sure there is some information in the form.\n  // A dog needs a name, but may be location independent,\n  // so we'll only abandon the save if there's no name.\n  if (nameController.text.isEmpty) {\n    print('Dogs need names!');\n  } else {\n    // Create a new dog with the information from the form.\n    var newDog = Dog(nameController.text, locationController.text,\n        descriptionController.text);\n    // Pop the page off the route stack and pass the new\n    // dog back to wherever this page was created.\n    Navigator.of(context).pop(newDog);\n  }\n}\n```\n\nAnd lastly, add `submitPup` to your 'RaisedButton' `onPressed` callback:\n\n```dart\n// new_dog_form.dart\n\nbuilder: (BuildContext context) {\n  return RaisedButton(\n    onPressed: () => submitPup(context),\n    color: Colors.indigoAccent,\n    child: Text('Submit Pup'),\n  );\n},\n```\n\nAnd that's that. Now, you should be able to submit a new dog and see it on your main page!\n","created_at":"2020-07-21T16:25:49.304Z","id":77,"slug":"user-input","title":"User Input","tutorial":8,"updated_at":"2020-07-24T16:43:06.524Z"},{"author":1,"content":"> It's a Good Framework, Brant.\n\nIn this basic app tutorial we'll build a very simple, **pure Flutter app**, inspired by the greatest Twitter conversation of all time -- the We Rate Dogs v. Brant showdown of 2012.\n\nThe idea here is that we won't pull in any extra packages, we won't think about architecture or state management. We'll just use the tools that Flutter gives us out of the box to learn the basics.\n\nBy the end of this, you should be pretty amazed by how much Flutter gives us out of the box -- and we won't cover nearly everything.\n\nThis is what you're making:\n\n![add dog](https://res.cloudinary.com/ericwindmill/image/upload/c_scale,w_300/v1520699901/flutter_by_example/new_dog.gif)\n![good dogs](https://res.cloudinary.com/ericwindmill/image/upload/c_scale,w_300/v1520699902/flutter_by_example/good_dogs.gif)\n\nFinished source code: [We Rate Dogs example app](https://github.com/ericwindmill/flutter_by_example/examples/we_rate_dogs/basic_example)\n\nWe'll just start this from a fresh app. Once you have [Flutter installed on your machine and your environment set up](https://flutter.io), create a new Flutter app:\n\n```bash\nflutter create basic_flutter_app\ncd basic_flutter_app\nflutter run\n```\n\nAlternatively you can clone the pre-generated app from [GitHub](https://github.com/ericwindmill/flutter_by_example_apps).\n\n```bash\ngit clone https://github.com/ericwindmill/flutter_by_example_apps.git\ncd flutter_by_example_apps/blank_flutter_app\nflutter packages get\nflutter run\n```\n\nThis will give you a fresh, blank Flutter app.","created_at":"2020-07-21T16:21:32.005Z","id":70,"slug":"intro-and-setup-basic-app","title":"Intro and Setup","tutorial":8,"updated_at":"2020-07-24T16:48:10.310Z"},{"author":1,"content":"The hero transition is even more impressive and easier to work with.\n\nThis is what a hero animation does:\n\n![Hero animation screenshot](https://res.cloudinary.com/ericwindmill/image/upload/c_scale,r_5,w_300/v1521400580/flutter_by_example/hero_animation.gif)\n\nAnd you can make it happen with four new lines of code.\n\n### 1. Update the `dogImage` method of your `_DogCardState` class\n\n```dart\n// dog_card.dart\n\nWidget get dogImage {\n  // Wrap the dogAvatar widget in a Hero widget.\n  var dogAvatar = Hero(\n    // Give your hero a tag.\n    //\n    // Flutter looks for two widgets on two different pages,\n    // and if they have the same tag it animates between them.\n    tag: dog,\n    child: Container(\n\n    // ...\n    // Close the Hero parentheses at the bottom of the dogAvatar widget.\n```\n\n### 2. Update the `dogImage` method of your `_DogDetailPageState` class\n\nAdd almost the exact same two links of code:\n\n```dart\n  Widget get dogImage {\n    return Hero(\n      // The same code, except the Dog property lives on the widget in this file.\n      tag: widget.dog,\n      child: Container(\n        height: dogAvatarSize,\n\n    // ...\n    // Close the Hero parentheses at the bottom of your widget.\n```\n","created_at":"2020-07-21T16:28:43.293Z","id":81,"slug":"built-in-animation-hero-transition","title":"Built-in Animation: Hero transition","tutorial":8,"updated_at":"2020-07-24T16:43:06.524Z"},{"author":1,"content":"Time to make the app a little prettier by adding a gradient background.\n\nGradients are just as easy in Flutter as the are in CSS. And that's good since they're so hot right now.\n\nTo use gradients, you first need a `Container` widget, and within that you need to access its `decoration` property.\n\nStart by building the decoration of the `Container` widget in your `_MyHomePageState` `build` method in `main.dart`.\n\n```dart\n// main.dart\n\n@override\nWidget build(BuildContext context) {\n  return Scaffold(\n    appBar: AppBar(\n      title: Text(widget.title),\n      backgroundColor: Colors.black87,\n    ),\n    body: Container(\n      // Add box decoration\n      decoration: BoxDecoration(\n        // Box decoration takes a gradient\n        gradient: LinearGradient(\n          // Where the linear gradient begins and ends\n          begin: Alignment.topRight,\n          end: Alignment.bottomLeft,\n          // Add one stop for each color. Stops should increase from 0 to 1\n          stops: [0.1, 0.5, 0.7, 0.9],\n          colors: [\n            // Colors are easy thanks to Flutter's Colors class.\n            Colors.indigo[800],\n            Colors.indigo[700],\n            Colors.indigo[600],\n            Colors.indigo[400],\n          ],\n        ),\n      ),\n      child: Center(\n        child: DogList(initialDoggos),\n      ),\n    ),\n  );\n}\n```\n\nNow, gradients:\n\n![gradient screen shot](https://res.cloudinary.com/ericwindmill/image/upload/c_scale,w_300/v1521385515/flutter_by_example/Simulator_Screen_Shot_-_iPhone_X_-_2018-03-18_at_07.54.46.png)\n","created_at":"2020-07-21T16:24:16.584Z","id":74,"slug":"gradient-backgrounds","title":"Gradient Backgrounds","tutorial":8,"updated_at":"2020-07-24T16:43:06.524Z"},{"author":1,"content":"### 1. Create Dog Card Widget\n\nWe need a nice widget to display our doggos.\n\nFirst you'll make a card that looks like this:\n\n![dog card](https://res.cloudinary.com/ericwindmill/image/upload/v1521328467/flutter_by_example/Screen_Shot_2018-03-10_at_10.28.18_AM.png)\n\nCreate a new file called 'dog_card.dart`.\n\nIn that file, make a new, blank `StatefulWidget`. It should take a Dog in its constructor.\n\nFor the time being, all this will do is display the name of a dog.\n\n```dart\n// dog_card.dart\n\nimport 'package:flutter/material.dart';\n\nimport 'dog_model.dart';\n\nclass DogCard extends StatefulWidget {\n  final Dog dog;\n\n  DogCard(this.dog);\n\n  @override\n  _DogCardState createState() => _DogCardState(dog);\n}\n\nclass _DogCardState extends State<DogCard> {\n   Dog dog;\n\n   _DogCardState(this.dog);\n\n  @override\n  Widget build(BuildContext context) {\n    return Text(widget.dog.name);\n  }\n}\n```\n\nIn order to make the `DogCard` appear, let's modify the `_MyHomePageState` `build` method in `main.dart`:\n\n```dart\n// main.dart\n\n@override\n  Widget build(BuildContext context) {\n    return Scaffold(\n        appBar: AppBar(\n          title: Text(widget.title),\n          backgroundColor: Colors.black87,\n        ),\n        body: Container(\n          child: DogCard(initialDoggos[1]), // New code\n        ),\n    );\n  }\n```\n\nAnd import `dog_card.dart`:\n\n```dart\n// main.dart\n\nimport 'package:flutter/material.dart';\n\nimport 'dog_card.dart';\nimport 'dog_model.dart';\n```\n\nRefresh your app and you can see that it's wired up now. Time to build the card.\n\n### 2. Dog Card UI\n\nThere are two main parts to this card. The image, and the actual Card that sits under it.\n\nFirst, make that image.\n\nAdd this getter to you `_DogCardState` class:\n\n```dart\n// dog_card.dart\n\n// A class property that represents the URL flutter will render\n// from the Dog class.\nString renderUrl;\n\nWidget get dogImage {\n  return Container(\n      // You can explicitly set heights and widths on Containers.\n      // Otherwise they take up as much space as their children.\n    width: 100.0,\n    height: 100.0,\n      // Decoration is a property that lets you style the container.\n      // It expects a BoxDecoration.\n    decoration: BoxDecoration(\n      // BoxDecorations have many possible properties.\n      // Using BoxShape with a background image is the\n      // easiest way to make a circle cropped avatar style image.\n      shape: BoxShape.circle,\n      image: DecorationImage(\n        // Just like CSS's `imagesize` property.\n        fit: BoxFit.cover,\n        // A NetworkImage widget is a widget that\n        // takes a URL to an image.\n        // ImageProviders (such as NetworkImage) are ideal\n        // when your image needs to be loaded or can change.\n        // Use the null check to avoid an error.\n        image: NetworkImage(renderUrl ?? ''),\n      ),\n    ),\n  );\n}\n```\n\nIn order to see this image, you'll first need to tell the Dog class to get that image from the internets.\n\nIn your dog card, add this to your `_DogCardState` class:\n\n```dart\n// dog_card.dart\n\n// State classes run this method when the state is created.\n// You shouldn't do async work in initState, so we'll defer it\n// to another method.\nvoid initState() {\n  super.initState();\n  renderDogPic();\n}\n\n// IRL, we'd want the Dog class itself to get the image\n// but this is a simpler way to explain Flutter basics\nvoid renderDogPic() async {\n  // this makes the service call\n  await dog.getImageUrl();\n  // setState tells Flutter to rerender anything that's been changed.\n  // setState cannot be async, so we use a variable that can be overwritten\n  if (mounted) { // Avoid calling `setState` if the widget is no longer in the widget tree.\n    setState(() {\n      renderUrl = dog.imageUrl;\n    });\n  }\n}\n```\n\nNow you have a doggos avatar that's properly getting the URL to render.\n\nIn order to get the overlap look of the card, use the built-in widget `Stack`.\n\nThe `Stack` widget lays out its children relative to its edges.\n\nIn other words, it's CSS's `position, top, bottom, left and right` properties.\n\nWithin a stack, you can wrap children in 'Position' widgets, but you don't have to.\n\n* Position wrapped widgets are outside of document flow, to use web development terms. They'll be at position [0,0] by default -- the top corner of the Stack widget.\n* Non-wrapped widgets aren't positioned. They stay in normal 'document flow', laid out as a column of widgets by default.\n\nThis is how the stack is going to start:\n\n```dart\n// dog_card.dart\n\n@override\nWidget build(BuildContext context) {\n  // Start with a container so we can add layout and style props:\n  return Container(\n    // Arbitrary number that I decided looked good:\n    height: 115.0,\n    // A stack takes children, with a list of widgets.\n    child: Stack(\n      children: <Widget>[\n        // position our dog image, so we can explicitly place it.\n        // We'll place it after we've made the card.\n        Positioned(\n        child: dogImage,\n        ),\n      ],\n    ),\n  );\n}\n```\n\nReload your app to see a picture of a dog in the top corner.\n\nLet's create the card and layout in `_DogCardState`:\n\n```dart\n// dog_card.dart\n\nWidget get dogCard {\n  // A new container\n  // The height and width are arbitrary numbers for styling.\n  return Container(\n    width: 290.0,\n    height: 115.0,\n    child: Card(\n      color: Colors.black87,\n      // Wrap children in a Padding widget in order to give padding.\n      child: Padding(\n        // The class that controls padding is called 'EdgeInsets'\n        // The EdgeInsets.only constructor is used to set\n        // padding explicitly to each side of the child.\n        padding: const EdgeInsets.only(\n          top: 8.0,\n          bottom: 8.0,\n          left: 64.0,\n        ),\n        // Column is another layout widget -- like stack -- that\n        // takes a list of widgets as children, and lays the\n        // widgets out from top to bottom.\n        child: Column(\n          // These alignment properties function exactly like\n          // CSS flexbox properties.\n          // The main axis of a column is the vertical axis,\n          // `MainAxisAlignment.spaceAround` is equivalent of\n          // CSS's 'justify-content: space-around' in a vertically\n          // laid out flexbox.\n          crossAxisAlignment: CrossAxisAlignment.start,\n          mainAxisAlignment: MainAxisAlignment.spaceAround,\n          children: <Widget>[\n            Text(widget.dog.name,\n                // Themes are set in the MaterialApp widget at the root of your app.\n                // They have default values -- which we're using because we didn't set our own.\n                // They're great for having consistent, app-wide styling that's easily changed.\n                style: Theme.of(context).textTheme.headline),\n            Text(widget.dog.location,\n                style: Theme.of(context).textTheme.subhead),\n            Row(\n              children: <Widget>[\n                Icon(\n                  Icons.star,\n                ),\n                Text(': ${widget.dog.rating} / 10')\n              ],\n            )\n          ],\n        ),\n      ),\n    ),\n  );\n}\n```\n\nAlmost there. One more thing you need to do to complete the `DogCard` UI. Add a bit more styling to the main widget in the build method:\n\n```dart\n// dog_card.dart\n\n@override\nWidget build(BuildContext context) {\n  return Padding(\n    padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),\n    child: Container(\n      height: 115.0,\n      child: Stack(\n        children: <Widget>[\n          Positioned(\n            left: 50.0,\n            child: dogCard,\n          ),\n          Positioned(top: 7.5, child: dogImage),\n        ],\n      ),\n    ),\n  );\n}\n```\n","created_at":"2020-07-21T16:22:43.289Z","id":72,"slug":"build-a-custom-widget-basic-app","title":"Build a custom widget","tutorial":8,"updated_at":"2020-07-24T16:43:06.524Z"},{"author":1,"content":"Good UX is all about being explicit about what's going on the in app when something updates. So the app should give users feedback in two places:\n1. When you try to add a new dog but the form is wrong.\n2. When you try to update a dog's rating but the form is wrong.\n\n### 1. Feedback when Adding a New Dog\n\n![show snackbar on error screenshot](https://res.cloudinary.com/ericwindmill/image/upload/c_scale,r_5,w_300/v1521398916/flutter_by_example/Simulator_Screen_Shot_-_iPhone_X_-_2018-03-18_at_11.48.20.png)\n\nOnly the `submitPup` function needs to be updated in your `new_dog_form` page.\n\nFirst, some explanation that I glossed over in an earlier lesson:\n\nIn the build method on this page, the `RaisedButton` is wrapped in a `Builder`.\n\nA builder creates a new scaffold under the hood. It's a new page, a new `BuildContext`, etc.\n\nThis new scaffold is necessary to show `Snackbar`s (aka toasts on the web) because they are an element that is on another page, waiting to be called like any page would be.\n\nThe *better* way to do this would be to make a whole new stateless widget that's just a `RaisedButton` that shows a `Snackbar` on pressed.\n\nAnyway, update your `submitPup` method:\n\n```dart\n// new_dog_form.dart\n\nvoid submitPup(context) {\n  if (nameController.text.isEmpty) {\n    Scaffold.of(context).showSnackBar(\n      SnackBar(\n        backgroundColor: Colors.redAccent,\n        content: Text('Pups neeed names!'),\n      ),\n    );\n  } else {\n    var newDog = Dog(nameController.text, locationController.text,\n        descriptionController.text);\n    Navigator.of(context).pop(newDog);\n  }\n}\n```\n\nNow, try to submit a dog without a name.\n\n### 2. Dialog Feedback when the Rating Form is wrong\n\nThis time, you'll implement a `Dialog`, which on mobile is just a modal.\n\n`Dialog`s in Flutter give you access to `actions` so you can do things like ask user questions, get ratings, etc.\n\nWe're going to use what's called an `AlertDialog`, which alerts the users of something.\n\nThis goes in your `_DogDetailPageState`.\n\n```dart\n// dog_detail_page.dart\n\n// Just like a route, this needs to be async, because it can return\n// information when the user interacts.\nFuture<Null> _ratingErrorDialog() async {\n  // showDialog is a built-in Flutter method.\n  return showDialog(\n    context: context,\n    builder: (BuildContext context) {\n      return AlertDialog(\n        title: Text('Error!'),\n        content: Text(\"They're good dogs, Brant.\"),\n        // This action uses the Navigator to dismiss the dialog.\n        // This is where you could return information if you wanted to.\n        actions: [\n          FlatButton(\n            child: Text('Try Again'),\n            onPressed: () => Navigator.of(context).pop(),\n          )\n        ],\n      );\n    },\n  );\n}\n```\n\nFinally, update your `updateRating` method to handle that error:\n\n```dart\nvoid updateRating() {\n  if (_sliderValue < 10) {\n    _ratingErrorDialog();\n  } else {\n    setState(() => widget.dog.rating = _sliderValue.toInt());\n  }\n}\n```\n\n\nNow, when someone tries to give a dog a low rating, we'll stop them.\n\nAfter all, they're good dogs.\n","created_at":"2020-07-21T16:26:45.724Z","id":79,"slug":"snackbars-and-dialogs","title":"Snackbars and Dialogs","tutorial":8,"updated_at":"2020-07-24T16:43:06.524Z"},{"author":1,"content":"There are a couple different ways to add more pages to your Flutter app.\n\nFor all pages you *know* will exist, you can use declared routes.\n\nBut this app just builds pages on the fly for each individual dog. This is a good case for route builders.\n\n### 1. Create a Dog Detail Page:\n\nCreate a new file called `dog_detail_page.dart`\n\nThis 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.\n\nFor now this is what you'll be building:\n\n![dog detail screenshot](https://res.cloudinary.com/ericwindmill/image/upload/c_scale,w_300/v1521388587/flutter_by_example/Simulator_Screen_Shot_-_iPhone_X_-_2018-03-18_at_08.51.49.png)\n\n```dart\n// dog_detail_page.dart\n\nimport 'package:flutter/material.dart';\n\nimport 'dog_model.dart';\n\nclass DogDetailPage extends StatefulWidget {\n  final Dog dog;\n\n  DogDetailPage(this.dog);\n\n  @override\n  _DogDetailPageState createState() => _DogDetailPageState();\n}\n\nclass _DogDetailPageState extends State<DogDetailPage> {\n  // Arbitrary size choice for styles\n  final double dogAvatarSize = 150.0;\n\n  Widget get dogImage {\n    // Containers define the size of its children.\n    return Container(\n      height: dogAvatarSize,\n      width: dogAvatarSize,\n      // Use Box Decoration to make the image a circle\n      // and add an arbitrary shadow for styling.\n      decoration: BoxDecoration(\n        shape: BoxShape.circle,\n        // Like in CSS you often want to add multiple\n        // BoxShadows for the right look so the\n        // boxShadow property takes a list of BoxShadows.\n        boxShadow: [\n          const BoxShadow(\n              // just like CSS:\n              // it takes the same 4 properties\n              offset: const Offset(1.0, 2.0),\n              blurRadius: 2.0,\n              spreadRadius: -1.0,\n              color: const Color(0x33000000)),\n          const BoxShadow(\n              offset: const Offset(2.0, 1.0),\n              blurRadius: 3.0,\n              spreadRadius: 0.0,\n              color: const Color(0x24000000)),\n          const BoxShadow(\n              offset: const Offset(3.0, 1.0),\n              blurRadius: 4.0,\n              spreadRadius: 2.0,\n              color: const Color(0x1F000000)),\n        ],\n        // This is how you add an image to a Container's background.\n        image: DecorationImage(\n          fit: BoxFit.cover,\n          image: NetworkImage(widget.dog.imageUrl),\n        ),\n      ),\n    );\n  }\n\n  // The rating section that says ★ 10/10.\n  Widget get rating {\n    // Use a row to lay out widgets horizontally.\n    return Row(\n      // Center the widgets on the main-axis\n      // which is the horizontal axis in a row.\n      mainAxisAlignment: MainAxisAlignment.center,\n      children: <Widget>[\n        Icon(\n          Icons.star,\n          size: 40.0,\n        ),\n        Text(' ${widget.dog.rating} / 10',\n            style: Theme.of(context).textTheme.display2),\n      ],\n    );\n  }\n\n  // The widget that displays the image, rating and dog info.\n  Widget get dogProfile {\n    return Container(\n      padding: EdgeInsets.symmetric(vertical: 32.0),\n      decoration: BoxDecoration(\n        // This would be a great opportunity to create a custom LinearGradient widget\n        // that could be shared throughout the app but I'll leave that to you.\n        gradient: LinearGradient(\n          begin: Alignment.topRight,\n          end: Alignment.bottomLeft,\n          stops: [0.1, 0.5, 0.7, 0.9],\n          colors: [\n            Colors.indigo[800],\n            Colors.indigo[700],\n            Colors.indigo[600],\n            Colors.indigo[400],\n          ],\n        ),\n      ),\n      // The Dog Profile information.\n      child: Column(\n        crossAxisAlignment: CrossAxisAlignment.center,\n        children: <Widget>[\n          dogImage,\n          Text(\n            '${widget.dog.name}  🎾',\n            style: TextStyle(fontSize: 32.0),\n          ),\n          Text(\n            widget.dog.location,\n            style: TextStyle(fontSize: 20.0),\n          ),\n          Padding(\n            padding:\n                const EdgeInsets.symmetric(horizontal: 32.0, vertical: 16.0),\n            child: Text(widget.dog.description),\n          ),\n          rating\n        ],\n      ),\n    );\n  }\n\n  //Finally, the build method:\n  //\n  // Aside:\n  // It's often much easier to build UI if you break up your widgets the way I\n  // have in this file rather than trying to have one massive build method\n  @override\n  Widget build(BuildContext context) {\n    // This is a new page, so you need a new Scaffold!\n    return Scaffold(\n      backgroundColor: Colors.black87,\n      appBar: AppBar(\n        backgroundColor: Colors.black87,\n        title: Text('Meet ${widget.dog.name}'),\n      ),\n      body: dogProfile,\n    );\n  }\n}\n```\n\n### 2. Add the Routing mechanism:\n\nNow you've added the `DogDetailPage` but you can't get to it. Let's add some routing.\n\nOn your main page that lists all the dogs, each card will be a button that when tapped brings up that dog's detail page.\n\nImport `DogDetailPage` into `dog_card.dart`:\n\n```dart\n// dog_card.dart\n\nimport 'package:flutter/material.dart';\n\nimport 'dog_detail_page.dart';\nimport 'dog_model.dart';\n\n```\n\nIn your `_DogCardState` class, have `build` wrap everything in a button:\n\n```dart\n// dog_card.dart\n\n@override\nWidget build(BuildContext context) {\n  // InkWell is a special Material widget that makes its children tappable\n  // and adds Material Design ink ripple when tapped.\n  return InkWell(\n    // onTap is a callback that will be triggered when tapped.\n    onTap: showDogDetailPage,\n    child: Padding(\n      padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),\n      child: Container(\n        height: 115.0,\n        child: Stack(\n          children: <Widget>[\n            Positioned(\n              left: 50.0,\n              child: dogCard,\n            ),\n            Positioned(top: 7.5, child: dogImage),\n          ],\n        ),\n      ),\n    ),\n  );\n}\n\n// This is the builder method that creates a new page.\nshowDogDetailPage() {\n  // Navigator.of(context) accesses the current app's navigator.\n  // Navigators can 'push' new routes onto the stack,\n  // as well as pop routes off the stack.\n  //\n  // This is the easiest way to build a new page on the fly\n  // and pass that page some state from the current page.\n  Navigator.of(context).push(\n    MaterialPageRoute(\n      // builder methods always take context!\n      builder: (context) {\n        return DogDetailPage(dog);\n      },\n    ),\n  );\n}\n```\n\nYour app now has pages for each and every dog.\n\nAnd you may have noticed that there's a back button on the dog detail page, but there's no code for it.\n\nFlutter 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.\n","created_at":"2020-07-21T16:24:53.143Z","id":75,"slug":"routing-add-a-detail-page","title":"Routing: Add a detail page","tutorial":8,"updated_at":"2020-07-24T16:43:06.524Z"}]},"strapiTableOfContents":{"contents":"{\n  \"Dart\": {\n    \"Getting Started with Dart\": [\n      \"About Dart\",\n      \"Install Dart on your machine\",\n      \"Dartpad\",\n      \"Text Editors: Intellij and VSCode\",\n      \"Resources: Documentation and Pub.dev\",\n      \"Hello World\",\n      \"The main function\",\n      \"Print to the console\"\n    ],\n    \"Dart Fundamentals\": [\n      \"Values and variables\",\n      \"Comments\",\n      \"const and final variables\",\n      \"Arithmetic and Comparison Operators\",\n      \"Assignment Operators\",\n      \"Logical Operators\",\n      \"Null Aware Operators\",\n      \"Type Test Operators\",\n      \"Bitwise and Shift Operators\",\n      \"Control Flow: if, else, else if\",\n      \"Switch statements and case\",\n      \"Ternary Conditional operator\",\n      \"Loops: for and while\",\n      \"Anatomy of Dart Functions\",\n      \"Arrow functions\",\n      \"Function arguments: default, optional, named\",\n      \"Lexical Scope\",\n      \"Cascade notation\"\n    ],\n    \"Data Types\": [\n      \"Intro to Dart's Type System\",\n      \"Numbers\",\n      \"Strings\",\n      \"Booleans\",\n      \"dynamic\",\n      \"lists\",\n      \"sets\",\n      \"maps\"\n    ],\n    \"Object-Oriented Programming\": [\n      \"Intro to OOP\",\n      \"Classes\",\n      \"Constructors\",\n      \"Properties and methods\",\n      \"Methods: static, private, etc\",\n      \"Getters and setters\",\n      \"Extending classes (inheritance)\",\n      \"Initializer lists and final properties\",\n      \"Factory methods\",\n      \"Singletons\",\n      \"Abstract classes (and interfaces)\",\n      \"Mixins\",\n      \"Extension methods\"\n    ],\n    \"Iterables, Iterators, and Collections\": [\n      \"What are collections (and iterables)?\",\n      \"Looping: for-in and forEach\",\n      \"Reading elements pt 1: first, last\",\n      \"Adding elements: add and insert (all)\",\n      \"Checking for elements: contains, indexOf, any, every\",\n      \"Removing elements: remove, clear, removeWhere\",\n      \"Filtering elements: where, takeWhile, and skipWhile\",\n      \"Changing elements: map and expand\",\n      \"Deriving values from elements: fold, reduce, join\",\n      \"Type casting collections: cast, as, retype, toSet, toList\",\n      \"Iterators: understanding and creating your own\",\n      \"Iterable-like methods on maps (and putIfAbsent)\"\n    ]\n  },\n  \"Flutter\": {\n    \"Getting started with Flutter\": [\n      \"About Flutter\",\n      \"IDEs and resources\"\n    ],\n    \"Widgets\": [\n      \"Intro to Widgets\",\n      \"Widget types: Stateful and Stateless\",\n      \"StatefulWidget lifecycle\",\n      \"The Widget tree\",\n      \"BuildContext\",\n      \"Inherited Widgets\",\n      \"Thinking in widgets\"\n    ],\n    \"Intro Flutter App\": [\n      \"Intro and Setup\",\n      \"Data Model and HTTP\",\n      \"Build a custom widget\",\n      \"ListView and builder pattern\",\n      \"Gradient Backgrounds\",\n      \"Routing: Add a detail page\",\n      \"Routing 2: Add a form page\",\n      \"User Input\",\n      \"Sliders and Buttons\",\n      \"Snackbars and Dialogs\",\n      \"Built-in Animation: AnimatedCrossFade\",\n      \"Built-in Animation: Hero transition\"\n    ],\n    \"Custom Animation: Progress Indicator\": [\n      \"Intro and Overview\",\n      \"Build the example app boiler-plate\",\n      \"Custom Widget: Peg\",\n      \"Tween and AnimationController classes\",\n      \"Tween by example\",\n      \"Using Tweens and Intervals\",\n      \"Wrap the Pegs in AnimatedWidgets\",\n      \"Bring it all together\"\n    ],\n    \"State Management: Blocs without Libraries\": [\n      \"What are blocs?\",\n      \"Calendar App introduction\",\n      \"Create bloc one\",\n      \"Create a bloc provider\",\n      \"Using StreamBuilders with blocs\",\n      \"Create bloc two: Add/Edit Tasks\",\n      \"Consume the second bloc's streams\",\n      \"Complete source code\"\n    ],\n    \"State Management: Provider\": [\n      \"What is Provider?\",\n      \"The most basic example using Provider\",\n      \"ChangeNotifierProvider\",\n      \"Rebuilding widgets with Consumer\",\n      \"Finer build control with Selector\",\n      \"Future Provider\",\n      \"MultiProvider micro lesson\",\n      \"Stream Provider\",\n      \"Using context extensions for more control\",\n      \"ProxyProvider\",\n      \"Using .value constructors\",\n      \"The final example (A shopping cart app)\",\n      \"For the curious: How is provider implemented\"\n    ],\n    \"Handling Data with Brick: Offline First with Rest\": [\n      \"About Brick and setup\",\n      \"Adding a Repository\",\n      \"Adding a Model\",\n      \"Generating Code\",\n      \"Rendering Models\",\n      \"Adding an Association\"\n    ]\n  }\n}"}},"pageContext":{"slug":"sliders-and-buttons","tutorialTitle":"Intro Flutter App","previous":{"author":1,"content":"Right now you just have a card for your dog. It would be more useful to render all of them as a list.\n\nOne of the most important concepts in Flutter UI is rendering UI lists, which is often done in builder methods.\n\nBuilder methods essentially create a widget once for each piece of data in a Dart `List`.\n\nFirst, create a new file called `dog_list.dart`.\n\n## 1. DogList Class\n\n```dart\n// dog_list.dart\n\nimport 'package:flutter/material.dart';\n\nimport 'dog_card.dart';\nimport 'dog_model.dart';\n\nclass DogList extends StatelessWidget {\n  // Builder methods rely on a set of data, such as a list.\n  final List<Dog> doggos;\n  DogList(this.doggos);\n\n  // First, make your build method like normal.\n  // Instead of returning Widgets, return a method that returns widgets.\n  // Don't forget to pass in the context!\n  @override\n  Widget build(BuildContext context) {\n    return _buildList(context);\n  }\n\n  // A builder method almost always returns a ListView.\n  // A ListView is a widget similar to Column or Row.\n  // It knows whether it needs to be scrollable or not.\n  // It has a constructor called builder, which it knows will\n  // work with a List.\n\n  ListView _buildList(context) {\n    return ListView.builder(\n      // Must have an item count equal to the number of items!\n      itemCount: doggos.length,\n      // A callback that will return a widget.\n      itemBuilder: (context, int) {\n        // In our case, a DogCard for each doggo.\n        return DogCard(doggos[int]);\n      },\n    );\n  }\n}\n```\n\nThe only thing left to do is to actually **use** the `DogList`. Replace the `DogCard` in main with the `DogList` of Dog Cards.\n\nFirst, import `DogList` to `main.dart`. Note that the `dog_card.dart` import is no longer needed.\n\n```dart\n// main.dart\n\nimport 'package:flutter/material.dart';\n\nimport 'dog_list.dart';\nimport 'dog_model.dart';\n```\n\nThen modify the build method in `_MyHomePageState`:\n\n```dart\n// main.dart\n\n@override\nWidget build(BuildContext context) {\n  return Scaffold(\n    appBar: AppBar(\n      title: Text(widget.title),\n      backgroundColor: Colors.black87,\n    ),\n    body: Container(\n      // Remove the DogCard Widget.\n      // Instead, use your new DogList Class,\n      // Pass in the mock data from the list above.\n      child: Center( // Changed code\n        child: DogList(initialDoggos), // Changed code\n      ),\n    ),\n  );\n}\n```\n\nThis is your app at this point with random doggos photos:\n\n![sample app](https://res.cloudinary.com/ericwindmill/image/upload/c_scale,w_300/v1521385666/flutter_by_example/Simulator_Screen_Shot_-_iPhone_X_-_2018-03-18_at_08.07.33.png)\n","created_at":"2020-07-21T16:23:03.276Z","id":73,"slug":"list-view-and-builder-pattern","title":"ListView and builder pattern","tutorial":8,"updated_at":"2020-07-25T15:28:45.281Z"},"next":{"author":1,"content":"The only other page we will create is to add dogs.\n\nThe next section will show you how to handle user input, but you might as well add that route now, while we're on the subject.\n\n### 1. Add NewDogPage\n\nCreate a new file in the `lib` folder called `new_dog_form.dart`.\n\nThe UI of this page is simple:\n\n![form page screen shot](https://res.cloudinary.com/ericwindmill/image/upload/c_scale,w_300/v1521390457/flutter_by_example/Simulator_Screen_Shot_-_iPhone_X_-_2018-03-18_at_09.27.27.png)\n\nHere's the code with no functionality (again, you'll add the user input functionality in the next section):\n\n```dart\n// new_dog_form.dart\n\nimport 'package:flutter/material.dart';\n\nclass AddDogFormPage extends StatefulWidget {\n  @override\n  _AddDogFormPageState createState() => _AddDogFormPageState();\n}\n\nclass _AddDogFormPageState extends State<AddDogFormPage> {\n  @override\n  Widget build(BuildContext context) {\n    // new page needs scaffolding!\n    return Scaffold(\n      appBar: AppBar(\n        title: Text('Add a new Dog'),\n        backgroundColor: Colors.black87,\n      ),\n      body: Container(\n        color: Colors.black54,\n        child: Padding(\n          padding: const EdgeInsets.symmetric(\n            vertical: 8.0,\n            horizontal: 32.0,\n          ),\n          child: Column(\n            children: [\n              Padding(\n                padding: const EdgeInsets.only(bottom: 8.0),\n                // Text Field is the basic input widget for Flutter.\n                // It comes built in with a ton of great UI and\n                // functionality, such as the labelText field you see below.\n                child: TextField(\n                    decoration: InputDecoration(\n                  labelText: 'Name the Pup',\n                )),\n              ),\n              Padding(\n                padding: const EdgeInsets.only(bottom: 8.0),\n                child: TextField(\n                    decoration: InputDecoration(\n                  labelText: \"Pup's location\",\n                )),\n              ),\n              Padding(\n                padding: const EdgeInsets.only(bottom: 8.0),\n                child: TextField(\n                  decoration: InputDecoration(\n                    labelText: 'All about the pup',\n                  ),\n                ),\n              ),\n              // A Strange situation.\n              // A piece of the app that you'll add in the next\n              // section *needs* to know its context,\n              // and the easiest way to pass a context is to\n              // use a builder method. So I've wrapped\n              // this button in a Builder as a sort of 'hack'.\n              Padding(\n                padding: const EdgeInsets.all(16.0),\n                child: Builder(\n                  builder: (context) {\n                    // The basic Material Design action button.\n                    return RaisedButton(\n                      // If onPressed is null, the button is disabled\n                      // this is my goto temporary callback.\n                      onPressed: () => print('PRESSED'),\n                      color: Colors.indigoAccent,\n                      child: Text('Submit Pup'),\n                    );\n                  },\n                ),\n              ),\n            ],\n          ),\n        ),\n      ),\n    );\n  }\n}\n```\n\n### 2. Add the Routing\n\nLike the last section, you now have a page that you can't access. Add the button and routing information to the `_MyHomePageState` class.\n\n```dart\n// main.dart\n\n@override\nWidget build(BuildContext context) {\n  return Scaffold(\n    appBar: AppBar(\n      title: Text(widget.title),\n      backgroundColor: Colors.black87,\n      // This is how you add new buttons to the top right of a material appBar.\n      // You can add as many as you'd like.\n      actions: [\n         IconButton(\n          icon: Icon(Icons.add),\n          onPressed: _showNewDogForm,\n        ),\n      ],\n    ),\n  ...\n```\n\nThat will add a plus-sign looking button to the top right corner of your app, and finally you can add the method that builds a new route.\n\nImport `new_dog_form.dart` in `main.dart`:\n\n```dart\n// main.dart\n\nimport 'package:flutter/material.dart';\n\nimport 'dog_list.dart';\nimport 'dog_model.dart';\nimport 'new_dog_form.dart';\n```\n\nAdd this anywhere in your `_MyHomePageState` class:\n\n```dart\n// Any time you're pushing a new route and expect that route\n// to return something back to you,\n// you need to use an async function.\n// In this case, the function will create a form page\n// which the user can fill out and submit.\n// On submission, the information in that form page\n// will be passed back to this function.\nFuture _showNewDogForm() async {\n  // push a new route like you did in the last section\n  Dog newDog = await Navigator.of(context).push(\n    MaterialPageRoute(\n      builder: (BuildContext context) {\n        return AddDogFormPage();\n      },\n    ),\n  );\n  // A null check, to make sure that the user didn't abandon the form.\n  if (newDog != null) {\n    // Add a newDog to our mock dog array.\n    initialDoggos.add(newDog);\n  }\n}\n```\n","created_at":"2020-07-21T16:25:20.996Z","id":76,"slug":"routing-2-add-a-form-page","title":"Routing 2: Add a form page","tutorial":8,"updated_at":"2020-07-24T16:43:06.524Z"}}},"staticQueryHashes":["2185715291","3564968493","63159454"]}