본문 바로가기

안드로이드/Flutter

Flutter Bloc패턴, http 통신 http.get 예제

오늘의 주제는 플러터의 프론트앤드, 백앤드 개발을 분리하는 방법인

Bloc패턴에 대해 알려주겠다.

 

오늘 게시글의 특이사항은, Data Flow를 본인 손으로 종이에 직접 작성하여

주석을 써서 코드의 이해를 도왔다는 점이다.

 

아래는 그 코드이다.

 

이 코드는 인터넷 강의인

https://survivalcoding.com/courses/

 

오준석의 생존코딩

How to learn mobile development 교육하는 개발자 오준석입니다. 모바일 개발을 더 빨리 배우세요

survivalcoding.com

에 가면 배울 수 있다.

차분한 목소리와 초보도 따라할 수 있는 속도가 인상적인 강의이다.

나도 많이 배우고있다.

 

참고로 출처도 밝힐 의도임을 알린다.

 

아래는 main.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/*
이 프로젝트는 플러터 강의인
https://survivalcoding.com/courses/enrolled/546893 를 실습하기 위한 프로젝트 입니다.
이 강의는
1. 미세먼지 앱
- Http 통신
- Test 코드 작성
- Json to Dart
- Stream, StreamBuilder
2. 카운터 앱
- RxDart
- BehaviorSubject
3. 장바구니앱
- flutter_bloc 라이브러리 소개
- 간단 Bloc 적용
- InheritedWidgetd을 활용한 BlocProvider 만들어 적용하기
 
목적: Bloc패턴을 자유롭게 사용할 수 있다.
 
FireBase -
*/
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:networkStudy/bloc/air_bloc.dart';
import 'package:networkStudy/models/air_result.dart';
 
void main() => runApp(MyApp());
 
final airBloc = AirBloc();
 
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Main(),
    );
  }
}
 
class Main extends StatefulWidget {
  @override
  _MainState createState() => _MainState();
}
 
class _MainState extends State<Main> {
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: StreamBuilder<AirResult>// <> 안에 streambuilder의 타입을 지정해줘야한다.
            stream: airBloc.airResult,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return buildPadding(snapshot.data);
              } else {
                return CircularProgressIndicator();
              }
            }
        ),
      ),
    );
  }
 
  Widget buildPadding(AirResult result) {
    return Padding(
      padding: const EdgeInsets.all(12.0),
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '현재 위치 (${result.data.city}) 미세먼지',
              style: TextStyle(
                fontSize: 30,
              ),
            ),
            SizedBox(
              height: 16,
            ),
            Card(
              child: Column(
                children: <Widget>[
                  Container(
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceAround,
                      children: <Widget>[
                        Text('얼굴사진'),
                        Text(
                          '${result.data.current.pollution.aqius}',
                          style: TextStyle(fontSize: 40),
                        ),
                        Text(
                          getString(result),
                          style: TextStyle(fontSize: 20),
                        ),
                      ],
                    ),
                    color: getColor(result),
                    padding: const EdgeInsets.all(8.0),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(10.0),
                    child: Container(
                      child: Row(
                        mainAxisAlignment:
                        MainAxisAlignment.spaceAround,
                        children: <Widget>[
                          Row(
                            children: <Widget>[
                              Image.network( // 날씨에 따라 바뀌는 아이콘
                                'https://airvisual.com/images/${result.data
                                    .current.weather.ic}.png',
                                width: 32,
                                height: 32,
                              ),
                              SizedBox(
                                width: 16,
                              ),
                              Text(
                                '${result.data.current.weather.tp}',
                                style: TextStyle(fontSize: 16),
                              ),
                            ],
                          ),
                          Text(
                              '습도 ${result.data.current.weather.hu}%'),
                          Text('${result.data.current.weather.ws}'),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            ),
            SizedBox(
              height: 16,
            ),
            ClipRRect(
              // 위젯을 둥글게 해주는 효과가 있는 위젯
              borderRadius: BorderRadius.circular(30),
              child: RaisedButton(
                padding: const EdgeInsets.symmetric(
                    vertical: 15, horizontal: 50),
                // 위아래로 15, 수평으로 50의 여백을 줌 = 커짐
                color: Colors.redAccent,
                child: Icon(
                  Icons.refresh,
                  color: Colors.white,
                ),
                onPressed: () {
                  airBloc.fetch();
                },
              ),
            )
          ],
        ),
      ),
    );
  }
 
  Color getColor(AirResult result) {
    if (result.data.current.pollution.aqius <= 50) {
      return Colors.greenAccent;
    } else if (result.data.current.pollution.aqius <= 100) {
      return Colors.orangeAccent;
    } else {
      return Colors.red;
    }
  }
 
  String getString(AirResult result) {
    if (result.data.current.pollution.aqius <= 50) {
      return '좋음';
    } else if (result.data.current.pollution.aqius <= 100) {
      return '보통';
    } else if (result.data.current.pollution.aqius <= 100) {
      return '나쁨';
    } else {
      return '최악';
    }
  }
}
 
