최근에는 Dio를 사용하고 있습니다. 왜냐하면 디코드가 필요 없거든요. DIO returns decoded MAP. not required to decode
Axios나 fetch를 이용해서 브라우저 상에서 api와 통신을 하듯, flutter에서는 http 패키지를 통해 api와 통신한다.
데이터 페칭을 위한 flutter의 cookbook을 먼저 읽어봅시다.
(https://flutter.dev/docs/cookbook/networking/fetch-data)
(https://pub.dev/documentation/http/latest/)
패키지는 (https://pub.dev/packages/http) 에서 받을 수 있다.
간단하게 다음과 같이 작성해보았습니다.
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class LoadingScreen extends StatefulWidget {
@override
_LoadingScreenState createState() => _LoadingScreenState();
}
class _LoadingScreenState extends State<LoadingScreen> {
void getWeatherData() async {
String url =
'http://api.openweathermap.org/data/2.5/weather?lat=35&lon=105&appid=[api키]';
try {
va response = await http.get(url);
if (response.statusCode == 200) {
String data = response.body;
print(data);
} else {
print("Error!");
}
} catch (e) {
print(e);
}
}
@override
Widget build(BuildContext context) {
// build 될 때 실행됩니다.
getWeatherData();
return Scaffold();
}
}
reponse 클래스에는 body 뿐만 아니라 여러 메서드, 속성들이 들어 있습니다. 그래도 정보를 받을 body가 제일 중요하겠죠. 나머지는 (https://pub.dev/documentation/http/latest/http/Response-class.html)에서 확인할 수 있습니다.
그런데, 이렇게 res로 받은 json을 활용하는 방법이 문제입니다. JS에서는 그냥 object처럼 다뤄서 문제될 것이 없었습니다만 flutter에서는 그런 사용 방식이 불가능합니다. flutter에서는 json을 decode한 다음 Map 처럼 활용해야 합니다.
이를 위해 dart:convert 패키지를 import한 다음, json.decode를 통해 reponse의 body를 디코드한 후 Map의 활용법처럼 가져와서 사용하면 됩니다.
여기서 하나의 팁은, json을 불러와서 파싱하는 과정에서 변수 선언을 가급적이면 유연하게 (var) 할당하라는 것입니다. decode가 될 때까지 타입이 무엇인지 모르기 때문에 다이나믹하게 설정해주는 것이 좋습니다. 게다가 해당 정보가 반드시 정갈한 한가지 타입만 있으리라는 법도 없습니다. 따라서 var를 할당합시다.
import 'dart:convert';
void getWeatherData() async {
String url =
'http://api.openweathermap.org/data/2.5/weather?lat=35&lon=105&appid=[api키]';
try {
http.Response response = await http.get(url);
if (response.statusCode == 200) {
String data = response.body;
var decodedJson = json.decode(data);
// 객체 안의 객체 가져오기
print(decodedJson["coord"]["lon"]);
// 객체 안의 리스트 일부 가져오기
print(decodedJson["weather"][0]["main"]);
} else {
print("Error!");
}
} catch (e) {
print(e);
}
// 처리를 다 하면 Redirect 처리합니다. (강제 페이지 이동~)
// 물론 이동시키는 페이지에 정보도 전달해야 합니다.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LocationScreen(
locationData: decodeJson,
)));
}
이렇게 decode된 json을 넘겨받는 위젯은 stateful 위젯일 겁니다.(안 그렇다면 왜 정보를 넘겨줄 일이 없습니다.)
이 경우에 경보를 넘겨 받는 방식은 widget(소문자입니다.) 을 통해서 가능합니다. widget은 연결된 부모 위젯을 가리키므로 여기서 변수나 정보를 빼다가 쓰면 됩니다. (vscode가 준 설명을 읽어보니 A [State] object's configuration is the corresponding [StatefulWidget] instance. 입니다.)
class LocationScreen extends StatefulWidget {
final locationData;
LocationScreen({@required this.locationData});
@override
_LocationScreenState createState() => _LocationScreenState();
}
class _LocationScreenState extends State<LocationScreen> {
@override
void initState() {
super.initState();
// widget은 연결된 부모 위젯을 가리킵니다.
print(widget.locationData);
}
여기서, 정보를 얻은 다음에 다른 페이지로 이동시키기 전에는 Loader를 사용하는게 좋습니다. 웹에서도 무언가 정보를 받아올 때 정보를 받아오는 중이라고 표시하여 유저에게 무엇이 이루어지고 있다는 것을 알려주었듯이 말입니다.
(https://pub.dev/packages/flutter_spinkit#-readme-tab-)
모바일에서는 스피너를 주로 사용합니다. 위의 주소에서 확인합시다. 설치하기가 번거롭다면
CircularProgressIndicator와 같은 플러터 자체 내장 스피너를 사용합시다.
위젯 이름은 간단하게 SpinKit 뒤에 원하는 스피너의 이름을 붙여주면 됩니다. 저는 CubeGrid를 사용할 것이니 SpinKitCubeGrid라는 위젯을 사용했습니다.
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SpinKitCubeGrid (
color: Colors.white,
size: 50.0,
),
),
);
}
플러터 자체 내장 인디케이터를 사용해보았습니다. 정보를 아직 받아오지 못한 상태면 CircularProgressIndicator, 받아 왔으면 정보를 뿌려주면 됩니다. React랑 똑~같습니다.
Scaffold(
body: _results == null
? Center(child: CircularProgressIndicator())
: Padding(
padding: const EdgeInsets.all(8.0),
... 생략
-
api 통신에 있어서 initState 자체에서에서 호출하는 방식으로 작성하고 싶겠지만 initState는 async를 쓸 수 없습니다. initState는 void에 async가 아니어야만 한다는 규칙이 있다. 그러니까 async/await를 외부 함수에서 정의해주고 initState는 그걸을 불러와서 쓰기만 해야 합니다.
-
정보를 받아온 후 화면에 뿌리려면 State가 변경되는 것이므로 Stateful 위젯이어야 하며, 값을 변경해줄 때는 setState를 이용해야 한다.
-
setState를 async로 만들어서 변수를 지정하는 방식은 권장되지 않는다. 작동하지도 않는다. Instead of performing asynchronous work inside a call to setState(), first execute the work (without updating the widget state), and then synchronously update the state inside a call to setState().
-
그러니까 밖에서 async/await 처리를 전부 한 다음에 내부에서는 지정만 해줘야 한다.
dynamic rate = '?';
getData() async {
String apiKey = 'asdfasdf';
String url =
'https://rest.coinapi.io/v1/exchangerate/BTC/$selectedCur?apikey=$apiKey';
try {
var response = await http.get(url);
if (response.statusCode == 200) {
var decodeJson = await json.decode(response.body);
// 곧바로 rate = await decodeJson['rate'].round();를 주면 변경되지 않는다.
int awaitRate = await decodeJson['rate'].round();
setState(() {
// rate = await decodeJson['rate'].round()도 안된다.
// 그렇게 되면 setState가 async가 되어야 하는데 이는 비권장된다.
rate = awaitRate;
});
} else {
print(response.statusCode);
}
} catch (e) {
print(e);
}
}
'📱 Mobile > 📱 (old) Flutter v 1.0' 카테고리의 다른 글
dart.io 패키지를 활용한 플랫폼(iOS, Android) 감지하기 (0) | 2020.05.23 |
---|---|
TextField를 통해 입력받은 값을 다른 페이지에 전달하기 (0) | 2020.05.22 |
stful 위젯의 lifecycle (0) | 2020.05.21 |
빠른 코딩을 위한 상수 전용 페이지 구성과 .copywith 메서드 (0) | 2020.05.20 |
커스텀 위젯 만들기 (0) | 2020.05.19 |