TIL #004 네트워크 수업, 실전에 응용해보자
2019. 11. 5. 23:36ㆍ공부/TIL
TIL #004
191105 화
오늘 배운 점
<Flutter>
1. 텍스트 에디터
- zefyr을 사용하는 것이 불가피한 듯하다. 구현 과정이 상당히 복잡할 것으로 예상된다. 오늘은 실습이 아닌 예시 코드를 뜯어보는 정도로만 공부했다.
- 이미지 업로드 방법은 이미 이전 TIL에 작성하였다.
- 영상을 업로드하려면 패키지 flutter_uploader가 별도로 설치되어야 한다.
- 비디오 재생하는 플러그인은 Flutter팀이 공식 배포한 video_player을 활용한다. permission 작업은 운영체제마다 Manifest 파일에 수동으로 해주어야 한다.
- 참고 링크 https://learningflutter.net/flutter-markdown-editor/
learningflutter.net
2. 관리자 모드(admin side app)
- Firebase와 다수의 dependency를 활용해서 DB에서 상품 정보를 가져오고, 관련 통계를 화면에 출력하는 간단한 관리자 모드 앱 코딩을 실습해보았다.
- DB의 정보를 가져와서 필요한 통계 자료들을 구성하기 위한 설계를 사용자 앱 구상 단계에 미리미리 해야 한다.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:uuid/uuid.dart';
class BrandService{
Firestore _firestore = Firestore.instance;
void createBrand(String name){
var id = Uuid();
String brandId = id.v1();
_firestore.collection('brands').document(brandId).setData({'brand': name});
}
}
class CategoryService{
Firestore _firestore = Firestore.instance;
void createCategory(String name){
var id = Uuid();
String categoryId = id.v1();
_firestore.collection('categories').document(categoryId).setData({'category': name});
}
}
enum Page { dashboard, manage }
class Admin extends StatefulWidget {
@override
_AdminState createState() => _AdminState();
}
class _AdminState extends State<Admin> {
Page _selectedPage = Page.dashboard;
MaterialColor active = Colors.red;
MaterialColor notActive = Colors.grey;
TextEditingController categoryController = TextEditingController();
TextEditingController brandController = TextEditingController();
GlobalKey<FormState> _categoryFormKey = GlobalKey();
GlobalKey<FormState> _brandFormKey = GlobalKey();
BrandService _brandService = BrandService();
CategoryService _categoryService = CategoryService();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
children: <Widget>[
Expanded(
child: FlatButton.icon(
onPressed: () {
setState(() => _selectedPage = Page.dashboard);
},
label: Text('Dashboard'))),
Expanded(
child: FlatButton.icon(
onPressed: () {
setState(() => _selectedPage = Page.manage);
},
label: Text('Manage'))),
],
),
),
body: _loadScreen());
}
Widget _loadScreen() {
switch (_selectedPage) {
case Page.dashboard:
return Column(
children: <Widget>[
ListTile(
title: Text(
'Revenue',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 24.0, color: Colors.grey),
),
),
Expanded(
child: GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2),
children: <Widget>[
Card(
child: ListTile(title: FlatButton.icon(label: Text("Users")),),
),
Card(
child: ListTile(title: FlatButton.icon(label: Text("Categories")),),
),
Card(
child: ListTile(title: FlatButton.icon(label: Text("Products")),),
),
Card(
child: ListTile(title: FlatButton.icon(label: Text("Sold")),),
),
Card(
child: ListTile(title: FlatButton.icon(label: Text("Orders")),),
),
Card(
child: ListTile(title: FlatButton.icon(label: Text("Return")),),
),
],
),
),
],
);
break;
case Page.manage:
return ListView(
children: <Widget>[
ListTile(title: Text("Add product"),),
ListTile(title: Text("Products list"),),
ListTile(title: Text("Add category"),
onTap: () {_categoryAlert();},),
ListTile(title: Text("Category list"),),
ListTile(title: Text("Add brand"),
onTap: () {_brandAlert();},),
ListTile(title: Text("brand list"),),
],
);
break;
default:
return Container();
}
}
void _categoryAlert() {
var alert = AlertDialog(//카테고리 알림
content: Form(
key: _categoryFormKey,
child: TextFormField(
controller:
validator: (value) {}
),
),
actions: <Widget>[
FlatButton(onPressed: (){}, child: Text('ADD')),
FlatButton(onPressed: (){}, child: Text('CANCEL')),
],
);
showDialog(context: context, builder: (_) => alert);
}
void _brandAlert() {} //같은 방법으로 브랜드 알림
참고 링크: https://github.com/Santos-Enoque/admin_side_flutter_ecommerce_app/blob/master/lib/screens/admin.dart
3. 이 주의 위젯 공부 - MediaQuery
- 앱의 UI를 디바이스 화면 및 사용자 정의에 따라 조정해주는 위젯이다. 화면 크기뿐 아니라 화면 방향, 폰트 크기 등을 조절할 수도 있다.
- MediaQuery로 EdgeInset을 주는 건 lower-level SafeArea 위젯 역할과 같다.
build(BuildContext context) {
MediaQuery.of(context).devicePixelRatio; //화면 비율 관련
var deviceOrientation = MediaQuery.of(context).orientation; //화면 방향
var fontScaling = MediaQuery.of(context).textScaleFactor; //폰트 크기
var notchInset = MediaQuery.of(context).padding; //screen occlusion
var noAnimations = MediaQuery.of(context).disableAnimations; //애니메이션 제어
var screenContrast = MediaQuery.of(context).platformBrightness; //화면 대비
}
//화면 크기
Size screenSize(BuildContext context) {
return MediaQuery.of(context).size;
}
//화면 높이
double screenHeight(BuildContext context, {double dividedBy = 1, double reducedBy = 0.0}) {
return (screenSize(context).height - reducedBy) / dividedBy;
}
//화면 너비
double screenWidth(BuildContext context, {double dividedBy = 1, double reducedBy = 0.0}) {
return (screenSize(context).width - reducedBy) / dividedBy;
}
//상단 툴바를 제외한 화면 높이
double screenHeightExcludingToolbar(BuildContext context, {double dividedBy = 1}) {
return screenHeight(context, dividedBy: dividedBy, reducedBy: kToolbarHeight);
}
Container(height: screenHeightExcludingToolbar(context, dividedBy:3)),
- MediaQuery의 다른 속성들 https://www.woolha.com/tutorials/flutter-using-mediaquery-examples
4. 소셜로그인 후 토큰으로 웹 세션 유지하기 (Token based communication with server)
- 토큰은 매 요청마다 클라이언트와 서버끼리 주고받는(stateful) 통행료이며, 본인 확인 목적으로 발행되어 사용자는 웹서비스를 이용할 수 있는 권리를 획득한다. 유효 기간이 있으며, SSL(보안 소켓 계층) 인증서 등을 통해 암호화하여 안전성을 확보할 수 있다.
- 최초 TCP 연결을 위한 Handshake token과 인증 요청 후 또는 토큰이 만료될(만료된) 경우 생성되는 Communication token으로 2가지가 있다.
//dependencies @pubspec.yaml
dependencies:
flutter:
sdk: flutter
device_info:
shared_preferences:
http:
//Handshake token
var response = await http.get(url, headers: {
'X-DEVICE-ID': 'my_device_id',
'X-TOKEN': '',
'X-APP-ID': 'my_application_id'
});
if (response.statusCode == 200) {
String token = response.body;
}
const String _applicationId = "my_application_id";
const String _storageKeyMobileToken = "token"; //토큰 저장
const String _urlBase = "https://www.myserver.com";
const String _serverApi = "/api/mobile/";
String _deviceIdentity = "";
//디바이스 정보를 얻기 위한 메서드
final DeviceInfoPlugin _deviceInfoPlugin = new DeviceInfoPlugin();
Future<String> _getDeviceIdentity() async {
if (_deviceIdentity == '') {
try {
if (Platform.isAndroid) {
AndroidDeviceInfo info = await _deviceInfoPlugin.androidInfo;
_deviceIdentity = "${info.device}-${info.id}";
} else if (Platform.isIOS) {
IosDeviceInfo info = await _deviceInfoPlugin.iosInfo;
_deviceIdentity = "${info.model}-${info.identifierForVendor}";
}
} on PlatformException {
_deviceIdentity = "unknown";
}
}
return _deviceIdentity;
}
//Shared Preference에서 토큰을 반환
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
Future<String> _getMobileToken() async {
final SharedPreferences prefs = await _prefs;
return prefs.getString(_storageKeyMobileToken) ?? '';
}
//Shared Preference에 토큰 저장
Future<bool> _setMobileToken(String token) async {
final SharedPreferences prefs = await _prefs;
return prefs.setString(_storageKeyMobileToken, token);
}
//Http GET 요청
Future<String> ajaxGet(String serviceName) async {
var responseBody = '{"data": "", "status": "NOK"}';
try {
var response = await http.get(_urlBase + '/$_serverApi$serviceName',
headers: {
'X-DEVICE-ID': await _getDeviceIdentity(),
'X-TOKEN': await _getMobileToken(),
'X-APP-ID': _applicationId
});
if (response.statusCode == 200) {
responseBody = response.body;
}
} catch (e) {
throw new Exception("AJAX ERROR");
}
return responseBody;
}
//Http POST 요청
Future<Map> ajaxPost(String serviceName, Map data) async {
var responseBody = json.decode('{"data": "", "status": "NOK"}');
try {
var response = await http.post(_urlBase + '/$_serverApi$serviceName',
body: json.encode(data),
headers: {
'X-DEVICE-ID': await _getDeviceIdentity(),
'X-TOKEN': await _getMobileToken(),
'X-APP-ID': _applicationId,
'Content-Type': 'application/json; charset=utf-8'
});
if (response.statusCode == 200) {
responseBody = json.decode(response.body);
// 새로운 토큰 받으면 저장하기
if (responseBody["status"] == "TOKEN") {
await _setMobileToken(responseBody["data"]);
// 여기에 POST 요청 rerun
}
}
} catch (e) {
throw new Exception("AJAX ERROR");
}
return responseBody;
}
- 참고 링크들
https://www.didierboelens.com/2018/05/token-based-communication-with-server---part-1/
www.didierboelens.com
soul0.tistory.com
https://tansfil.tistory.com/58
tansfil.tistory.com
내일 배울 것
<Flutter>
1. 애니메이션 관련 위젯
2. Builder 개념 재정리
3. 소셜로그인 with Google
더보기
- 역시 네트워크가 들어가는 순간 내용이 배로 어려워진다. 이번에 컴퓨터네트워크 전공 수업을 병행하며 공부하니 그래도 이해가 수월하다.
- 사소한 것 하나라도 매일 공부하는 데에 의의를 두어야겠다. 특정 분야에 지속적인 관심을 가지고 노력하는 게 쉽지 않다는 것을 깨닫고 있다.
- 그래도 조금식 탄력이 붙는 것 같다! 습관으로 자리잡았으면 좋겠다.
'공부 > TIL' 카테고리의 다른 글
TIL #006 다시 플러터 (0) | 2019.11.08 |
---|---|
TIL #005 Tensorflow 맛보기 (0) | 2019.11.07 |
TIL #003 Flutter 1.9의 새로운 위젯들 (0) | 2019.11.04 |
TIL#002 Cookies in SNS (0) | 2019.11.02 |
TIL #001 여유롭게 시작해보기 (0) | 2019.11.01 |