---
# System prepended metadata

title: 'flutter : world time app'

---

# 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