Adding Comic Selection by Number

It’s very easy to fetch comics by number, so it seems obvious that we should be able to let the user select comics using the comic number, especially because very often users want to go back to a comic they’ve seen on the Web.

To do that, we’ll start by creating a page to select the comics, which will look like this:

images/Networking/select.png

Since it doesn’t need any parameters to work, we don’t need to specify the constructor, but we need to have a fetchComic method that fetches by comic number, unlike the one we used for the HomePage:

 class​ SelectionPage ​extends​ StatelessWidget {
  Future<Map<​String​, ​dynamic​>> _fetchComic(​String​ n) async =>
  json.decode(
  await http.read(​'https://xkcd.com/​​$n​​/info.0.json'​)
  );
 }

The build method will be more complex, using some Flutter features we’ve never seen before in this book:

 @override
 Widget ​build​(BuildContext context) {
 return​ Scaffold(
  appBar: AppBar(
  title: Text(​"Comic selection"​),
  ),
  body: Center(
child: TextField(
  decoration: InputDecoration(
  labelText: ​"Insert comic #"​,
  ),
keyboardType: TextInputType.number,
autofocus: ​true​,
  onSubmitted: (​String​ a) => Navigator.push(
  context,
  MaterialPageRoute(
builder: (context) => FutureBuilder(
  future: _fetchComic(a),
  builder: (context, snapshot) {
 if​(snapshot.hasData) ​return​ ComicPage(snapshot.data);
 return​ CircularProgressIndicator();
  }
  ),
  ),
  ),
  ),
  ),
  );
 }

This is a TextField, which is a widget that allows the user to input text.

keyboardType is used to set the type of information the user is supposed to input (the framework will optimize the display and/or the type of keyboad shown to the user to suit this setting). It is of type TextInputType, which can be one of the following:

  • TextInputType.text (default) for regular single-line text.
  • TextInputType.multiline for multiple lines of text.
  • TextInputType.number, which is what we’re going to use, for numbers.
  • TextInputType.datetime for date and time.
  • TextInputType.emailAddress for email addresses.
  • TextInputType.phone for telephone numbers.

This argument makes the view automatically focus on the TextField. This means the keypad will automatically open and any numbers typed on it will be sent to the TextField, without requiring the user to tap on it first.

We haven’t yet fetched the comic, which means we need to call the FutureBuilder to fetch the comic and use the comic as an argument for the ComicPage.

Now we need to be able to reach that page from the home page. We’ll do this by using AppBar actions, which are widgets we can show on the right side of the app bar. In this case, we’re going to use an IconButton:

  appBar: AppBar(
  title: Text(title),
actions: <Widget>[
  IconButton(
  icon: Icon(Icons.looks_one),
tooltip: ​"Select Comics by Number"​,
onPressed: () =>
  Navigator.push(
  context,
  MaterialPageRoute(
  builder: (BuildContext context) => SelectionPage()
  ),
  ),
  ),
  ],
 ),

This is where we are going to add a List of widgets to show on the AppBar. In this case it’s going to be just one IconButton.

The IconButton also needs a tooltip to help the user understand what the button does. Further on in the book there is a screenshot of what it looks like.

This is just like the ComicTile’s onpressed.

The updated home page, showing the overall new look of the app, looks like:

images/Networking/home2.png

and when the user keeps the button pressed the tooltip will look like:

images/Networking/home2tooltip.png

What If That Comic Doesn’t Exist?

The user could input any number that goes through their head in the SelectionPage. While I don’t actively endorse abusing innocent app features this way, the user could choose to input a number that is bigger than the latest comic’s number, and we can’t just let the app crash or let the CircularProgressIndicator loop forever because the user isn’t supposed to do that.

We need to handle that situation, and for that we’ll use the AsyncSnapshot’s hasError property: if it is true, it means an error occurred during the processing of the future. If it is true, we’ll show the user an ErrorPage, which we’ll build later.

Meanwhile, here’s the new builder for our FutureBuilder:

 builder: (context, snapshot) {
 if​(snapshot.hasError) ​return​ ErrorPage();
 if​(snapshot.hasData) ​return​ ComicPage(snapshot.data);
 return​ CircularProgressIndicator();
 }

I said we were going to build a ComicPage at some point. The moment has come to do that, so let’s do it! The ComicPage needs to be just a simple StatelessWidget and the usual Scaffold and AppBar. The body will be a Column containing an icon and a Text informing the user they have done something wrong:

 class​ ErrorPage ​extends​ StatelessWidget {
  @override
  Widget build(BuildContext context) {
 return​ Scaffold(
  appBar: AppBar(
  title: Text(​"Error Page"​),
  ),
  body: Column(
  children: [
  Icon(Icons.not_interested),
  Text(
 "The comics you have selected doesn't exist"​+
 "or isn't available"
  ),
  ]
  )
  );
  }
 }
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset