본문 바로가기
삽질하는 개발자 hashblown

프론트엔드 개발자의 TIL #003

by hashblown 2019. 11. 5.

TIL #003

191105 화

 


오늘 배운 점

<Flutter>

1. 텍스트 에디터 

  • zefyr을 사용하는 것이 불가피한 듯하다. 구현 과정이 상당히 복잡할 것으로 예상된다. 오늘은 실습이 아닌 예시 코드를 뜯어보는 정도로만 공부했다.
  • 이미지 업로드 방법은 이미 이전 TIL에 작성하였다.
  • 영상을 업로드하려면 패키지 flutter_uploader가 별도로 설치되어야 한다.
  • 비디오 재생하는 플러그인은 Flutter팀이 공식 배포한 video_player을 활용한다. permission 작업은 운영체제마다 Manifest 파일에 수동으로 해주어야 한다.
  • 참고 링크 https://learningflutter.net/flutter-markdown-editor/

 

 

Build A Markdown Editor With Flutter

Before this tutorial you should have set up your development environment with How to Install Flutter on Mac OS and ran your first…

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)),

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/

 

Flutter - Token based communication with server - part 1

Flutter - This article gives an introduction to the notion of token-based, secured communication between the Flutter application and Web Server. It describes a generic protocol and flow based on Web API but without focusing on any standard such as OAuth2 p

www.didierboelens.com

https://soul0.tistory.com/414

 

token (토큰이란) ? 무엇일까? 왜 발생할까?

token (토큰이란) ? 무엇일까? 왜 발생할까? 토큰이란 무엇일까? * 토큰(Token) 의 사전적 정의 프로그래밍 언어에서의 토큰은, 문법적으로 더 이상 나눌 수 없는 기본적인 언어요소를 말하는데, 예를 들어 하나의..

soul0.tistory.com

https://tansfil.tistory.com/58

 

쉽게 알아보는 서버 인증 1편(세션/쿠키 , JWT)

앱 개발을 처음 배우게 됐을 때, 각종 화면을 디자인해보면서 프론트엔드 개발에 큰 흥미가 생겼습니다. 한때 프론트엔드 개발자를 꿈꾸기도 했었죠(현실은 ...) 그러나 서버와 통신을 처음 배웠을 때 마냥 쉬운..

tansfil.tistory.com

 


내일 배울 것

<Flutter>

1. 애니메이션 관련 위젯

2. Builder 개념 재정리

3. 소셜로그인 with Google

 


더보기

- 역시 네트워크가 들어가는 순간 내용이 배로 어려워진다. 이번에 컴퓨터네트워크 전공 수업을 병행하며 공부하니 그래도 이해가 수월하다.

- 사소한 것 하나라도 매일 공부하는 데에 의의를 두어야겠다. 특정 분야에 지속적인 관심을 가지고 노력하는 게 쉽지 않다는 것을 깨닫고 있다.

- 그래도 조금식 탄력이 붙는 것 같다! 습관으로 자리잡았으면 좋겠다.

댓글