Welcome, Guest User :: Click here to login

Logo 67443

Lab 2: Temp Converter

Due Date: September 10

Objectives

  • reinforce learning from previous lab
  • use models to handle business logic
  • use navigation to allow for additional pages

README.md

Lab 2: Temp Converter App

Today we are going to be building a temperature converter that will allow us to move seamlessly between degrees Fahrenheit and degrees Celsius. It will also have a second page that will give us information about how the app works and its creator (namely, you ;-) Screenshots of these pages can be seen below:

TempConverter Main TempConverter About

  1. Our first step is to use VS Code to create a new Flutter project called 'TempConverter'. Please feel free to refer to last week's lab if you don't remember the details of this step.

  2. Our next step is to create a model. Typically, we want to separate the business logic from the user interface; the way this is typically done is to create a model class that has the business logic and then later create an object of this type for the interface to use. For us now, create in the lib directory a new file called converter.dart. Open that file and add the following code:

    class Converter {
      double temp = 32;
      String mode = "°F";
    
      void setTemp(double t) {
        temp = t;
      }
    
      void toggle() {
        if (mode == "°F") {
          mode = "°C";
        } else {
          mode = "°F";
        }
      }
    
      double convert() {
    
         // if temp less than -500 (absolute zero), set to -500
    
         // if mode is °F, then convert by subtracting 32
         // and then multiplying by 5 / 9
     
         // otherwise multiply by 9 / 5 and 
         // then add 32 to the result
    
    
      String convertString() {
        return convert().toStringAsFixed(1) + (mode == "°F" ? "°C" : "°F");
      }
    }
    

    A few notes on the model to consider. This model has the a simple constructor which sets default values for mode (which can only be either "°F" or "°C") and temperature (initially set to 32). It also has a basic setter for changing the temp value, a method for calculating the conversion, and a method which takes that conversion and turns it into a string with the appropriate mode as a suffix. Note that the actual logic for the convert() method is missing, but some comments are there to guide you as you write it.

  3. Now with the model in place, go to main.dart and import the model right after we import the main flutter package.

  4. Going to the first function, main(), as well as the class it creates an instance of MyApp, what changes, if any, do we have to make? Again, refer to last's week lab to determine what needs to be adjusted here.

  5. Moving onto MyHomePage, which is a StatefulWidget, we see we do have some legacy code that the demo uses to increment a counter. Clean this up so all we have is a title and the override of the createState() method. Again, last week's lab may be helpful in this regard.

  6. Now we come to the _MyHomePageState class, where a lot of our heavy lifting occurs. Since we've already imported our model, we can start this class by creating an instance of the model to work with: Converter _converter = Converter(); We also need a temp string to display the temperature results; we can do this with String _textTemp = "Enter a temp";. Finally, we need to track whether we've switched modes; we can do that with bool _isSwitched = true;.

  7. Last week we only had one thing on the interface that needed to be redrawn based on user input, namely the stopwatch display. This week, we need to update the temperature display based on what the user enters in the input box. We will create an _updateTemp() method, that uses the setState() method to redraw the interface while using the model that has that logic for creating the temp string. Both these things can be done as follows:

      void _updateTemp() {
        setState(() {
          _textTemp = _converter.convertString();
        });
      }
    
  8. However, this week we have a second event -- someone toggling the mode switch -- that would also necessitate an interface redraw. Like the previous method, this uses setState() to call for the redraw, but it also uses the model again to perform some basic logic of toggling the measurement system and creating a temp string. We can create that method with the following:

      void _updateSwitch(bool value) {
        setState(() {
          _isSwitched = value;
          _converter.toggle();
          _textTemp = _converter.convertString();
        });
      }
    

    We will find an opportunity to call this method a little later, but now let's build out the interface a bit.

  9. If you recall, we build out the interface by override the widget's build(). Like last week, we will start with a Scaffold() and within the scaffold, we will add the appBar that displays the title. Do that now and run the app in the simulator to see that it works.

  10. Within the scaffold, add in the body which consists of a Center widget and a Column widget as a child, like we did last week. Within Column center align things and then add in an array of children: <Widget>[]. The first thing to add to this array of children widgets is a Text widget as follows and see it in the simulator:

      Text(
        _textTemp,
        style: TextStyle(fontSize: 30),
      ),
    
  11. Now we need to create the input box for users to add temps. We can do with with an additional child widget (still in the array, following the Text) as follows:

            Container(
              child: TextField(
                style: TextStyle(fontSize: 30),
                keyboardType: TextInputType.number,
                textAlign: TextAlign.right,
                decoration: InputDecoration(
                  border: OutlineInputBorder(),
                  labelText: 'Input Temp',
                ),
                onChanged: (text) {
                 
                   // try to set the model temp 
                 
                   // update the interface
    
                },
              ),
              padding: EdgeInsets.all(50),
              width: 400,
            ),
    
  12. Now if you ran this, you'd get the box but nothing worked because we haven't set onChanged yet. First thing to do is to use the input and set the model's temp variable using our setTemp method. Problem is that we have to deal with people doing stupid things, like adding the temp value of freezing! which is not a real temp. We can kill two birds with one stone using the following line of code: _converter.setTemp((double.tryParse(text) ?? -500.0));

  13. The second thing this method has to do is actually update the interface. We have a method ready to help us to just that, so go ahead and call it here.

  14. After we have that working, we need to add in as an additional child a widget to toggle between modes. In this case, we need a Row widget that itself have three children: one text label, one switch, and another text label. The following code will help:

            Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
              Text(
                '°C',
                style: TextStyle(fontSize: 25),
              ),
              Switch(
                value: _isSwitched,
                onChanged: (value) {
                  
                   // time to update the interface again
    
                },
              ),
              Text(
                '°F',
                style: TextStyle(fontSize: 25),
              ),
            ]),
    

    Again, we already have a method that will handle onChanged for us; call it now in place of the comment.

  15. After that, we have one more child element to add: an about button offset a bit lower that will take us to a separate page. Before we can add that button, we need to create the page to land on. Let's start by adding a new file to lib called about.dart and add the following:

    import 'package:flutter/material.dart';
    
    class AboutPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
    
        // add scaffold
        // add appBar
        // add body, with Center and Column widgets
    
      }
    }
    
    

    You know how to do the things in the comments, so do that now.

  16. Inside the Column widget there are several children. Below is some code to use and adapt as you see fit. (Definitely change the name to your name if you want lab credit. ;-)

      Padding(
        padding: const EdgeInsets.only(left: 37.0, right: 37.0),
        child: Text(
          "This Temp Converter app lets you move between temperatures in degrees Fahrenheit and degrees Celsius. Simply enter a temperature in the box and the corresponding temperature will automatically appear on the screen. You can toggle between the two sets of units with the switch located below the input box.\n",
         style: TextStyle(
           fontWeight: FontWeight.normal,
           fontSize: 16.0,
          ),
        ),
      ),
      Padding(
        padding: const EdgeInsets.all(37.0),
        child: Text(
          "This amazing app was created for you by <YOUR NAME HERE>.\n",
          style: TextStyle(
            fontWeight: FontWeight.bold,
            fontSize: 12.0,
           ),
         ),
       ),
    
    
  17. This is great, but we need a way to dismiss this page. Basically, in Flutter there is a Navigator with a stack that we will push or pop pages to in order to get back and forth. We will add some code in a moment to add this AboutPage to the Navigator stack, for now we just want to pop it by adding the following child element:

      Padding(
        padding: const EdgeInsets.all(12.0),
        child: ElevatedButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: Text('Go back'),
        ),
      ),
    
    
  18. Having this page in place, we can go back to main.dart and first thing (right after importing our model) let's import the about page with import 'about.dart';

  19. Now we can in our final child widget after our toggle switch row. The following code should suffice to push the about page onto the Navigator stack:

      Padding(
        padding: const EdgeInsets.only(top: 100.0),
        child: OutlinedButton.icon(
          label: Text('About Temp Converter'),
          icon: Icon(Icons.info),
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => AboutPage()),
            );
          },
        )
      )
    
  20. Now run this in the simulator and see that you can move back and forth between pages, change temperatures, change degree modes and that everything works. Also feel free to experiment with padding, color, and the like to tweak the interface. (And make sure that 'debug' ribbon in the upper right is gone -- should have happened way earlier, but just in case...)

Qapla'