# 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':

- hot reload :

- 會上頁再press 'Edit Location':

每次進入此頁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 ?按鈕

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