# flutter : world time app ## Starting the World Time App 1. add screen in /lib,右鍵去new package (name=pages) in pages/ new dart file(home、choose_location、loading) 2. home page ```dart import 'package:flutter/material.dart'; class Home extends StatefulWidget { @override _State createState() => _State(); } class _State extends State<Home> { @override Widget build(BuildContext context) { return Scaffold( body: SafeArea(child : Text('home screen')), //set it in area we can see ); } } ``` - StatefulWidget : 有交互,需要动态变化 Flutter-你还在滥用StatefulWidget吗 : [https://lizhaoxuan.github.io/2019/01/02/Flutter-你还在滥用StatefulWidget吗/](https://lizhaoxuan.github.io/2019/01/02/Flutter-%E4%BD%A0%E8%BF%98%E5%9C%A8%E6%BB%A5%E7%94%A8StatefulWidget%E5%90%97/) - Scaffold and Container Scaffold : 頁面 Container : A convenience widget that combines common painting, positioning, and sizing widgets. - SafeArea() 讓裡面的東西不要被手機上面一條(時間通知)擋到 3. in main.dart `import 'package:world_time/pages/home.dart';` ## Maps & Routing ### map - maps in dart == object literals in javascript == dictionaries in python - key and value ```dart void main(){ Map student = {'name':'chun-li','age:25}; print(student['name']);//show chun-li } ``` 1. in main.dart ```dart import 'package:flutter/material.dart'; import 'package:world_time/pages/home.dart'; import 'package:world_time/pages/loading.dart'; import 'package:world_time/pages/choose_location.dart'; void main() => runApp(MaterialApp( initialRoute: '/home', routes: { '/':(context) => Loading(),//base page '/home' : (context) => Home(), '/location': (context) => ChooseLocation(), }, )); ``` - key : actual routes value : function - context : 當前widget所創建的Element對象 追蹤我們在widget tree的位置 - initialRoute : 初始路徑 2. in home.dart ```dart import 'package:flutter/material.dart'; class Home extends StatefulWidget { @override _State createState() => _State(); } class _State extends State<Home> { @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child : Column( children: <Widget>[ TextButton.icon( onPressed: (){ Navigator.pushNamed(context,'/location'); }, icon: Icon(Icons.edit_location), label: Text('Edit Location'), ), ], ), ), //set it in area we can see ); } } ``` - Navigator.pushNamed(context,'/location') push一個新的page(新路徑) - FlatButton.icon(棄用) →TextButton.icon 3. choose_location.dart ```dart import 'package:flutter/material.dart'; class ChooseLocation extends StatefulWidget { const ChooseLocation({Key? key}) : super(key: key); @override _ChooseLocationState createState() => _ChooseLocationState(); } class _ChooseLocationState extends State<ChooseLocation> { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey[200], appBar: AppBar( backgroundColor: Colors.blue[200], title: Text('choose as locaition'), centerTitle: true,//put title in middle elevation: 0,//app bar服貼在背景上 ), body: Text('choose location screen'), ); } } ``` ### routing 堆疊的方式累積頁面 ## Widget Lifecycle ### stateflul widgets - initState() - 只會呼叫一次(widget創建時) - subscribe to streams or any object that could change our widget data - build() - build widget tree - 每次call setState()就會觸發build() - dispose() - 當widget/state object被移除時觸發 in choose_location.dart ```dart class _ChooseLocationState extends State<ChooseLocation> { @override void initState() { super.initState(); print('initState function run'); } @override Widget build(BuildContext context) { print('build function run'); ... } } ``` - run & press 'Edit Location': ![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/80286361-b939-415b-8b6a-f59fa644cb7a/Untitled.png) - hot reload : ![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/795cc0ec-a7ff-42d4-89c5-7afd4770c828/Untitled.png) - 會上頁再press 'Edit Location': ![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/1bbe3247-9935-4369-b8e7-492a4f638298/Untitled.png) 每次進入此頁initState() 和build都會run 當我們離開 : 1. 去掉widget 2. 去掉state object 去那頁 : 都再創造出一次 ```dart class _ChooseLocationState extends State<ChooseLocation> { int counter=0; @override void initState() { super.initState(); print('initState function run'); } @override Widget build(BuildContext context) { print('build function run'); return Scaffold( ... body: ElevatedButton( onPressed: () { setState(() { counter += 1; }); }, child: Text('counter is $counter'), ), ); } } ``` 每次按counter is ?按鈕 ![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/ce0aa60e-4c79-4e14-8dfd-40052525980e/Untitled.png) Raisebuttom棄用了,改用ElevatedButton : [https://api.flutter.dev/flutter/material/ElevatedButton-class.html](https://api.flutter.dev/flutter/material/ElevatedButton-class.html) ## Asynchronous Code Asynchronous Code不會阻擋後面的code ```dart void getData() async { //simulate network request for a username String username = await Future.delayed(Duration(seconds: 3),(){ return 'yoshi'; }); //simulate network request to get bio of username String bio = await Future.delayed(Duration(seconds: 2),(){ return 'negan...'; }); print('$username-$bio'); } ``` - Future object : [https://api.dart.dev/stable/2.13.4/dart-async/Future-class.html](https://api.dart.dev/stable/2.13.4/dart-async/Future-class.html) - Future.delay(argument_1,argument_2) - argument_1 : Duration() - 這是一個內建(built-in)類別(不用import) - argument_2 : a function 時間到會啟動 - async function - 裡面是Asynchronous Code - await 這行做完再做下一行 ## Flutter Packages (http) - 利用此package將http request對接到不同api或終端 - Third party API - reload v.s. restart - 使用http package : [https://pub.dev/packages/http/install](https://pub.dev/packages/http/install) - pubspec.yaml內加入 ``` dependencies: http: ^0.13.3 ``` in loading.dart ```dart import 'package:flutter/material.dart'; import 'package:http/http.dart'; import 'dart:convert'; class Loading extends StatefulWidget { const Loading({Key? key}) : super(key: key); @override _LoadingState createState() => _LoadingState(); } class _LoadingState extends State<Loading> { void getData() async { Response response = await get(Uri.parse('https://jsonplaceholder.typicode.com/todos/1')); Map data = jsonDecode(response.body); print("test"); print(data); print(data['title']); } @override void initState() { super.initState(); getData(); } @override Widget build(BuildContext context) { return Scaffold( body: Text('loading screan'), ); } } ``` - import 'package:http/http.dart';就可以使用get ! error The argument type 'String' can't be assigned to the parameter type 'Uri' - [https://stackoverflow.com/questions/66473263/the-argument-type-string-cant-be-assigned-to-the-parameter-type-uri](https://stackoverflow.com/questions/66473263/the-argument-type-string-cant-be-assigned-to-the-parameter-type-uri) [Untitled](https://www.notion.so/9977c7fdf06c4a5e869e57753a9a727e) - response.body是json格式的string還沒辦法取出key - Response response = await get(Uri.parse('https://jsonplaceholder.typicode.com/todos/1')); - 等取完資料再run下一行 - jsonplaceholder : [https://jsonplaceholder.typicode.com/](https://jsonplaceholder.typicode.com/) - [https://jsonplaceholder.typicode.com/todos/1](https://jsonplaceholder.typicode.com/todos/1) - 裡面有假的(測試用)json - import 'dart:convert'; 使用jsonDecode() - 存成Map就可以用了 ## World Time API - world time api - 各地 : [http://worldtimeapi.org/timezones](http://worldtimeapi.org/timezones) - json 格式: [https://worldtimeapi.org/api/timezone/Europe/London](https://worldtimeapi.org/api/timezone/Europe/London) ```dart void getTime() async { //make the request Response response = await get(Uri.parse('https://worldtimeapi.org/api/timezone/Europe/London')); Map data = jsonDecode(response.body); //print(data); //get properties from data String datetime = data['datetime']; String offset = data['utc_offset'].substring(1,3); //print(datetime); //print(offset); //create datetime object DateTime now = DateTime.parse(datetime); now = now.add(Duration(hours: int.parse(offset))); print(now); } ``` - DateTime object : - DateTime now = DateTime.parse(datetime); - 轉換成DateTime object才好將datetime和offset相加 - [https://api.dart.dev/stable/2.13.4/dart-core/DateTime-class.html](https://api.dart.dev/stable/2.13.4/dart-core/DateTime-class.html) - substring(1,3); - offset : +01:00 → 01 - int.parse : string to int ## WorldTime Custom Class - Error services/world_time.dart ```dart import 'package:http/http.dart'; import 'dart:convert'; class WorldTime{ String location;//location name for the UI String time='';//the time in that location String flag;//url to an asset flag icon String url;//location url for api endpoint WorldTime({required this.location,required this.flag,required this.url}); Future<void> getTime() async { //make the request Response response = await get(Uri.parse('https://worldtimeapi.org/api/timezone/$url')); Map data = jsonDecode(response.body); //print(data); //get properties from data String datetime = data['datetime']; String offset = data['utc_offset'].substring(1,3); //print(datetime); //print(offset); //create datetime object DateTime now = DateTime.parse(datetime); now = now.add(Duration(hours: int.parse(offset))); //print(now); //set the time property time = now.toString(); } } ``` - Response response = await get(Uri.parse('https://worldtimeapi.org/api/timezone/$url')); - 只有尾巴不同國家不同 - time = now.toString(); - 轉string存入time - WorldTime({required this.location,required this.flag,required this.url}); - required : 因為後面的變數不能是null loading.dart ```dart import 'package:flutter/material.dart'; import 'package:world_time/services/world_time.dart'; class Loading extends StatefulWidget { const Loading({Key? key}) : super(key: key); @override _LoadingState createState() => _LoadingState(); } class _LoadingState extends State<Loading>{ String time = 'loading'; void setupWorldTime() async{ WorldTime instance = WorldTime(location: 'Berlin',flag: 'germany.png',url: 'Europe/Berlin'); await instance.getTime(); print(instance.time); setState(() { time = instance.time; }); } @override void initState() { super.initState(); setupWorldTime(); } @override Widget build(BuildContext context) { return Scaffold( body: Padding( padding: EdgeInsets.all(50.0), child: Text(time), ), ); } } ``` - WorldTime instance = WorldTime(location: 'Berlin',flag: 'germany.png',url: 'Europe/Berlin'); - pass data進去 - await instance.getTime(); - getTime是非同步,要出去拿data,會直接run下一行 → 需要await - `Future<void> getTime() async` - future是一個暫時的佔位符(placeholder)直到function完成 - 完成時會回傳void - await只能用在asyns function裡 - padding 留白 - EdgeInsets.all四周都 ## Error Handling