« Previous 1 2 3 4 Next »
Program from one source to many apps with Flutter
Big Flap
Weather App
The next example app (Listing 2) displays the current weather with the help of two external packages. The current weather data is provided by the OpenWeatherMap online service. You could receive and analyze the weather data from this source in JSON data format, but a Flutter package named weather
[6] for OpenWeatherMap simplifies the steps. All you need is an API key, which you can get for free from the OpenWeatherMap homepage. This key authorizes you to retrieve the weather data.
Listing 2
Weather App
001 import 'package:flutter/material.dart'; 002 import 'package:weather/weather.dart'; 003 import 'package:shared_preferences/shared_preferences.dart'; 004 005 void main() { 006 runApp(MyApp()); 007 } 008 009 class MyApp extends StatelessWidget { 010 @override 011 Widget build(BuildContext context) { 012 return MaterialApp( 013 title: 'Weather App', 014 home: Scaffold(resizeToAvoidBottomInset: false, appBar: AppBar(title: Text('Weather App')), 015 body: Center( 016 child: Column( 017 children: [ 018 Image.asset('assets/images/weather_icon.png'), 019 Text('Current Weather', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 24),), 020 Flexible(child: MyHomePage()) 021 ],), 022 ),), 023 ); 024 } 025 } 026 027 class MyHomePage extends StatefulWidget { 028 @override 029 _MyHomePageState createState() => _MyHomePageState(); 030 } 031 032 class _MyHomePageState extends State<MyHomePage> { 033 final _city = TextEditingController(); 034 final _apiKey = TextEditingController(); 035 var _temperature = ''; 036 var _weather = ''; 037 var _pressure = ''; 038 var _windSpeed = ''; 039 var _humidity = ''; 040 var _lastUpdate = ''; 041 042 void _showApiKeyInputDialog() async { 043 final prefs = await SharedPreferences.getInstance(); 044 if(prefs.containsKey('apikey')){ 045 var _key = prefs.getString('apikey'); 046 showDialog( 047 context: context, 048 builder: (BuildContext context) { 049 return AlertDialog( 050 title: Text('Enter API key'), 051 content: TextField( 052 onChanged: (value) { 053 }, 054 controller: _apiKey..text = _key.toString(), 055 decoration: InputDecoration(hintText: 'API key'), 056 onSubmitted: (value) async { 057 await prefs.setString('apikey', _apiKey.text); 058 }, 059 ), 060 actions: [TextButton(child: Text('DELETE KEY'), onPressed: () async { 061 await prefs.remove('apikey'); _apiKey.clear(); Navigator.pop(context);}), TextButton( 062 child: Text("CANCEL"), 063 onPressed: () {Navigator.pop(context);}, 064 ), TextButton( 065 child: Text('OK'), 066 onPressed: () async { await prefs.setString('apikey', _apiKey.text); Navigator.pop(context);}, 067 )], 068 );} 069 ); 070 } 071 else { 072 showDialog( 073 context: context, 074 builder: (BuildContext context) { 075 return AlertDialog( 076 title: Text('Enter API key'), 077 content: TextField( 078 onChanged: (value) { 079 }, 080 controller: _apiKey, 081 decoration: InputDecoration(hintText: 'API key'), 082 onSubmitted: (value) async { 083 await prefs.setString('apikey', _apiKey.text); 084 }, 085 ), 086 actions: [TextButton(child: Text('DELETE KEY'), onPressed: () async { 087 await prefs.remove('apikey'); _apiKey.clear(); Navigator.pop(context);}), 088 TextButton( 089 child: Text("CANCEL"), 090 onPressed: () {Navigator.pop(context);}, 091 ), 092 TextButton( 093 child: Text('OK'), 094 onPressed: () async { await prefs.setString('apikey', _apiKey.text); Navigator.pop(context);}, 095 )], 096 );}); 097 } 098 } 099 100 void _displayWeather() async { 101 final prefs = await SharedPreferences.getInstance(); 102 var _key = prefs.getString('apikey').toString(); 103 WeatherFactory wf = new WeatherFactory(_key, language: Language.ENGLISH); 104 Weather w = await wf.currentWeatherByCityName(_city.text); 105 double? celsius = w.temperature!.celsius; 106 setState(() { 107 _temperature = celsius!.round().toString() + ' Degrees Celsius'; 108 _weather = w.weatherDescription!; 109 _pressure = w.pressure.toString() + ' hPa'; 110 _windSpeed = w.windSpeed.toString() + ' km/h'; 111 _humidity = w.humidity.toString() + ' %'; 112 _lastUpdate = w.date!.toIso8601String().replaceFirst('T', ' ').replaceRange(w.date!.toIso8601String().lastIndexOf('.')-1, w.date!.toIso8601String().lastIndexOf('0'), ''); 113 }); 114 } 115 116 @override 117 Widget build(BuildContext context) { 118 return Scaffold(resizeToAvoidBottomInset: false, 119 body: ListView(children: [Container(child: Column(children: [ 120 SizedBox(height: 10), 121 TextFormField(decoration: const InputDecoration(hintText: 'Please enter a city', labelText: 'City'), controller: _city,), 122 SizedBox(height: 10), 123 Row(mainAxisAlignment: MainAxisAlignment.start, children: [Text('Temperature: ', style: TextStyle(fontSize: 18),), 124 Text(_temperature.toString(), style: TextStyle(fontSize: 18)),]), 125 SizedBox(height: 10), 126 Row(mainAxisAlignment: MainAxisAlignment.start, children: [Text('Weather: ', style: TextStyle(fontSize: 18),), 127 Text(_weather, style: TextStyle(fontSize: 18)),]), 128 SizedBox(height: 10), 129 Row(mainAxisAlignment: MainAxisAlignment.start, children: [Text('Pressure: ', style: TextStyle(fontSize: 18),), 130 Text(_pressure.toString(), style: TextStyle(fontSize: 18)),]), 131 SizedBox(height: 10), 132 Row(mainAxisAlignment: MainAxisAlignment.start, children: [Text('Wind speed: ', style: TextStyle(fontSize: 18),), 133 Text(_windSpeed, style: TextStyle(fontSize: 18)),]), 134 SizedBox(height: 10), 135 Row(mainAxisAlignment: MainAxisAlignment.start, children: [Text('Humidity: ', style: TextStyle(fontSize: 18),), 136 Text(_humidity, style: TextStyle(fontSize: 18)),]), 137 SizedBox(height: 10), 138 Row(mainAxisAlignment: MainAxisAlignment.start, children: [Text('Last updated: ', style: TextStyle(fontSize: 18),), 139 Text(_lastUpdate, style: TextStyle(fontSize: 18)),]), 140 SizedBox(height: 10), 141 SizedBox(width: double.infinity, height: 35, child: ElevatedButton(onPressed: _displayWeather, child: const Text('Display Weather')),), 142 ],))],), 143 floatingActionButton: FloatingActionButton.extended( 144 onPressed: _showApiKeyInputDialog, icon: Icon(Icons.add), label: Text('API key'), tooltip: 'Enter API key',) 145 ); 146 } 147 }
The second package named shared_preferences
[7] stores this API key locally on the target platform, so you do not need reenter the key after each reboot. This very popular package is used a great deal in Flutter development because storing small amounts of data, like a string here, is straightforward. It avoids the use of a full-blown database, which would be technical overkill in this case.
The graphic with the sun at the very top of the sample app's interface (Figure 2) is in PNG format. To insert an image file like this, you need to declare it in pubspec.yaml
by specifying the path to the file. Usually, external resources like this are stored in the assets/
folder of the Flutter project. In the flutter
section, note the file path to the image file, as shown in Listing 3; then, add the graphic to the UI in the build
method of the StatelessWidget
, as shown in line 18 of Listing 2. The Center
widget wrapper centers the graphic.
Listing 3
Graphical Assets
flutter: assets: - assets/images/weather_icon.png
In line 14, a Scaffold
is passed to the home
area. It groups an app bar with the text 'Weather App'
along with the resizeToAvoidBottomInset
option set to false
. When the app's graphical keyboard pops up, this means it can overlay the widgets. The text widget in line 19 formats the 'Current Weather'
header. The first parameter specifies the desired string, and the second specifies the type and size of the font with the TextStyle
widget. In this case, the headline is displayed in 24-point (fontSize
) bold (fontWeight
).
In line 20, the Flexible
widget inserts the StatefulWidget
named MyHomePage
into the UI with its child
attribute. With the Flexible
widget, the child
widget dynamically resizes. MyHomePage
is a StatefulWidget
because it defines the area of the graphical interface that can change and is why it inherits the functionality of the StatefulWidget
class in line 27.
API Key
Line 42 creates an asynchronous _showApiKeyInputDialog
function that is used to query the API key. The underscore at the beginning of the function name means that the function is only visible in its own Dart file, as are variable and class names. The function is called as soon as you press the FloatingActionButton
bottom right in the app. Line 143 defines the matching button. The onPressed
parameter specifies the function to be called when the button is pressed. In this case, an AlertDialog
from the Material library is displayed, and you need to enter the API key from OpenWeatherMap that you created earlier.
As mentioned before, the key is stored locally with shared_preferences
; this arrangement works quite well. Line 43 creates an instance of shared_preferences
named prefs
inside the _showApiKeyInputDialog
function, which is an asynchronous action thanks to the await
keyword. The string itself is stored in the cache as a key-value pair, much like an associative array. In this example, the apikey
string represents the key.
The prefs.containsKey('apikey')
method in line 44 checks to see whether a key already exists on the target device. If so, the method reads its value and assigns it to the _key
variable. Finally, line 46 calls the dialog with showDialog()
. To do this, line 49 returns an AlertDialog
, as mentioned earlier, whose TextField
then displays the API key.
If no API key resides in the local cache, the else
branch displays an identical dialog starting in line 74, but with an empty text box. In both cases, the AlertDialog
has three options: DELETE KEY
deletes the API key from the cache, CANCEL
closes the dialog, and OK
saves the entry currently in the text box as the API key. To create these options, three TextButtons
below the actions
parameters in lines 60 and 86 are defined. When done, Navigator.pop(context)
closes the dialog again in each case.
The counterpart to prefs.getString('apikey')
for reading the API key is the prefs.setString('apikey', _apiKey.text)
method, which stores a string locally. Lines 66 and 94 make use of this to store the API key with shared_preferences
. The first parameter specifies the key, and the second names the string to be stored under that key. In this case, the second parameter consists of a TextEditingController
that is connected to the text field from the _showApiKeyInputDialog
function and reads the API key there. If you want to use DELETE KEY
to clear the API key from the cache, this is done with the prefs.remove('apikey')
method in lines 61 and 87. The subsequent _apiKey.clear()
sets the value of the TextEditingController
to empty.
The Weather
Line 100 formulates another asynchronous function named _displayWeather()
that displays the weather and is executed when the Display Weather
button is pressed. Line 141 adds this ElevatedButton
, the name of the button widget in Flutter, to the graphical interface. The second option, child
, lets you label the button with a text widget. If the weather data has changed, line 106 uses setState()
to assign the new values to the variables and update the text widgets in the interface.
Displaying the current weather requires another instance of SharedPreferences
(line 101). The following line again uses the prefs.getString('apikey')
method to read the current value in the cache and assign it to the _key
variable. In line 103, the WeatherFactory
class comes into play for the first time; it is used to retrieve weather data from the OpenWeatherMap online service. WeatherFactory
belongs to the weather
package imported in line 2. The new
keyword instantiates an object of this class named wf
. As the first parameter, the constructor expects the API key in the variable _key
. The second optional parameter language
can be used to define the language for the weather display.
The wf
object comes with a method named currentWeatherByCityName
, which expects as a parameter the city for which you want the weather displayed by the app. In this case, a TextEditingController
again acts as a parameter to read the city specified in the text box in line 121. The TextEditingController
variable was initialized in line 33 along with the controller
property in line 121. The text
property of this controller can be used to read the string currently in the associated text box.
Once the city has been passed as a parameter to the currentWeatherByCityName
method, you can use the associated attribute temperature
to determine the temperature at this location. As you can see in line 107, the temperature is displayed in degrees Celsius (alternatively, you could display it in Fahrenheit or Kelvin). The w
object from line 104 has other attributes, too, such as weatherDescription
, which describes the weather, and pressure
, which gives the air pressure. Similarly, windSpeed
provides the wind speed, humidity
the humidity, and date
the date and time of the weather measurement. The corresponding variables for each weather property are initialized in lines 35-40.
« Previous 1 2 3 4 Next »
Buy this article as PDF
(incl. VAT)