티스토리 뷰
앱이 서버와 서로 통신하면서 데이터를 주고 받으려면 네트워크로 연결해야 한다.
네트워크와 열결하는 통신규약을 HTTP라고 한다.
HTTP
- 클라이언트와 서버 사이에 이루어지는 요청/응답(request/response) 프로토콜이다.
- 클라이언트인 웹 브라우저가 HTTP를 통하여 서버로부터 웹페이지(HTML)나 그림 정보를 요청하면, 서버는 이 요청에 응답하여 필요한 정보를 해당 사용자에게 전달하게 된다.
- 이 정보가 모니터와 같은 출력 장치를 통해 사용자에게 나타나는 것이다.
API
- Application Programming Interface 애플리케이션 프로그래밍 인터페이스 응용 프로그램 프로그래밍 인터페이스
- 응용프로그램에서 사용할 수 있도록, OS나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만든 인터페이스
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HttpApp(),
);
}
}
class HttpApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => _HttpApp();
}
class _HttpApp extends State<HttpApp> {
String result = '';
TextEditingController? _editingController;
ScrollController? _scrollController;
List? data;
int page = 1;
@override
void initState() {
super.initState();
data = new List.empty(growable: true);
_editingController = new TextEditingController();
_scrollController = new ScrollController();
_scrollController!.addListener(() {
if (_scrollController!.offset >=
_scrollController!.position.maxScrollExtent &&
!_scrollController!.position.outOfRange) {
print('bottom'); // 리스트의 마지막 일 때 실행
page++;
getJSONData();
}
});
}
/* initState()에서 new로 생성한 스크롤 컨트롤러의 addListener() 함수를 이용해 스크롤할 때 이벤트를 받도록 처리한다.
offset은 목록에서 현재 위치를 double형 변수로 나타낸다.
스크롤할 때마다 offset을 검사해 maxScrollExtent보다 크거나 같고 스크롤 컨트롤러의 position에 정의된 범위를 넘어가지 않으면 목록의 마지막이라고 인식한다. 그러면 page를 1만큼 중가한 후 getJSONData() 함수를 호출한다.
*/
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: TextField( //앱바 부분에 텍스트 필드 이용하여 입력
controller: _editingController,
style: TextStyle(color: Colors.white),
keyboardType: TextInputType.text,
decoration: InputDecoration(hintText: '검색어를 입력하세요'),
// decoration은 텍스트 필드 위젯에 보이는 텍스트를 꾸미는 옵션
),
),
body: Container(
child: Center(
child: data!.length == 0 //data가 0일시에는 해당 text를 표시한다.
? Text(
'데이터가 존재하지 않습니다.\n검색해주세요',
style: TextStyle(fontSize: 20),
textAlign: TextAlign.center,
)
: ListView.builder( // 서버로부터 데이터를 받으면 ListView.builder로 표시한다.
itemBuilder: (context, index) {
print(data![index]['thumbnail']);
return Card(
child: Container(
child: Row( //Row와 Column을 이용해 위젯을 배치한다
children: <Widget>[
if(data?[index]['thumbnail'] != '')
Image.network( // 네트워크에 있는 이미지를 가져오는 위젯
data![index]['thumbnail'],
//data![index]['thumbnail'] 에서 가져온 URL을 이용해 간단하게 화면에 이미지 출력
height: 100,
width: 100,
fit: BoxFit.contain,
) else Container( height: 100,
width: 100,),
Column( //Row와 Column을 이용해 위젯을 배치한다
children: <Widget>[
Container(
width:
MediaQuery.of(context).size.width - 150,
// MediaQuery.of(context).size는 지금 스마트폰의 하면크기를 의미
child: Text(
data![index]['title'].toString(),
textAlign: TextAlign.center,
),
),
Text(
'저자 : ${data![index]['authors'].toString()}'),
Text(
'가격 : ${data![index]['sale_price'].toString()}'),
Text(
'판매중 : ${data![index]['status'].toString()}'),
],
)
],
mainAxisAlignment: MainAxisAlignment.start,
),
),
);
},
itemCount: data!.length,
controller: _scrollController,
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
page = 1;
data!.clear();
getJSONData();
// 플로팅 버튼 누를 시 getJSONData 함수 호출된다. 이는 비동기로 데이터를 주고받기 위해 비동기처리
// 플로팅 버튼 누를 때마다 page의 값은 1로 초기화 되며 data 리스트도 clear된다.
},
child: Icon(Icons.search),
),
);
}
Future<String> getJSONData() async { // 비동기 처리 방식
var url =
'https://dapi.kakao.com/v3/search/book?target=title&page=$page&query=${_editingController!.value.text}';
// _editingController!.value.text : 컨트롤러에서 받은 데이터를 URL의 질의에 적용할 수 있도록
/*
- https://dapi.kakao.com/v3/search/book? : 요청할 도메인으로 카카오에서 책을 검색하는 API를 나타낸다.
- target=title 도메인에 요청할 파라미터로 target 파라미터에 title을 전달한다.
- &page=$page 파라미터를 추가하여 페이지 단위로 데이터를 가져올수 있게 구현
*/
var response = await http.get(Uri.parse(url),
headers: {"Authorization": "KakaoAK aa51bf3d875ea350a1d8bd05de36d8b8"});
// await를 통해서 서버가 데이터를 넘겨줄때 까지 대기한다.
print(response.body); // 검색 결과 로그창으로 확인
setState(() {
var dataConvertedToJSON = json.decode(response.body); // data를 JSON 형태로 디코딩함
List result = dataConvertedToJSON['documents'];
data!.addAll(result);
});
return response.body;
}
}
앱으로 부터 용량이 큰 이미지 파일을 내려받아야 할 때 사용자는 파일을 내려받을 때까지 기다려야 하는데 3초이상 아무런 반응이 없으면 앱이 멈춘것으로 간주한다. 따라서 파일을 내려받을 때 진행 상황을 표시해 주면 사용자가 기다릴 수 있게 된다.
서버에서 이미지 파일을 내려받는 앱을 만들기 위해서는 dio라는 패키지와 내부 저장소를 이용하는 path_provider라는 패키지를 이용해야 한다.
1. largeFileMain.dart
class _LargeFileMain extends State<LargeFileMain> {
bool downloading = false;
var progressString = "";
String? file = "";
TextEditingController? _editingController;
@override
void initState() {
super.initState();
_editingController = new TextEditingController(text: 'https://www.motherjones.com/wp-content/
uploads/2019/12/Getty121719.jpg?w=1200&h=630&crop=1'); // 기본 URL
}
Future<void> downloadFile() async { //파일을 내려받는 함수
Dio dio = Dio();
try {
var dir = await getApplicationDocumentsDirectory();
//path_provider 패키지가 제공하며 플러터 앱의 내부 디렉터리를 가져오는 역할을 한다.
/*
- dio.download를 이용해 url에 담긴 주소에서 파일을 내려받는다. 내려받은 파일은 내부디렉토리 안에 myimage.jpg라는 이름으로 저장한다. 이때 데이터를 받을 때마다 onRecieveProgress()함수를 실행해 진행상황을 표시한다. 이 함수가 전달받은 rec은 지금까지 내려받은 데이터, total은 파일의 전체 크기를 의미한다.
- dio.download 함수에서 첫번째 인자로 사용자가 입력한 주소를 전달한다.
*/
await dio.download(_editingController!.value.text, '${dir.path}/myimage.jpg',
onReceiveProgress: (rec, total) {
print('Rec: $rec , Total: $total');
file = '${dir.path}/myimage.jpg';
setState(() {
downloading = true; // 내려받기 시작되면 true선언
progressString = ((rec / total) * 100).toStringAsFixed(0) + '%';
});
});
} catch (e) {
print(e);
}
setState(() {
downloading = false;
progressString = 'Completed';
});
print('Download completed');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TextField( //URL 입력 받기위해 생성
controller: _editingController,
style: TextStyle(color: Colors.white),
keyboardType: TextInputType.text,
decoration: InputDecoration(hintText: 'url 입력하세요'),
),
),
body: Center(
child: downloading //true일 경우
? Container(
height: 120.0,
width: 200.0,
child: Card(
color: Colors.black,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator(),
SizedBox( //고정된 높이와 넓이 안에서 네모난 형태의 위젯을 만들 수 있음
height: 20.0,
),
Text(
'Downloading File: $progressString',
style: TextStyle(
color: Colors.white,
),
)
],
),
),
)
/*
- FutureBuilder를 통해서 이미지 내려받기 화면을 만든다. 해당 위젯은 아직은 데이터가 없지만 앞으로 데이터를 받아서 처리한 후에 만들겠다는 의미이다.
- 파일의 입출력이나 네트워크 통신을 구현할 때는 대부분 비동기 방식으로 처리하기 때문에 Future을 이용한다.
- FutureBuilder는 builder에서 snapshot이라는 변수를 반환하는데, 이는 FutureBuilder.future에서 받아온 데이터를 저장한 dynamic형태의 변수이다. snapshot.connectionState를 이용해 switch문으로 데이터를 받을 때, 오류가 발생할 때, 데이터가 완료되었을때를 나누어 처리한다.
*/
: FutureBuilder( //downloading이 false일 경우
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none: // FutureBuilder.future 가null일 때
print('none');
return Text('데이터 없음');
case ConnectionState.waiting: // 연결되기 전 데이터를 반환받지 않았을 때
print('waiting');
return CircularProgressIndicator();
case ConnectionState.active: // 하나 이상의 데이터를 반환받았을 때
print('active');
return CircularProgressIndicator();
case ConnectionState.done: // 모든 데이터를 받아서 연결이 끝났을 때
print('done');
if (snapshot.hasData) {
return snapshot.data as Widget;
}
}
return Text('데이터 없음');
},
future: downloadWidget(file!),
)),
floatingActionButton: FloatingActionButton( //플로팅 버튼을 누르면 downloadFile함수를 실행한다.
onPressed: () {
downloadFile();
},
child: Icon(Icons.file_download),
),
);
}
/*
- downloadWidget 함수는 이미지 파일이 있는지 확인해서 있으면 이미지를 화면에 보여주는 위젯반환한다.
- 그리고 evict() 함수는 캐시를 초기화한다.
- 플러터는 빠른 이미지 처리를 위해 캐시에 같은 이름의 이미지가 있으면 이미지를 변경하지 않고 해당 이미지를 사용한다. 이때 evict() 함수를 호출해 캐시를 비우면 같은 이름이어도 이미지를 갱신한다.
*/
Future<Widget> downloadWidget(String filePath) async {
File file = File(filePath);
bool exist = await file.exists();
new FileImage(file).evict(); // 캐시 초기화하기
if (exist) {
return Center(
child: Column(
children: <Widget>[Image.file(file)],
),
);
} else {
return Text('No Data');
}
}
}
'플러터 앱 스터디 일지' 카테고리의 다른 글
플러터 스터디 - chap 9 (0) | 2021.10.11 |
---|---|
플러터 스터디 chap 8 (0) | 2021.10.06 |
플러터 스터디 chap 6 - ios 스타일 Cupertino 위젯 사용 (0) | 2021.09.28 |
플러터 스터디 chap 5 (0) | 2021.09.27 |
플러터 스터디 chap 4 (0) | 2021.09.26 |