cs

 

아래는 air_bloc.dart

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:networkStudy/models/air_result.dart';
import 'package:rxdart/rxdart.dart';
 
class AirBloc {
  final _airSubject = BehaviorSubject<AirResult>();
 
  AirBloc() {
    fetch();
  }
 
  Future<AirResult> fetchData() async {
    var response = await http.get(
        'https://api.airvisual.com/v2/nearest_city?key=9c66e166-ad4b-41c0-9d27-85bee4c73854');
    AirResult result = AirResult.fromJson(json.decode(response.body));
    print('fetch');
    return result;
  }
 
  void fetch() async {
    var airResult = await fetchData();
    _airSubject.add(airResult);
  }
 
  Stream<AirResult> get airResult => _airSubject.stream;
}
 
cs

 

아래는 air_result.dart 코드이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
class AirResult {
  String status;
  Data data;
 
  AirResult({this.status, this.data});
 
  AirResult.fromJson(Map<String, dynamic> json) {
    status = json['status'];
    data = json['data'!= null ? new Data.fromJson(json['data']) : null;
  }
 
  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['status'= this.status;
    if (this.data != null) {
      data['data'= this.data.toJson();
    }
    return data;
  }
}
 
class Data {
  String city;
  String state;
  String country;
  Location location;
  Current current;
 
  Data({this.city, this.state, this.country, this.location, this.current});
 
  Data.fromJson(Map<String, dynamic> json) {
    city = json['city'];
    state = json['state'];
    country = json['country'];
    location = json['location'!= null
        ? new Location.fromJson(json['location'])
        : null;
    current =
    json['current'!= null ? new Current.fromJson(json['current']) : null;
  }
 
  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['city'= this.city;
    data['state'= this.state;
    data['country'= this.country;
    if (this.location != null) {
      data['location'= this.location.toJson();
    }
    if (this.current != null) {
      data['current'= this.current.toJson();
    }
    return data;
  }
}
 
class Location {
  String type;
  List<double> coordinates;
 
  Location({this.type, this.coordinates});
 
  Location.fromJson(Map<String, dynamic> json) {
    type = json['type'];
    coordinates = json['coordinates'].cast<double>();
  }
 
  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['type'= this.type;
    data['coordinates'= this.coordinates;
    return data;
  }
}
 
class Current {
  Weather weather;
  Pollution pollution;
 
  Current({this.weather, this.pollution});
 
  Current.fromJson(Map<String, dynamic> json) {
    weather =
    json['weather'!= null ? new Weather.fromJson(json['weather']) : null;
    pollution = json['pollution'!= null
        ? new Pollution.fromJson(json['pollution'])
        : null;
  }
 
  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    if (this.weather != null) {
      data['weather'= this.weather.toJson();
    }
    if (this.pollution != null) {
      data['pollution'= this.pollution.toJson();
    }
    return data;
  }
}
 
class Weather {
  String ts;
  int tp;
  int pr;
  int hu;
  var ws;
  int wd;
  String ic;
 
  Weather({this.ts, this.tp, this.pr, this.hu, this.ws, this.wd, this.ic});
 
  Weather.fromJson(Map<String, dynamic> json) {
    ts = json['ts'];
    tp = json['tp'];
    pr = json['pr'];
    hu = json['hu'];
    ws = json['ws'];
    wd = json['wd'];
    ic = json['ic'];
  }
 
  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['ts'= this.ts;
    data['tp'= this.tp;
    data['pr'= this.pr;
    data['hu'= this.hu;
    data['ws'= this.ws;
    data['wd'= this.wd;
    data['ic'= this.ic;
    return data;
  }
}
 
class Pollution {
  String ts;
  int aqius;
  String mainus;
  int aqicn;
  String maincn;
 
  Pollution({this.ts, this.aqius, this.mainus, this.aqicn, this.maincn});
 
  Pollution.fromJson(Map<String, dynamic> json) {
    ts = json['ts'];
    aqius = json['aqius'];
    mainus = json['mainus'];
    aqicn = json['aqicn'];
    maincn = json['maincn'];
  }
 
  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['ts'= this.ts;
    data['aqius'= this.aqius;
    data['mainus'= this.mainus;
    data['aqicn'= this.aqicn;
    data['maincn'= this.maincn;
    return data;
  }
}
cs

 

그리고 이것은 본인이 작성한 데이터 플로우이다

 

참고하도록 하자

 

모든 설명은 이 종이에 있으므로 포스팅을 마친다.

도움이 되기를 바란다.