30. 플러터 : 서버/클라이언트 연동 (5)

http 프로토콜을 다루는 방식에는 여러 가지가 있지만
get과 post 정도만 사용해도 왠만한 것은 처리할 수 있다.
지금까지는 get 방식을 사용해서 서버로부터 데이터를 수신하기만 했다.
get 방식은 가져오기만 하고 보낼 수는 없다.

이번 글에서는 post 방식을 통해
서버로 데이터를 전송하고 결과를 수신하는 코드를 만든다.

상단 왼쪽의 위젯은 사용자 입력을 받는 TextField이고
상단 오른쪽의 위젯은 덧셈과 곱셈 연산의 결과를 보여주는 Text 위젯이다.
서버로 보내는 데이터가 너무 단순해서 실망할 수도 있겠지만
이번 코드를 조금만 확장하면
딥러닝 모델에서 예측해야 하는 데이터를 전송하기에 부족함이 없다.



서버 코드는 이전에 비해 훨씬 간결하게 처리했다.
덧셈과 곱셈 서비스인 add와 multiply 함수밖에 없지만,
함수 위쪽에 지정된 methods에 POST가 추가되어 있음을 눈여겨 보도록 한다.

클라이언트(플러터 앱)로부터 수신한 데이터는 모두 문자열이기 때문에
계산하기 전에 int 함수로 형 변환을 해야 한다.

참.. IP 주소는 자신의 컴퓨터 주소를 쓰는 것도 잊지 말자.
서버 주소가 계속해서 바뀐다.
도서관에서 작업하다가 집에 와서도 계속이다.
어제는 대전에 있었고 토요일 아침인 지금은 강원도 망고서프에 있다.
예제를 구동할 때마다 번거롭다.


from flask import Flask, request

app = Flask(__name__)


@app.route('/add', methods=['POST'])
def add():
left = request.form['left']
rite = request.form['rite']

return str(int(left) + int(rite))


@app.route('/multiply', methods=['POST'])
def multiply():
left = request.form['left']
rite = request.form['rite']

return str(int(left) * int(rite))


if __name__ == '__main__':
app.run(host='192.168.0.125', debug=True)


플러터 앱을 만드고 나서
pubspec.yaml 파일에 http 모듈을 추가하도록 한다.
모든 설명은 코드에 직접 달았으니 코드를 꼼꼼하게 살펴보도록 한다.


import 'dart:async';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;


void main() => runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('서버/클라이언트')),
body: MyApp(),
))
);

class MyApp extends StatefulWidget {
@override
State createState() => MyAppState();
}

// 덧셈/곱셈 상수 정의
enum DataKind { NONE, ADD, MULTIPLY }

// 서버가 동작하는 컴퓨터의 IP 주소. 192는 사설 IP, 5000은 플라스크 기본 포트.
final gServerIp = 'http://192.168.0.125:5000/';

class MyAppState extends State<MyApp> {
DataKind mKind = DataKind.NONE;

String mResult = '0'; // 서버로부터 수신한 덧셈 또는 곱셈 결과
String mLeftText, mRiteText; // 사용자가 입력한 연산의 왼쪽과 오른쪽 항의 값

// post 동작의 결과를 수신할 비동기 함수
// 연산할 데이터를 전달(post)해야 하기 때문에 멤버로 만들어야 했다.
Future<String> postReply() async {
if(mLeftText == null || mRiteText == null)
return '';

// 문자열 이름은 서버에 정의된 add와 multiply 서비스
var addr = gServerIp + ((mKind == DataKind.ADD) ? 'add' : 'multiply');
var response = await http.post(addr, body: {'left': mLeftText, 'rite': mRiteText});

// 200 ok. 정상 동작임을 알려준다.
if (response.statusCode == 200)
return response.body;

// 데이터 수신을 하지 못했다고 예외를 일으키는 것은 틀렸다.
// 여기서는 코드를 간단하게 처리하기 위해.
throw Exception('데이터 수신 실패!');
}

@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded(
child: Padding(
// TextField 위젯은 사용자 입력을 받는다.
// Expanded 또는 Flexible 등의 위젯의 자식이어야 한다.
child: TextField(
textAlign: TextAlign.center,
style: TextStyle(fontSize: 25),
keyboardType: TextInputType.number, // 숫자만 입력
onChanged: (text) => mLeftText = text, // 입력할 때마다 호출
),
padding: EdgeInsets.all(10.0),
),
),
Expanded(
child: Padding(
child: TextField(
textAlign: TextAlign.center,
style: TextStyle(fontSize: 25),
keyboardType: TextInputType.number,
onChanged: (text) => mRiteText = text,
),
padding: EdgeInsets.all(10.0),
),
),
Expanded(
child: Container(
child: Padding(
// 연산 결과 표시. Text는 보여주기 전용 위젯
child: Text(mResult,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 25),
),
padding: EdgeInsets.all(10.0),
),
color: Colors.orange,
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
RaisedButton(
child: Text('덧셈'),
onPressed: () {
if(mLeftText != null && mRiteText != null) {
mKind = DataKind.ADD;
// postReply 함수는 비동기 함수여서 지연 처리
// then : 수신 결과를 멤버변수에 저장
// whenComplete : // 비동기 연산 완료되면 상태 변경
try {
postReply()
.then((recvd) => mResult = recvd)
.whenComplete(() {
if(mResult.isEmpty == false)
setState(() {});
});
} catch (e, s) {
print(s);
}
}
},
),
RaisedButton(
child: Text('곱셈'),
onPressed: () {
// 입력했다가 삭제하면 빈 문자열이 될 수 있다.
// 빈 문자열은 계산할 수 없으므로 예외 발생
// 숫자로 변환할 수 없는 문자열에 대해서도 예외가 발생하지만
// 숫자만 받을 수 있는 키보드를 사용함으로 해결했다.
if (mLeftText != null && mRiteText != null) {
mKind = DataKind.MULTIPLY;
postReply()
.then((recvd) => mResult = recvd)
.whenComplete(() {
if(mResult.isEmpty == false)
setState(() {});
});
}
}
),
],
),
],
);
}
}